package controller import ( "errors" "fmt" "strconv" "strings" "time" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/service" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type createChannelMonitorRequest struct { Name string `json:"name"` ChannelId int `json:"channel_id"` TargetUrl string `json:"target_url"` PrimaryModel string `json:"primary_model"` ExtraModels string `json:"extra_models"` CheckInterval int `json:"check_interval"` Timeout int `json:"timeout"` RequestTemplates string `json:"request_templates"` CustomHeaders string `json:"custom_headers"` CustomBody string `json:"custom_body"` Status *int `json:"status"` } type updateChannelMonitorRequest struct { Name *string `json:"name"` ChannelId *int `json:"channel_id"` TargetUrl *string `json:"target_url"` PrimaryModel *string `json:"primary_model"` ExtraModels *string `json:"extra_models"` CheckInterval *int `json:"check_interval"` Timeout *int `json:"timeout"` RequestTemplates *string `json:"request_templates"` CustomHeaders *string `json:"custom_headers"` CustomBody *string `json:"custom_body"` Status *int `json:"status"` } func GetChannelMonitors(c *gin.Context) { pageInfo := getChannelMonitorPageQuery(c) monitors, total, err := model.GetChannelMonitors(pageInfo.GetPage(), pageInfo.GetPageSize()) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(monitors) common.ApiSuccess(c, pageInfo) } func CreateChannelMonitor(c *gin.Context) { var req createChannelMonitorRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } monitor := &model.ChannelMonitor{ Name: strings.TrimSpace(req.Name), ChannelId: req.ChannelId, TargetUrl: strings.TrimSpace(req.TargetUrl), PrimaryModel: strings.TrimSpace(req.PrimaryModel), ExtraModels: strings.TrimSpace(req.ExtraModels), CheckInterval: req.CheckInterval, Timeout: req.Timeout, RequestTemplates: strings.TrimSpace(req.RequestTemplates), CustomHeaders: strings.TrimSpace(req.CustomHeaders), CustomBody: req.CustomBody, Status: 1, } if req.Status != nil { monitor.Status = *req.Status } if monitor.CheckInterval == 0 { monitor.CheckInterval = 300 } if monitor.Timeout == 0 { monitor.Timeout = 30 } if err := validateChannelMonitor(monitor); err != nil { common.ApiErrorMsg(c, err.Error()) return } if err := monitor.Insert(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, monitor) } func GetChannelMonitor(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } common.ApiSuccess(c, monitor) } func UpdateChannelMonitor(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } var req updateChannelMonitorRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if req.Name != nil { monitor.Name = strings.TrimSpace(*req.Name) } if req.ChannelId != nil { monitor.ChannelId = *req.ChannelId } if req.TargetUrl != nil { monitor.TargetUrl = strings.TrimSpace(*req.TargetUrl) } if req.PrimaryModel != nil { monitor.PrimaryModel = strings.TrimSpace(*req.PrimaryModel) } if req.ExtraModels != nil { monitor.ExtraModels = strings.TrimSpace(*req.ExtraModels) } if req.CheckInterval != nil { monitor.CheckInterval = *req.CheckInterval } if req.Timeout != nil { monitor.Timeout = *req.Timeout } if req.RequestTemplates != nil { monitor.RequestTemplates = strings.TrimSpace(*req.RequestTemplates) } if req.CustomHeaders != nil { monitor.CustomHeaders = strings.TrimSpace(*req.CustomHeaders) } if req.CustomBody != nil { monitor.CustomBody = *req.CustomBody } if req.Status != nil { monitor.Status = *req.Status } if err := validateChannelMonitor(monitor); err != nil { common.ApiErrorMsg(c, err.Error()) return } if err := monitor.Update(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, monitor) } func DeleteChannelMonitor(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } monitor.Status = 0 if err := monitor.Update(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, nil) } func RunChannelMonitorCheck(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } if err := service.RunMonitorCheck(monitor); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, gin.H{"message": "检查已完成"}) } func GetChannelMonitorHistory(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } pageInfo := getChannelMonitorPageQuery(c) startTime, ok := parseChannelMonitorOptionalTime(c, "start_time") if !ok { return } endTime, ok := parseChannelMonitorOptionalTime(c, "end_time") if !ok { return } histories, total, err := model.GetMonitorHistory( monitor.Id, pageInfo.GetPage(), pageInfo.GetPageSize(), strings.TrimSpace(c.Query("model_name")), strings.TrimSpace(c.Query("status")), startTime, endTime, ) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(histories) common.ApiSuccess(c, pageInfo) } func GetChannelMonitorRollup(c *gin.Context) { monitor, ok := getChannelMonitorFromParam(c) if !ok { return } startDate := strings.TrimSpace(c.Query("start_date")) endDate := strings.TrimSpace(c.Query("end_date")) if err := validateChannelMonitorDateRange(startDate, endDate); err != nil { common.ApiErrorMsg(c, err.Error()) return } rollups, err := model.GetMonitorDailyRollups( monitor.Id, strings.TrimSpace(c.Query("model_name")), startDate, endDate, ) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, rollups) } func getChannelMonitorFromParam(c *gin.Context) (*model.ChannelMonitor, bool) { id, err := strconv.Atoi(c.Param("id")) if err != nil { common.ApiError(c, err) return nil, false } monitor, err := model.GetChannelMonitorByID(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { common.ApiErrorMsg(c, "渠道监控不存在") return nil, false } common.ApiError(c, err) return nil, false } return monitor, true } func getChannelMonitorPageQuery(c *gin.Context) *common.PageInfo { pageInfo := common.GetPageQuery(c) if c.Query("p") == "" { if page, err := strconv.Atoi(c.Query("page")); err == nil && page > 0 { pageInfo.Page = page } } return pageInfo } func validateChannelMonitor(monitor *model.ChannelMonitor) error { if strings.TrimSpace(monitor.Name) == "" { return errors.New("name 不能为空") } if strings.TrimSpace(monitor.TargetUrl) == "" { return errors.New("target_url 不能为空") } if strings.TrimSpace(monitor.PrimaryModel) == "" { return errors.New("primary_model 不能为空") } if monitor.CheckInterval < 15 || monitor.CheckInterval > 3600 { return errors.New("check_interval 必须在 15 到 3600 之间") } if monitor.Timeout < 1 || monitor.Timeout > 300 { return errors.New("timeout 必须在 1 到 300 之间") } if monitor.Status != 0 && monitor.Status != 1 { return errors.New("status 必须是 0 或 1") } if err := service.ValidateChannelMonitorTargetURL(monitor.TargetUrl); err != nil { return fmt.Errorf("target_url 不合法: %w", err) } if err := validateChannelMonitorJSONStringArray(monitor.ExtraModels, "extra_models"); err != nil { return err } if err := validateChannelMonitorJSONStringArray(monitor.RequestTemplates, "request_templates"); err != nil { return err } if err := validateChannelMonitorJSONHeaders(monitor.CustomHeaders); err != nil { return err } return nil } func validateChannelMonitorJSONStringArray(raw string, field string) error { raw = strings.TrimSpace(raw) if raw == "" { return nil } var values []string if err := common.Unmarshal([]byte(raw), &values); err != nil { return fmt.Errorf("%s 必须是 JSON 字符串数组", field) } for _, value := range values { if strings.TrimSpace(value) == "" { return fmt.Errorf("%s 不能包含空字符串", field) } } return nil } func validateChannelMonitorJSONHeaders(raw string) error { raw = strings.TrimSpace(raw) if raw == "" { return nil } var headers map[string]string if err := common.Unmarshal([]byte(raw), &headers); err != nil { return errors.New("custom_headers 必须是 JSON 字符串对象") } for key := range headers { if strings.TrimSpace(key) == "" { return errors.New("custom_headers 不能包含空 header 名") } } return nil } func parseChannelMonitorOptionalTime(c *gin.Context, key string) (*time.Time, bool) { value := strings.TrimSpace(c.Query(key)) if value == "" { return nil, true } parsed, err := time.Parse(time.RFC3339, value) if err != nil { common.ApiErrorMsg(c, key+" 必须是 RFC3339 格式") return nil, false } return &parsed, true } func validateChannelMonitorDateRange(startDate string, endDate string) error { if startDate != "" { if _, err := time.Parse("2006-01-02", startDate); err != nil { return errors.New("start_date 必须是 YYYY-MM-DD 格式") } } if endDate != "" { if _, err := time.Parse("2006-01-02", endDate); err != nil { return errors.New("end_date 必须是 YYYY-MM-DD 格式") } } return nil }