|
@@ -0,0 +1,180 @@
|
|
|
|
|
+# 积分消耗与 Token 用量统计系统设计
|
|
|
|
|
+
|
|
|
|
|
+日期:2026-08-19
|
|
|
|
|
+状态:待审阅
|
|
|
|
|
+
|
|
|
|
|
+## 背景与目标
|
|
|
|
|
+
|
|
|
|
|
+需要基于 `mp_user_points_details` 明细表建立一套使用量统计系统,支持按**日期、用户、模型、中转站**四个维度查看数据,供平台管理员(superadmin)查看全部用户、组织管理员(admin)查看本公司(cpid)用户的统计数据。
|
|
|
|
|
+
|
|
|
|
|
+核心诉求:
|
|
|
|
|
+
|
|
|
|
|
+- 定时任务预聚合,前端查询走统计表,保证查询性能稳定。
|
|
|
|
|
+- 以 **token 消耗**为核心统计指标,积分消耗作为附带指标。
|
|
|
|
|
+- 初始化"模型名 → 中转站"映射表,便于后续人工维护。
|
|
|
|
|
+- 提供前端可用的查询 API,支持日期、用户、模型、中转站筛选。
|
|
|
|
|
+
|
|
|
|
|
+## 相关背景修复
|
|
|
|
|
+
|
|
|
|
|
+审查中发现并修复了视频积分明细 token 恒为 0 的 bug:
|
|
|
|
|
+
|
|
|
|
|
+- 根因:`AIVideoGenerationService::markVideoTaskSuccess()` 更新数据库后未同步内存中 `$task->result_json`,导致随后扣费解析 token 时读到旧空值。
|
|
|
|
|
+- 已修复 `markVideoTaskSuccess` 同步内存属性,并新增 `points:backfill-video-tokens` 命令回填历史数据(已执行完毕)。
|
|
|
|
|
+- 同时补齐了 `analyzeErrorMessage`(错误友好翻译)与 `geminiGenerateText`(潜在未接线路径)两处缺失的 token 记录。
|
|
|
|
|
+
|
|
|
|
|
+## 数据库设计
|
|
|
|
|
+
|
|
|
|
|
+### 1. 模型→中转站映射表 `mp_model_relay_map`
|
|
|
|
|
+
|
|
|
|
|
+用途:维护模型名与中转站的对应关系,统计任务按模型名关联出中转站。
|
|
|
|
|
+
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| id | bigint unsigned PK AI | |
|
|
|
|
|
+| model | varchar(255) NOT NULL UNIQUE | 模型名 |
|
|
|
|
|
+| relay_name | varchar(100) NOT NULL | 中转站名称 |
|
|
|
|
|
+| remark | varchar(255) NULL | 备注 |
|
|
|
|
|
+| created_at / updated_at | timestamp | |
|
|
|
|
|
+
|
|
|
|
|
+migration 中初始化以下映射(智能分析所得,后续可人工增改):
|
|
|
|
|
+
|
|
|
|
|
+| 模型 | 中转站 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| deepseek-chat / deepseek-reasoner / deepseek-v4-flash / deepseek-v4-pro | DeepSeek 官方 |
|
|
|
|
|
+| deepseek-v3-2-251201 | 火山方舟 |
|
|
|
|
|
+| doubao-seed-2-0-mini/lite/pro-260215 | 火山方舟 |
|
|
|
|
|
+| doubao-seedream-4-5-251128 / doubao-seedream-5-0-260128 / doubao-seedream-5-0-lite-260128 | 火山方舟 |
|
|
|
|
|
+| doubao-seedance-1-5-pro-251215 / doubao-seedance-2-0-260128 / doubao-seedance-2-0-fast-260128 | 火山方舟 |
|
|
|
|
|
+| doubao-seedance-2.0 | 百度 |
|
|
|
|
|
+| zhizhen-20 / zhizhen-20-fast / zhizhen-20-mini | 智帧 |
|
|
|
|
|
+| gpt-5.4 / gpt-5.6-terra / gpt-5.6-sol / gpt-5.6-luna / gpt-image-2 / GptImage2 | GPT 中转站 |
|
|
|
|
|
+| gemini-3-pro-preview / gemini-3-flash-preview / gemini-3.1-pro-preview / gemini-3.1-flash-image-preview-t / gemini-3.6-flash | Gemini 中转站 |
|
|
|
|
|
+| NanoBanana2 / NanoBananaPro | NanoBanana |
|
|
|
|
|
+
|
|
|
|
|
+未命中映射的模型在统计中归为 `unknown`。
|
|
|
|
|
+
|
|
|
|
|
+### 2. 统计表 `mp_points_daily_stats`
|
|
|
|
|
+
|
|
|
|
|
+用途:按 `(stat_date, uid, model, relay)` 维度预聚合的日粒度统计。
|
|
|
|
|
+
|
|
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| id | bigint unsigned PK AI | |
|
|
|
|
|
+| stat_date | date NOT NULL | 统计日期(按明细 created_at 归属) |
|
|
|
|
|
+| uid | bigint unsigned NOT NULL | 用户 ID |
|
|
|
|
|
+| cpid | bigint NOT NULL DEFAULT 0 | 用户所属组织(组织管理员过滤用) |
|
|
|
|
|
+| model | varchar(255) NOT NULL | 模型名 |
|
|
|
|
|
+| relay | varchar(100) NOT NULL DEFAULT 'unknown' | 中转站名称 |
|
|
|
|
|
+| call_count | int unsigned NOT NULL DEFAULT 0 | 调用次数 |
|
|
|
|
|
+| tokens_consumed | bigint NOT NULL DEFAULT 0 | token 消耗合计(核心指标) |
|
|
|
|
|
+| points_consumed | decimal(13,1) NOT NULL DEFAULT 0 | 积分消耗合计(附带指标) |
|
|
|
|
|
+| created_at / updated_at | timestamp | |
|
|
|
|
|
+
|
|
|
|
|
+索引:
|
|
|
|
|
+
|
|
|
|
|
+- 唯一键 `uk_date_uid_model_relay (stat_date, uid, model, relay)`
|
|
|
|
|
+- 索引 `idx_stat_date_cpid (stat_date, cpid)`
|
|
|
|
|
+- 索引 `idx_relay (relay)`
|
|
|
|
|
+- 索引 `idx_model (model)`
|
|
|
|
|
+
|
|
|
|
|
+## 定时任务
|
|
|
|
|
+
|
|
|
|
|
+### 命令 `stats:points-daily`
|
|
|
|
|
+
|
|
|
|
|
+参数:
|
|
|
|
|
+
|
|
|
|
|
+- `--date=YYYY-MM-DD`:只统计指定日期(默认昨天)
|
|
|
|
|
+- `--from=YYYY-MM-DD --to=YYYY-MM-DD`:批量补跑日期区间
|
|
|
|
|
+
|
|
|
|
|
+统计口径:
|
|
|
|
|
+
|
|
|
|
|
+- 数据源:`mp_user_points_details`,按 `created_at` 归属到日期。
|
|
|
|
|
+- 只统计 `type IN (chat, image, video)`,排除 `system/company`(发放/回收,无模型、无 token)。
|
|
|
|
|
+- **不过滤 `test_mode`**,测试用户记录全部纳入(按用户确认)。
|
|
|
|
|
+- 模型:优先取 `charge_info->model`,为空回退 `api_type`,再为空归 `unknown`。
|
|
|
|
|
+- 中转站:按模型名 LEFT JOIN `mp_model_relay_map` 关联,未命中归 `unknown`。
|
|
|
|
|
+- 指标:`call_count = COUNT(*)`,`tokens_consumed = SUM(tokens_consumed)`,`points_consumed = SUM(points_consumed)`。
|
|
|
|
|
+
|
|
|
|
|
+写入方式:按日期在事务内先删除该日旧数据再批量插入(幂等,可重复执行)。
|
|
|
|
|
+
|
|
|
|
|
+调度:`Kernel::schedule` 中每天 00:10 执行 `stats:points-daily`,`withoutOverlapping()` 防止并发。
|
|
|
|
|
+
|
|
|
|
|
+## API 设计
|
|
|
|
|
+
|
|
|
|
|
+### 1. 查询统计 `GET /api/points/stats`
|
|
|
|
|
+
|
|
|
|
|
+权限:
|
|
|
|
|
+
|
|
|
|
|
+- `superadmin`:查询全部用户数据。
|
|
|
|
|
+- `admin`:自动限定 `cpid` 为当前登录用户所属组织。
|
|
|
|
|
+- 其他角色:返回无权限错误。
|
|
|
|
|
+
|
|
|
|
|
+请求参数:
|
|
|
|
|
+
|
|
|
|
|
+| 参数 | 必填 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| start_date / end_date | 否 | 日期范围,默认近 30 天 |
|
|
|
|
|
+| uid | 否 | 用户 ID,支持逗号分隔多个 |
|
|
|
|
|
+| model | 否 | 模型名 |
|
|
|
|
|
+| relay | 否 | 中转站名称 |
|
|
|
|
|
+| group_by | 否 | 聚合维度,`date/uid/model/relay` 任意组合(如 `date,model`),默认四维全部分组 |
|
|
|
|
|
+| page / page_size | 否 | 分页,默认 15,最大 100 |
|
|
|
|
|
+
|
|
|
|
|
+响应(沿用现有 ApiResponse 风格):
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "code": 0,
|
|
|
|
|
+ "data": {
|
|
|
|
|
+ "list": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "stat_date": "2026-08-18",
|
|
|
|
|
+ "uid": 142858,
|
|
|
|
|
+ "nickname": "用户昵称",
|
|
|
|
|
+ "model": "zhizhen-20",
|
|
|
|
|
+ "relay": "智帧",
|
|
|
|
|
+ "call_count": 10,
|
|
|
|
|
+ "tokens_consumed": 1309900,
|
|
|
|
|
+ "points_consumed": 840.0
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ "meta": {
|
|
|
|
|
+ "current_page": 1,
|
|
|
|
|
+ "last_page": 10,
|
|
|
|
|
+ "per_page": 15,
|
|
|
|
|
+ "total": 145
|
|
|
|
|
+ },
|
|
|
|
|
+ "summary": {
|
|
|
|
|
+ "call_count": 1000,
|
|
|
|
|
+ "tokens_consumed": 100000000,
|
|
|
|
|
+ "points_consumed": 80000.0
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2. 筛选项来源 `GET /api/points/stats/filters`
|
|
|
|
|
+
|
|
|
|
|
+返回当前权限范围内的可选值,供前端下拉框使用:
|
|
|
|
|
+
|
|
|
|
|
+- 用户列表(id + 昵称/账号)
|
|
|
|
|
+- 模型列表
|
|
|
|
|
+- 中转站列表
|
|
|
|
|
+- 可统计日期范围(最早/最晚 stat_date)
|
|
|
|
|
+
|
|
|
|
|
+## 目录与文件规划
|
|
|
|
|
+
|
|
|
|
|
+- `database/migrations/xxxx_create_mp_model_relay_map_table.php`
|
|
|
|
|
+- `database/migrations/xxxx_create_mp_points_daily_stats_table.php`
|
|
|
|
|
+- `app/Console/Commands/PointsDailyStatsCommand.php`
|
|
|
|
|
+- `app/Services/PointsStatsService.php`
|
|
|
|
|
+- `app/Http/Controllers/Points/PointsStatsController.php`
|
|
|
|
|
+- `app/Transformer/Points/PointsStatsTransformer.php`(如有需要)
|
|
|
|
|
+- `routes/api.php` 新增路由
|
|
|
|
|
+
|
|
|
|
|
+## 测试计划
|
|
|
|
|
+
|
|
|
|
|
+- 定时任务:手动执行 `--date` 统计结果与明细表 SQL 对账一致。
|
|
|
|
|
+- 权限:superadmin 可见全部、admin 仅本组织、其他角色拒绝。
|
|
|
|
|
+- 筛选:日期、用户、模型、中转站、group_by 组合。
|
|
|
|
|
+- 幂等:重复执行同一日期统计不产生脏数据。
|