feat(gateway): SaaS P2 计量·增量3 —— GET /admin/usage 用量/积分/成本口径

系统级(store.WithoutTenant,跨租户)用量观测:
- 无 tenant:全平台按天 SUM 趋势 + 各租户用量排行(含当前余额)。
- 有 tenant:该租户按天趋势 + 当前积分余额。
- from/to(YYYYMMDD)缺省近 30 天。UsageTrend/UsageByTenant/TenantBalance store 方法。

live 验证:两口径数值自洽(balance=seed−credits、credits=tok×汇率、totals=trend之和)。
至此 P2 计量后端(事实源)齐:明细/账本/余额/rollup/查询。前端页 + P4 硬拦截/充值待需求拉动。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-07 11:42:21 +08:00
parent d058a63434
commit bf75bb8a62
4 changed files with 108 additions and 1 deletions
@@ -365,3 +365,39 @@ func mask(s string) string {
}
return "••••" + s[len(s)-4:]
}
// AdminUsage: GET /api/v1/admin/usage?tenant=&from=&to= —— 系统级用量/积分/成本口径。
// 无 tenant → 全平台按天 SUM + 各租户排行;有 tenant → 该租户按天趋势 + 当前余额。
// from/to 为 YYYYMMDD,缺省近 30 天。走 WithoutTenant(跨租户,与 admin/overview 同口径)。
func (h *Handler) AdminUsage(c *gin.Context) {
ctx := store.WithoutTenant(c.Request.Context())
now := time.Now()
from := c.Query("from")
if from == "" {
from = now.AddDate(0, 0, -29).Format("20060102")
}
to := c.Query("to")
if to == "" {
to = now.Format("20060102")
}
tenant := c.Query("tenant")
trend := h.db.UsageTrend(ctx, tenant, from, to)
var totTok, totCredits, totCost, totTasks int64
for _, d := range trend {
totTok += d.TotalTok
totCredits += d.CreditsMicro
totCost += d.CostMicros
totTasks += d.TaskCount
}
resp := gin.H{
"from": from, "to": to, "tenant": tenant, "trend": trend,
"totals": gin.H{"total_tok": totTok, "credits_micro": totCredits, "cost_micros": totCost, "task_count": totTasks},
}
if tenant != "" {
resp["balance_micro"] = h.db.TenantBalance(ctx, tenant)
} else {
resp["tenants"] = h.db.UsageByTenant(ctx, from, to, 20) // 全平台:各租户用量排行(含余额)
}
c.JSON(http.StatusOK, resp)
}