diff --git a/backend/internal/api/handlers/billing.go b/backend/internal/api/handlers/billing.go index 610bb1a..a179a8f 100644 --- a/backend/internal/api/handlers/billing.go +++ b/backend/internal/api/handlers/billing.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "sort" - "strings" "time" "ezops/internal/models" @@ -306,171 +305,3 @@ func (h *BillingHandler) checkBillingAlerts(stats *models.BillingStats, now time } } } - -// ListAlertRules 获取预警规则列表 -func (h *BillingHandler) ListAlertRules(c *gin.Context) { - var rules []models.BillingAlertRule - h.db.Order("created_at DESC").Find(&rules) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: models.ListResponse{ - List: rules, - Total: int64(len(rules)), - }, - }) -} - -// CreateAlertRule 创建预警规则 -func (h *BillingHandler) CreateAlertRule(c *gin.Context) { - var req models.CreateBillingAlertRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) - return - } - - rule := models.BillingAlertRule{ - Name: req.Name, - Provider: req.Provider, - CloudAccountID: req.CloudAccountID, - Metric: req.Metric, - Threshold: req.Threshold, - Operator: req.Operator, - Enabled: req.Enabled, - } - if rule.Metric == "" { - rule.Metric = "daily" - } - if rule.Operator == "" { - rule.Operator = ">" - } - - if err := h.db.Create(&rule).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败"}) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "创建成功", - Data: rule, - }) -} - -// UpdateAlertRule 更新预警规则 -func (h *BillingHandler) UpdateAlertRule(c *gin.Context) { - id := c.Param("id") - var rule models.BillingAlertRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) - return - } - - var req models.CreateBillingAlertRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) - return - } - - rule.Name = req.Name - rule.Provider = req.Provider - rule.CloudAccountID = req.CloudAccountID - rule.Metric = req.Metric - rule.Threshold = req.Threshold - rule.Operator = req.Operator - rule.Enabled = req.Enabled - if rule.Metric == "" { - rule.Metric = "daily" - } - if rule.Operator == "" { - rule.Operator = ">" - } - - h.db.Save(&rule) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "更新成功", - Data: rule, - }) -} - -// DeleteAlertRule 删除预警规则 -func (h *BillingHandler) DeleteAlertRule(c *gin.Context) { - id := c.Param("id") - if err := h.db.Delete(&models.BillingAlertRule{}, id).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"}) - return - } - c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"}) -} - -// ToggleAlertRule 启用/禁用预警规则 -func (h *BillingHandler) ToggleAlertRule(c *gin.Context) { - id := c.Param("id") - var rule models.BillingAlertRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) - return - } - - rule.Enabled = !rule.Enabled - h.db.Save(&rule) - - status := "已启用" - if !rule.Enabled { - status = "已禁用" - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: status, - Data: rule, - }) -} - -// GetProviderOptions 获取厂商选项(用于筛选下拉) -func (h *BillingHandler) GetProviderOptions(c *gin.Context) { - var providers []string - h.db.Model(&models.CloudBilling{}).Distinct("provider").Where("provider != ''").Pluck("provider", &providers) - - result := make([]gin.H, 0, len(providers)) - for _, p := range providers { - label := p - switch p { - case "aliyun": - label = "阿里云" - case "tencent": - label = "腾讯云" - case "aws": - label = "AWS" - case "huawei": - label = "华为云" - case "azure": - label = "Azure" - } - result = append(result, gin.H{"value": p, "label": label}) - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: result, - }) -} - -// formatProvider 格式化厂商名称 -func formatProvider(provider string) string { - names := map[string]string{ - "aliyun": "阿里云", - "tencent": "腾讯云", - "aws": "AWS", - "huawei": "华为云", - "azure": "Azure", - } - if name, ok := names[strings.ToLower(provider)]; ok { - return name - } - return provider -} diff --git a/backend/internal/api/handlers/billing_alert.go b/backend/internal/api/handlers/billing_alert.go new file mode 100644 index 0000000..7493972 --- /dev/null +++ b/backend/internal/api/handlers/billing_alert.go @@ -0,0 +1,168 @@ +package handlers + +import ( + "net/http" + "strings" + + "ezops/internal/models" + + "github.com/gin-gonic/gin" +) + +// 本文件从 billing.go 拆分而来,承载账单预警规则域 CRUD // Logan.Li +// 与 BillingHandler 共享 receiver,同 package 无需修改 routes.go + +// ListAlertRules 获取预警规则列表 // Logan.Li +func (h *BillingHandler) ListAlertRules(c *gin.Context) { + var rules []models.BillingAlertRule + h.db.Order("created_at DESC").Find(&rules) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "success", + Data: models.ListResponse{ + List: rules, + Total: int64(len(rules)), + }, + }) +} + +// CreateAlertRule 创建预警规则 // Logan.Li +func (h *BillingHandler) CreateAlertRule(c *gin.Context) { + var req models.CreateBillingAlertRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) + return + } + + rule := models.BillingAlertRule{ + Name: req.Name, + Provider: req.Provider, + CloudAccountID: req.CloudAccountID, + Metric: req.Metric, + Threshold: req.Threshold, + Operator: req.Operator, + Enabled: req.Enabled, + } + if rule.Metric == "" { + rule.Metric = "daily" + } + if rule.Operator == "" { + rule.Operator = ">" + } + + if err := h.db.Create(&rule).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败"}) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "创建成功", + Data: rule, + }) +} + +// UpdateAlertRule 更新预警规则 // Logan.Li +func (h *BillingHandler) UpdateAlertRule(c *gin.Context) { + id := c.Param("id") + var rule models.BillingAlertRule + if err := h.db.First(&rule, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) + return + } + + var req models.CreateBillingAlertRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) + return + } + + rule.Name = req.Name + rule.Provider = req.Provider + rule.CloudAccountID = req.CloudAccountID + rule.Metric = req.Metric + rule.Threshold = req.Threshold + rule.Operator = req.Operator + rule.Enabled = req.Enabled + if rule.Metric == "" { + rule.Metric = "daily" + } + if rule.Operator == "" { + rule.Operator = ">" + } + + h.db.Save(&rule) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "更新成功", + Data: rule, + }) +} + +// DeleteAlertRule 删除预警规则 // Logan.Li +func (h *BillingHandler) DeleteAlertRule(c *gin.Context) { + id := c.Param("id") + if err := h.db.Delete(&models.BillingAlertRule{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"}) + return + } + c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"}) +} + +// ToggleAlertRule 启用/禁用预警规则(服务端自动翻转) // Logan.Li +func (h *BillingHandler) ToggleAlertRule(c *gin.Context) { + id := c.Param("id") + var rule models.BillingAlertRule + if err := h.db.First(&rule, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "规则不存在"}) + return + } + + rule.Enabled = !rule.Enabled + h.db.Save(&rule) + + status := "已启用" + if !rule.Enabled { + status = "已禁用" + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: status, + Data: rule, + }) +} + +// GetProviderOptions 获取厂商选项(用于筛选下拉) // Logan.Li +func (h *BillingHandler) GetProviderOptions(c *gin.Context) { + var providers []string + h.db.Model(&models.CloudBilling{}).Distinct("provider").Where("provider != ''").Pluck("provider", &providers) + + result := make([]gin.H, 0, len(providers)) + for _, p := range providers { + result = append(result, gin.H{"value": p, "label": formatProvider(p)}) + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "success", + Data: result, + }) +} + +// formatProvider 格式化厂商名称 // Logan.Li +func formatProvider(provider string) string { + names := map[string]string{ + "aliyun": "阿里云", + "tencent": "腾讯云", + "aws": "AWS", + "huawei": "华为云", + "azure": "Azure", + } + if name, ok := names[strings.ToLower(provider)]; ok { + return name + } + return provider +}