Merge pull request #960 from 0xObjc/codex/user-spending-ranking

feat(admin): add user spending ranking dashboard view
This commit is contained in:
Wesley Liddick
2026-03-13 23:06:30 +08:00
committed by GitHub
17 changed files with 591 additions and 39 deletions

View File

@@ -1993,6 +1993,10 @@ type ModelStat = usagestats.ModelStat
// UserUsageTrendPoint represents user usage trend data point
type UserUsageTrendPoint = usagestats.UserUsageTrendPoint
// UserSpendingRankingItem represents a user spending ranking row.
type UserSpendingRankingItem = usagestats.UserSpendingRankingItem
type UserSpendingRankingResponse = usagestats.UserSpendingRankingResponse
// APIKeyUsageTrendPoint represents API key usage trend data point
type APIKeyUsageTrendPoint = usagestats.APIKeyUsageTrendPoint
@@ -2109,6 +2113,78 @@ func (r *usageLogRepository) GetUserUsageTrend(ctx context.Context, startTime, e
return results, nil
}
// GetUserSpendingRanking returns user spending ranking aggregated within the time range.
func (r *usageLogRepository) GetUserSpendingRanking(ctx context.Context, startTime, endTime time.Time, limit int) (result *UserSpendingRankingResponse, err error) {
if limit <= 0 {
limit = 12
}
query := `
WITH user_spend AS (
SELECT
u.user_id,
COALESCE(us.email, '') as email,
COALESCE(SUM(u.actual_cost), 0) as actual_cost,
COUNT(*) as requests,
COALESCE(SUM(u.input_tokens + u.output_tokens + u.cache_creation_tokens + u.cache_read_tokens), 0) as tokens
FROM usage_logs u
LEFT JOIN users us ON u.user_id = us.id
WHERE u.created_at >= $1 AND u.created_at < $2
GROUP BY u.user_id, us.email
),
ranked AS (
SELECT
user_id,
email,
actual_cost,
requests,
tokens,
COALESCE(SUM(actual_cost) OVER (), 0) as total_actual_cost
FROM user_spend
ORDER BY actual_cost DESC, tokens DESC, user_id ASC
LIMIT $3
)
SELECT
user_id,
email,
actual_cost,
requests,
tokens,
total_actual_cost
FROM ranked
ORDER BY actual_cost DESC, tokens DESC, user_id ASC
`
rows, err := r.sql.QueryContext(ctx, query, startTime, endTime, limit)
if err != nil {
return nil, err
}
defer func() {
if closeErr := rows.Close(); closeErr != nil && err == nil {
err = closeErr
result = nil
}
}()
ranking := make([]UserSpendingRankingItem, 0)
totalActualCost := 0.0
for rows.Next() {
var row UserSpendingRankingItem
if err = rows.Scan(&row.UserID, &row.Email, &row.ActualCost, &row.Requests, &row.Tokens, &totalActualCost); err != nil {
return nil, err
}
ranking = append(ranking, row)
}
if err = rows.Err(); err != nil {
return nil, err
}
return &UserSpendingRankingResponse{
Ranking: ranking,
TotalActualCost: totalActualCost,
}, nil
}
// UserDashboardStats 用户仪表盘统计
type UserDashboardStats = usagestats.UserDashboardStats