diff --git a/docs/api-reference.md b/docs/api-reference.md index 4dfb9e1..8abadf9 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -1,6 +1,8 @@ # Duoqi API Reference > 多奇服务端 API 接口文档。本文按当前 Fastify 路由和 TypeScript DTO 更新。 +> +> 最近一次代码审计:2026-05-18,来源为 `src/index.ts` 注册的路由、`src/routes/**/*.ts`、`src/types/app-api.ts` 和相关 service 返回值。 ## Base URL @@ -79,7 +81,7 @@ #### POST /auth/guest -认证:无 +认证:无 限流:10 次/分钟 请求: @@ -113,7 +115,7 @@ #### POST /auth/huawei -认证:无 +认证:无 限流:10 次/分钟 请求: @@ -128,7 +130,7 @@ #### POST /auth/refresh -认证:无 +认证:无 限流:10 次/分钟 请求: @@ -152,6 +154,24 @@ } ``` +#### POST /auth/phone + +认证:无 +限流:10 次/分钟 + +当前已注册但未实现,固定返回 HTTP 501: + +```json +{ + "success": false, + "data": null, + "error": { + "code": "NOT_IMPLEMENTED", + "message": "Phone login not implemented yet" + } +} +``` + #### GET /auth/me 认证:JWT @@ -204,6 +224,8 @@ "dailyAttemptsLeft": 5, "dailyAttemptsMax": 5, "nextAttemptResetAt": "2026-05-06T00:00:00.000Z", + "highRewardSessionsLeft": 3, + "highRewardSessionsMax": 3, "xp": 0, "level": 1, "xpToNextLevel": 400, @@ -290,7 +312,7 @@ #### GET /tracks/:trackId -认证:JWT +认证:JWT 路径参数:`trackId` 可以是分类 `id` 或 `slug`。 响应:单个 `ThemeTrackDto`,不存在时 `data` 为 `null`。 @@ -315,6 +337,7 @@ "trackId": "history", "nodeId": "chapter-uuid", "totalQuestions": 5, + "highRewardEligible": true, "questions": [ { "challengeId": "challenge-session-uuid", @@ -336,7 +359,7 @@ } ``` -服务端会创建挑战组会话并一次返回 5 题,题目选项不包含正确答案标记。题库不足 5 题或没有可用题目时 `data` 为 `null`。 +服务端会创建挑战组会话并一次返回 5 题,题目选项不包含正确答案标记。`highRewardEligible=false` 表示今日高奖励挑战次数已用尽,本轮 XP 和宝箱掉落按降级规则结算。题库不足 5 题或没有可用题目时 `data` 为 `null`。 #### POST /challenges/answer @@ -370,6 +393,8 @@ "progress": { "hearts": 5, "dailyAttemptsLeft": 5, + "highRewardSessionsLeft": 2, + "highRewardSessionsMax": 3, "xp": 10, "streakDays": 0 }, @@ -393,7 +418,7 @@ 认证:JWT -响应:`ProgressSummaryDto`,字段同 `/app/bootstrap` 中的 `progress`。 +响应:`ProgressSummaryDto`,字段同 `/app/bootstrap` 中的 `progress`,包含 `highRewardSessionsLeft` 和 `highRewardSessionsMax`。 #### PATCH /progress/preferences @@ -411,7 +436,7 @@ #### POST /progress/check-in -认证:JWT +认证:JWT 请求:无 响应:更新后的 `ProgressSummaryDto`。 @@ -691,7 +716,7 @@ } ``` -`type` 取值:`hearts`, `bonusAttempts`, `streakProtection`。 +`type` 取值:`hearts`, `bonusAttempts`, `streakProtection`。 `platform` 取值:`ios`, `android`, `harmony`, `web`。 符合资格响应: @@ -783,8 +808,17 @@ Plus 用户响应(无需看广告,返回订阅权益摘要): "maxHearts": 5, "dailyAttemptsLeft": 2, "dailyAttemptsMax": 5, + "nextAttemptResetAt": "2026-05-06T00:00:00.000Z", + "highRewardSessionsLeft": 3, + "highRewardSessionsMax": 3, + "xp": 1200, + "level": 4, + "xpToNextLevel": 200, "streakDays": 21, - "streakProtectedUntil": null + "checkInDays": 8, + "streakProtectedUntil": null, + "activeTrackId": "history", + "isSubscribed": false }, "limits": { "remainingHeartsRecoveriesToday": 2, @@ -809,7 +843,18 @@ Plus 用户响应(无需看广告,返回订阅权益摘要): "hearts": 2, "maxHearts": 5, "dailyAttemptsLeft": 1, - "dailyAttemptsMax": 5 + "dailyAttemptsMax": 5, + "nextAttemptResetAt": "2026-05-06T00:00:00.000Z", + "highRewardSessionsLeft": 0, + "highRewardSessionsMax": 3, + "xp": 1200, + "level": 4, + "xpToNextLevel": 200, + "streakDays": 21, + "checkInDays": 8, + "streakProtectedUntil": null, + "activeTrackId": "history", + "isSubscribed": false } }, "error": null @@ -882,12 +927,38 @@ Plus 用户响应(无需看广告,返回订阅权益摘要): } ``` +#### POST /admin/auth + +认证:无 + +兼容旧管理端的 `ADMIN_TOKEN` 验证接口。新管理端应使用 `/admin/login` 获取 Admin JWT。 + +请求: + +```json +{ + "token": "admin-token" +} +``` + +响应: + +```json +{ + "success": true, + "data": { + "authenticated": true + }, + "error": null +} +``` + ### 管理员管理 #### GET /admin/admins -认证:Admin JWT -查询参数:`page`, `limit`, `role`, `isActive`。 +认证:Admin JWT +查询参数:`page`, `limit`, `role`, `isActive`。 响应:管理员数组 + `pagination`。 #### GET /admin/admins/:id @@ -936,7 +1007,7 @@ Plus 用户响应(无需看广告,返回订阅权益摘要): 认证:Admin JWT -查询参数:`page`, `limit`, `status`, `categoryId`, `keyword`, `difficulty`, `source`, `sortBy`, `sortOrder`。 +查询参数:`page`, `limit`, `status`, `categoryId`, `keyword`, `difficulty`, `source`, `sortBy`, `sortOrder`。 `sortBy` 取值:`createdAt`, `updatedAt`, `difficulty`。`sortOrder` 取值:`asc`, `desc`。 #### GET /admin/questions/:id @@ -1039,7 +1110,7 @@ Plus 用户响应(无需看广告,返回订阅权益摘要): #### POST /admin/questions/import-csv -认证:Admin JWT +认证:Admin JWT `Content-Type`: `text/plain` CSV 表头: @@ -1052,8 +1123,8 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 #### GET /admin/categories -认证:Admin JWT -查询参数:`page`, `limit`。 +认证:Admin JWT +查询参数:`page`, `limit`。 响应:分类数组 + `pagination`。 #### POST /admin/categories @@ -1096,8 +1167,8 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 #### GET /admin/knowledge-cards -认证:Admin JWT -查询参数:`page`, `limit`。 +认证:Admin JWT +查询参数:`page`, `limit`。 响应:知识点卡片数组 + `pagination`。 #### GET /admin/knowledge-cards/by-question/:questionId @@ -1124,7 +1195,7 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 #### GET /admin/skill-tree -认证:Admin JWT +认证:Admin JWT 查询参数:`categoryId`。 #### POST /admin/skill-tree @@ -1156,8 +1227,8 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 #### GET /admin/users -认证:Admin JWT -查询参数:`page`, `limit`, `search`。 +认证:Admin JWT +查询参数:`page`, `limit`, `search`。 响应:用户数组 + `pagination`。 #### GET /admin/users/:id @@ -1198,10 +1269,124 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 #### GET /admin/feedback -认证:Admin JWT -查询参数:`page`, `limit`。 +认证:Admin JWT +查询参数:`page`, `limit`。 响应:反馈数组 + `pagination`。 +### 游戏化审计 + +#### GET /admin/gamification/users/:id/wallet + +认证:Admin JWT +响应:指定用户金币钱包,未创建钱包时 `data` 为 `null`。 + +#### GET /admin/gamification/users/:id/inventory + +认证:Admin JWT +响应:指定用户道具库存数组。 + +#### GET /admin/gamification/users/:id/rewards + +认证:Admin JWT +查询参数:`page`, `limit`。 +响应:指定用户奖励流水数组 + `pagination`。 + +#### GET /admin/gamification/users/:id/ad-recovery + +认证:Admin JWT +查询参数:`page`, `limit`。 +响应:指定用户广告恢复会话数组 + `pagination`。 + +#### GET /admin/gamification/users/:id/transactions + +认证:Admin JWT +查询参数:`page`, `limit`。 +响应:指定用户资源变更流水数组 + `pagination`。 + +### 定时任务 + +#### GET /admin/jobs + +认证:Admin JWT + +响应: + +```json +{ + "success": true, + "data": [ + { + "name": "weekly-settlement", + "description": "周榜结算:按组快照上周排名,给每组前 3 名发金币奖励", + "schedule": "每周一 UTC 00:30" + }, + { + "name": "expire-subscriptions", + "description": "订阅过期检查:检查并过期到期的订阅", + "schedule": "每日 UTC 01:00" + } + ], + "error": null +} +``` + +#### POST /admin/jobs/trigger + +认证:Admin JWT + +请求: + +```json +{ + "job": "weekly-settlement", + "dryRun": true +} +``` + +`job` 取值:`weekly-settlement`, `expire-subscriptions`。`dryRun` 可选,默认 `false`。 + +响应: + +```json +{ + "success": true, + "data": { + "job": "weekly-settlement", + "dryRun": true, + "executedAt": "2026-05-18T00:30:00.000Z", + "result": {} + }, + "error": null +} +``` + +## 已注册兼容接口 + +以下接口仍在 `src/index.ts` 中注册,但不是新客户端的推荐主合同。新客户端优先使用上文的 Flutter 客户端聚合 API、订阅 API 和广告恢复两步流程。 + +| 路径 | 状态 | 推荐替代 | +|------|------|----------| +| `GET /quiz/categories` | 兼容旧出题接口 | `GET /tracks` | +| `GET /quiz/categories/:id/chapters` | 兼容旧出题接口 | `GET /tracks/:trackId` | +| `GET /quiz/chapters/:id/questions` | 兼容旧出题接口 | `GET /challenges/next` | +| `POST /quiz/answer` | 兼容旧出题接口 | `POST /challenges/answer` | +| `POST /quiz/rate` | 兼容旧反馈接口 | 仍可用于题目评分 | +| `GET /progress/dashboard` | 兼容旧进度接口 | `GET /progress/summary` | +| `GET /progress/streak` | 兼容旧进度接口 | `GET /progress/summary` | +| `GET /progress/hearts` | 兼容旧进度接口 | `GET /progress/summary` | +| `POST /progress/hearts/restore` | 已废弃恢复接口 | `POST /rewards/ad-recovery/session` + `POST /rewards/ad-recovery/complete` | +| `GET /progress/chapters` | 兼容旧进度接口 | `GET /tracks/:trackId` | +| `POST /feedback` | 兼容旧反馈接口 | 仍可用于用户反馈 | +| `GET /leaderboard` | 兼容旧排行榜接口 | `GET /leaderboards` | +| `GET /leaderboard/me` | 兼容旧排行榜接口 | `GET /leaderboards/me` | +| `GET /achievements` | 兼容旧成就接口 | 当前无聚合替代 | +| `POST /achievements/check` | 兼容旧成就接口 | 当前无聚合替代 | +| `POST /payment/verify-huawei` | 兼容旧支付接口 | `POST /subscription/verify` | +| `GET /payment/subscription` | 兼容旧订阅接口 | `GET /subscription` | +| `POST /rewards/hearts/restore` | 已废弃恢复接口 | `POST /rewards/ad-recovery/session` + `POST /rewards/ad-recovery/complete` | +| `POST /rewards/attempts/restore` | 已废弃恢复接口 | `POST /rewards/ad-recovery/session` + `POST /rewards/ad-recovery/complete` | +| `POST /rewards/streak/protect` | 已废弃恢复接口 | `POST /rewards/ad-recovery/session` + `POST /rewards/ad-recovery/complete` | + ## 错误代码 | 代码 | 说明 | @@ -1215,4 +1400,7 @@ categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2 | `INVALID_STATUS_TRANSITION` | 题目状态流转不合法 | | `INVALID_RECEIPT` | 支付收据验证失败 | | `UNSUPPORTED_PLATFORM` | 订阅平台暂不支持 | +| `NOT_IMPLEMENTED` | 路由已注册但功能未实现 | | `INTERNAL_ERROR` | 服务器内部错误 | + +客户端应把 `error.code` 当作稳定的机器分支键,`error.message` 只用于调试或兜底展示。广告恢复等业务失败会以 `success=true` 返回,并把状态放在 `data.status` / `data.reason` 中;这类流程不要只依赖 `error.code` 判断。