管理员需要一个面板查看不同 AI 模型的用户使用情况,支持日期范围筛选、金额汇总和成功笔数统计。
涉及表:consumptions、ai_models、user_meta,无需其他表。
GET /api/admin/model-usage
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
start_date | YYYY-MM-DD | 14天前 | 开始日期 |
end_date | YYYY-MM-DD | 今天 | 结束日期(最大跨度90天) |
model_key | string | — | 按模型筛选,支持逗号分隔多选(如 seedream_v4,seedance_pro) |
category | string | — | 按模型分类筛选:text-chat / image-generator / video-generator |
user_keyword | string | — | 搜索用户(模糊匹配 email / user_id) |
source | string | — | 调用来源:page(页面)/ api(API Key) |
page | int | 1 | 页码 |
pageSize | int | 20 | 每页条数(最大200) |
sortBy | string | total_amount | 排序字段:total_amount / total_calls / success_calls / stat_date |
sortOrder | ASC/DESC | DESC | 排序方向 |
所有筛选条件可自由组合。
| 场景 | 参数 |
|---|---|
| 全局概览 | 无额外参数(默认最近14天) |
| 只看文本模型 | category=text-chat |
| 查字节模型使用情况 | model_key=seedream_v4,seedream_v4_5,seedance_pro |
| 搜某个用户 | user_keyword=test@email.com |
| 搜用户 + 指定模型 | user_keyword=test@email.com&model_key=seedream_v4 |
| 只看 API 调用 | source=api |
| 按日期正序排列 | sortBy=stat_date&sortOrder=ASC |
{
"data": {
"date_range": { "start_date": "2026-03-18", "end_date": "2026-03-31" },
"totals": {
"total_calls": 120,
"success_calls": 118,
"total_amount": 15.5,
"total_credits": 1550
},
"items": [
{
"user_id": "user_abc",
"email": "test@example.com",
"stat_date": "2026-03-24",
"total_calls": 4,
"success_calls": 4,
"total_amount": 1.2,
"total_credits": 120
}
],
"daily_stats": [
{ "date": "2026-03-23", "total_calls": 20, "success_calls": 19, "amount": 2.5, "credits": 250 },
{ "date": "2026-03-24", "total_calls": 35, "success_calls": 35, "amount": 4.8, "credits": 480 }
],
"pageInfo": { "page": 1, "pageSize": 20, "total": 32 }
}
}
字段说明:
totals — 当前筛选条件下的汇总items — 按用户+日期聚合的明细列表(分页),每行 = 某用户某天的使用统计daily_stats — 每日趋势(用于图表,自动填充空日期为零值)pageInfo — items 的分页信息packages/server/src/
pages/api/admin/
model-usage/
index.ts # 路由入口
services/admin/
model-usage.ts # 服务层
模型信息统一存储在 consumptions.input_meta JSON 的 $.model 字段中:
JSON_UNQUOTE(JSON_EXTRACT(c.input_meta, '$.model'))
WHERE c.deleted_at IS NULL
AND c.created_at >= :start_date
AND c.created_at < DATE_ADD(:end_date, INTERVAL 1 DAY)
-- 按需追加:
AND JSON_UNQUOTE(JSON_EXTRACT(c.input_meta, '$.model')) IN (:model_keys) -- 多选
AND c.api_key_id IS [NOT] NULL -- source
AND (c.user_id LIKE :user_keyword OR um.email LIKE :user_keyword) -- 用户搜索
当传了 category 时,额外 JOIN ai_models 并加 AND am.category = :category。
SELECT
COUNT(*) AS total_calls,
SUM(CASE WHEN c.complete_at IS NOT NULL THEN 1 ELSE 0 END) AS success_calls,
COALESCE(SUM(c.task_used), 0) AS total_credits_db
FROM consumptions c
{JOINS}
{WHERE}
SELECT
c.user_id, um.email,
DATE(c.created_at) AS stat_date,
COUNT(*) AS total_calls,
SUM(CASE WHEN c.complete_at IS NOT NULL THEN 1 ELSE 0 END) AS success_calls,
COALESCE(SUM(c.task_used), 0) AS total_credits_db
FROM consumptions c
LEFT JOIN user_meta um ON um.user_id = c.user_id
{WHERE}
GROUP BY c.user_id, um.email, stat_date
ORDER BY :sortExpr :sortOrder
LIMIT :limit OFFSET :offset
SELECT DATE(c.created_at) AS date,
COUNT(*) AS total_calls,
SUM(CASE WHEN c.complete_at IS NOT NULL THEN 1 ELSE 0 END) AS success_calls,
COALESCE(SUM(c.task_used), 0) AS total_credits_db
FROM consumptions c
{JOINS}
{WHERE}
GROUP BY date
ORDER BY date ASC
应用层用 dayjs 循环填充空日期为零值。
三个查询并行执行(Promise.all)。
| 工具 | 导入路径 |
|---|---|
requireAdmin | #server/services/admin/auth |
createOK / ServerError | #utils |
creditsToAmount / creditsToShowCredits | #utils/credits |
dayjs | dayjs |
QueryTypes.SELECT | sequelize |
useSequelize | #utils |
| 决策 | 说明 |
|---|---|
| items 按用户+日期聚合 | 每行 = 某用户某天的使用统计,与业务需求一致 |
model 字段取 $.model | input_meta.model 是所有任务类型统一的模型标识字段 |
| model_key 支持多选 | 逗号分隔,SQL 用 IN (:model_keys) |
| 成功笔数 | complete_at IS NOT NULL 即为成功 |
| 三查询并行 | totals + items + daily_stats 用 Promise.all |
| 积分转换在应用层 | 与现有模式一致 |
| 排序白名单 | 防 SQL 注入 |
| 日期填充在应用层 | dayjs 循环补零 |