package api import ( "strconv" "time" "github.com/gin-gonic/gin" "github.com/openteam/server/internal/pkg/resp" "github.com/openteam/server/internal/store" ) // UsageSummary GET /api/v1/usage/summary — 今日/本月汇总。 func (h *Handler) UsageSummary(c *gin.Context) { u, _ := userFromContext(c) now := time.Now().UTC() today := now.Format("2006-01-02") month := now.Format("2006-01") var todayReq, monthReq int64 var todayTok, monthTok int64 var todayCost, monthCost float64 h.a.DB.Model(&store.UsageDaily{}).Where("user_id = ? AND date = ?", u.ID, today). Select("COALESCE(SUM(requests),0), COALESCE(SUM(input_tokens+output_tokens+cache_read_tokens),0), COALESCE(SUM(cost),0)"). Row().Scan(&todayReq, &todayTok, &todayCost) h.a.DB.Model(&store.UsageDaily{}).Where("user_id = ? AND date LIKE ?", u.ID, month+"%"). Select("COALESCE(SUM(requests),0), COALESCE(SUM(input_tokens+output_tokens+cache_read_tokens),0), COALESCE(SUM(cost),0)"). Row().Scan(&monthReq, &monthTok, &monthCost) resp.OK(c, gin.H{ "today": gin.H{"requests": todayReq, "tokens": todayTok, "cost": todayCost}, "month": gin.H{"requests": monthReq, "tokens": monthTok, "cost": monthCost}, }) } // UsageStats GET /api/v1/usage/stats?from&to&group=day|model func (h *Handler) UsageStats(c *gin.Context) { u, _ := userFromContext(c) from := c.DefaultQuery("from", time.Now().Add(-30*24*time.Hour).Format("2006-01-02")) to := c.DefaultQuery("to", time.Now().Format("2006-01-02")) group := c.DefaultQuery("group", "day") q := h.a.DB.Model(&store.UsageDaily{}).Where("user_id = ? AND date BETWEEN ? AND ?", u.ID, from, to) out := make([]gin.H, 0, 64) if group == "model" { var rows []struct { ModelID uint64 Requests int64 Tokens int64 Cost float64 } q.Select("model_id, SUM(requests) requests, SUM(input_tokens+output_tokens+cache_read_tokens) tokens, SUM(cost) cost"). Group("model_id").Scan(&rows) for _, r := range rows { var m store.Model name := strconv.FormatUint(r.ModelID, 10) if h.a.DB.First(&m, r.ModelID).Error == nil { name = m.Name } out = append(out, gin.H{"model": name, "requests": r.Requests, "tokens": r.Tokens, "cost": r.Cost}) } } else { var rows []struct { Date string Requests int64 Tokens int64 Cost float64 } q.Select("date, SUM(requests) requests, SUM(input_tokens+output_tokens+cache_read_tokens) tokens, SUM(cost) cost"). Group("date").Order("date").Scan(&rows) for _, r := range rows { out = append(out, gin.H{"date": r.Date, "requests": r.Requests, "tokens": r.Tokens, "cost": r.Cost}) } } resp.OK(c, gin.H{"items": out}) } // UsageLogs GET /api/v1/usage/logs?from&to&page&page_size&model func (h *Handler) UsageLogs(c *gin.Context) { u, _ := userFromContext(c) page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } q := h.a.DB.Model(&store.UsageLog{}).Where("user_id = ?", u.ID) if from := c.Query("from"); from != "" { q = q.Where("created_at >= ?", from+" 00:00:00") } if to := c.Query("to"); to != "" { q = q.Where("created_at <= ?", to+" 23:59:59") } if model := c.Query("model"); model != "" { q = q.Where("model_name = ?", model) } var total int64 q.Count(&total) var logs []store.UsageLog q.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&logs) out := make([]gin.H, 0, len(logs)) for _, l := range logs { out = append(out, gin.H{ "id": l.ID, "request_id": l.RequestID, "model": l.ModelName, "protocol": l.Protocol, "input_tokens": l.InputTokens, "output_tokens": l.OutputTokens, "cache_read_tokens": l.CacheReadTokens, "cost": l.Cost, "latency_ms": l.LatencyMS, "status": l.Status, "error_code": l.ErrorCode, "created_at": l.CreatedAt, }) } resp.OK(c, gin.H{"items": out, "total": total, "page": page, "page_size": size}) }