package handlers import ( "fmt" "net/http" "sort" "time" "ezops/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // BillingHandler 云账单处理器 type BillingHandler struct { db *gorm.DB } func NewBillingHandler(db *gorm.DB) *BillingHandler { return &BillingHandler{db: db} } // ListBillings 获取账单列表 func (h *BillingHandler) ListBillings(c *gin.Context) { var billings []models.CloudBilling query := h.db.Model(&models.CloudBilling{}) // 筛选 if provider := c.Query("provider"); provider != "" { query = query.Where("provider = ?", provider) } if accountName := c.Query("account_name"); accountName != "" { query = query.Where("account_name LIKE ?", "%"+accountName+"%") } if startDate := c.Query("start_date"); startDate != "" { query = query.Where("billing_date >= ?", startDate) } if endDate := c.Query("end_date"); endDate != "" { query = query.Where("billing_date <= ?", endDate) } // 排序 query = query.Order("billing_date DESC, id DESC") // 分页 page := 1 pageSize := 50 if c.Query("page") != "" { fmt.Sscanf(c.Query("page"), "%d", &page) } if c.Query("page_size") != "" { fmt.Sscanf(c.Query("page_size"), "%d", &pageSize) } if page < 1 { page = 1 } if pageSize < 1 || pageSize > 500 { pageSize = 50 } var total int64 query.Count(&total) query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&billings) c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "success", Data: models.ListResponse{ List: billings, Total: total, }, }) } // CreateBilling 创建账单记录 func (h *BillingHandler) CreateBilling(c *gin.Context) { var req models.CreateBillingRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } billing := models.CloudBilling{ CloudAccountID: req.CloudAccountID, AccountName: req.AccountName, Provider: req.Provider, BillingDate: req.BillingDate, Cost: req.Cost, Currency: req.Currency, Service: req.Service, Region: req.Region, Description: req.Description, } if billing.Currency == "" { billing.Currency = "CNY" } if err := h.db.Create(&billing).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "创建成功", Data: billing, }) } // BatchCreateBillings 批量创建账单 func (h *BillingHandler) BatchCreateBillings(c *gin.Context) { var req struct { Records []models.CreateBillingRequest `json:"records" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } created := 0 for _, r := range req.Records { billing := models.CloudBilling{ CloudAccountID: r.CloudAccountID, AccountName: r.AccountName, Provider: r.Provider, BillingDate: r.BillingDate, Cost: r.Cost, Currency: r.Currency, Service: r.Service, Region: r.Region, Description: r.Description, } if billing.Currency == "" { billing.Currency = "CNY" } if err := h.db.Create(&billing).Error; err == nil { created++ } } c.JSON(http.StatusOK, models.Response{ Code: 200, Message: fmt.Sprintf("成功导入 %d 条记录", created), Data: gin.H{"created": created, "total": len(req.Records)}, }) } // DeleteBilling 删除账单记录 func (h *BillingHandler) DeleteBilling(c *gin.Context) { id := c.Param("id") if err := h.db.Delete(&models.CloudBilling{}, id).Error; err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"}) } // GetBillingStats 获取账单统计 func (h *BillingHandler) GetBillingStats(c *gin.Context) { now := time.Now() today := now.Format("2006-01-02") yesterday := now.AddDate(0, 0, -1).Format("2006-01-02") monthStart := now.Format("2006-01") + "-01" yearStart := fmt.Sprintf("%d-01-01", now.Year()) stats := models.BillingStats{ MonthTrend: []models.DailyCost{}, ProviderDist: []models.ProviderCost{}, Alerts: []models.BillingAlert{}, } // 今日成本 h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", today). Select("COALESCE(SUM(cost), 0)").Scan(&stats.TodayCost) // 昨日成本 h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", yesterday). Select("COALESCE(SUM(cost), 0)").Scan(&stats.YesterdayCost) // 本月成本 h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", monthStart). Select("COALESCE(SUM(cost), 0)").Scan(&stats.MonthCost) // 本年成本 h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", yearStart). Select("COALESCE(SUM(cost), 0)").Scan(&stats.YearCost) // 近30天趋势 type dayCost struct { BillingDate string Total float64 } var trends []dayCost h.db.Model(&models.CloudBilling{}). Select("billing_date, SUM(cost) as total"). Where("billing_date >= ?", now.AddDate(0, 0, -29).Format("2006-01-02")). Group("billing_date"). Order("billing_date ASC"). Scan(&trends) for _, t := range trends { stats.MonthTrend = append(stats.MonthTrend, models.DailyCost{Date: t.BillingDate, Cost: t.Total}) } // 本月厂商成本分布 type provCost struct { Provider string Total float64 } var provCosts []provCost h.db.Model(&models.CloudBilling{}). Select("provider, SUM(cost) as total"). Where("billing_date >= ?", monthStart). Group("provider"). Scan(&provCosts) var totalProviderCost float64 for _, p := range provCosts { totalProviderCost += p.Total } for _, p := range provCosts { percent := 0.0 if totalProviderCost > 0 { percent = p.Total / totalProviderCost * 100 } stats.ProviderDist = append(stats.ProviderDist, models.ProviderCost{ Provider: p.Provider, Cost: p.Total, Percent: percent, }) } sort.Slice(stats.ProviderDist, func(i, j int) bool { return stats.ProviderDist[i].Cost > stats.ProviderDist[j].Cost }) // 检查预警规则 h.checkBillingAlerts(&stats, now) c.JSON(http.StatusOK, models.Response{ Code: 200, Message: "success", Data: stats, }) } // checkBillingAlerts 检查预警规则是否触发 func (h *BillingHandler) checkBillingAlerts(stats *models.BillingStats, now time.Time) { var rules []models.BillingAlertRule h.db.Where("enabled = ?", true).Find(&rules) for _, rule := range rules { var current float64 if rule.Metric == "daily" { // 查今日该厂商/账号的成本 q := h.db.Model(&models.CloudBilling{}).Where("billing_date = ?", now.Format("2006-01-02")) if rule.Provider != "" { q = q.Where("provider = ?", rule.Provider) } if rule.CloudAccountID != nil { q = q.Where("cloud_account_id = ?", *rule.CloudAccountID) } q.Select("COALESCE(SUM(cost), 0)").Scan(¤t) } else { // monthly - 本月累计 q := h.db.Model(&models.CloudBilling{}).Where("billing_date >= ?", now.Format("2006-01")+"-01") if rule.Provider != "" { q = q.Where("provider = ?", rule.Provider) } if rule.CloudAccountID != nil { q = q.Where("cloud_account_id = ?", *rule.CloudAccountID) } q.Select("COALESCE(SUM(cost), 0)").Scan(¤t) } // 检查阈值 triggered := false switch rule.Operator { case ">": triggered = current > rule.Threshold case ">=": triggered = current >= rule.Threshold case "<": triggered = current < rule.Threshold case "<=": triggered = current <= rule.Threshold } if triggered { providerLabel := rule.Provider if providerLabel == "" { providerLabel = "全部" } stats.Alerts = append(stats.Alerts, models.BillingAlert{ RuleID: rule.ID, RuleName: rule.Name, Provider: providerLabel, Current: current, Threshold: rule.Threshold, Metric: rule.Metric, Operator: rule.Operator, }) } } }