feat(gateway): GET /me/usage —— 用户自己租户的用量明细(余额+趋势+最近消耗)

面向用户口径(非 admin,受插件按请求 ctx 租户自动隔离):返回积分余额 +
credit_enforce + 按天消耗趋势 + 区间合计 + 最近 10 条消耗明细。复用
UsageTrend/TenantBalance,新增 RecentUsage(按 tenant 取近 N 条 usage_event)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-08 09:31:12 +08:00
parent 511ae199a4
commit f6ea03b34b
3 changed files with 59 additions and 0 deletions
@@ -550,6 +550,50 @@ func (h *Handler) TenantCurrent(c *gin.Context) {
})
}
// MyUsage: GET /api/v1/me/usage?days= —— 当前用户自己租户的用量明细(余额 + 按天趋势 + 最近消耗)。
// 面向用户口径(非 admin):只看自己租户,受插件按请求 ctx 租户自动隔离。
func (h *Handler) MyUsage(c *gin.Context) {
tid := tenantID(c)
if tid == "" {
c.JSON(http.StatusOK, gin.H{"tenant": nil})
return
}
ctx := c.Request.Context()
days := 30
if v := c.Query("days"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 90 {
days = n
}
}
now := time.Now()
from := now.AddDate(0, 0, -(days - 1)).Format("20060102")
to := now.Format("20060102")
trend := h.db.UsageTrend(ctx, tid, from, to)
var totTok, totCredits, totCost, totTasks int64
for _, d := range trend {
totTok += d.TotalTok
totCredits += d.CreditsMicro
totCost += d.CostMicros
totTasks += d.TaskCount
}
recent := make([]gin.H, 0, 10)
for _, u := range h.db.RecentUsage(ctx, tid, 10) {
recent = append(recent, gin.H{
"task_id": u.TaskID, "model": u.Model, "total_tok": u.TotalTok,
"credits_micro": u.CreditsMicro, "cost_micros": u.CostMicros, "currency": u.Currency, "ts": u.TS,
})
}
c.JSON(http.StatusOK, gin.H{
"from": from, "to": to,
"balance_micro": h.db.TenantBalance(ctx, tid),
"credit_enforce": h.db.CreditEnforceEnabled(ctx),
"trend": trend,
"totals": gin.H{"total_tok": totTok, "credits_micro": totCredits, "cost_micros": totCost, "task_count": totTasks},
"recent": recent,
})
}
// sessionID 从请求取会话标识(真实场景应由会话中间件注入)。
func sessionID(c *gin.Context) string {
if s := c.GetHeader("X-Session-ID"); s != "" {