373 lines
9.4 KiB
Go
373 lines
9.4 KiB
Go
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
|
|
}
|