- 新增 /auth/link 游客账号关联接口的完整 API 文档 (认证表、请求/响应格式、场景说明、幂等保证、错误码 CONFLICT) - CLAUDE.md 补充相关项目引用和编码行为约定
30 KiB
Duoqi API Reference
多奇服务端 API 接口文档。本文按当前 Fastify 路由和 TypeScript DTO 更新。
最近一次代码审计:2026-05-18,来源为
src/index.ts注册的路由、src/routes/**/*.ts、src/types/app-api.ts和相关 service 返回值。
Base URL
| 环境 | Base URL |
|---|---|
| 生产 | https://api.duoqi.me/v1 |
| 本地开发 | http://localhost:3000/v1 |
健康检查是唯一不带 /v1 前缀的客户端端点:GET /health。
通用约定
认证
| 类型 | Header | 适用路径 |
|---|---|---|
| 无需认证 | - | /health, /v1/auth/guest, /v1/auth/huawei, /v1/auth/refresh, /v1/admin/login |
| JWT(游客) | Authorization: Bearer <jwt_token> |
/v1/auth/link |
| JWT | Authorization: Bearer <jwt_token> |
大多数客户端 API |
| Admin JWT | Authorization: Bearer <admin_jwt_token> |
/v1/admin/* |
统一响应
{
"success": true,
"data": {},
"error": null
}
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input"
}
}
分页响应会额外包含:
{
"pagination": {
"total": 100,
"page": 1,
"limit": 20
}
}
客户端 API
健康检查
GET /health
认证:无
响应:
{
"success": true,
"data": {
"status": "ok",
"timestamp": "2026-05-05T12:00:00.000Z"
},
"error": null
}
认证
POST /auth/guest
认证:无 限流:10 次/分钟
请求:
{
"deviceId": "device-id"
}
响应:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"nickname": null,
"avatarUrl": null,
"tier": "free"
},
"tokens": {
"accessToken": "jwt",
"refreshToken": "jwt"
}
},
"error": null
}
POST /auth/huawei
认证:无 限流:10 次/分钟
请求:
{
"authorizationCode": "authorization-code"
}
响应同 /auth/guest。
POST /auth/refresh
认证:无 限流:10 次/分钟
请求:
{
"refreshToken": "jwt"
}
响应:
{
"success": true,
"data": {
"accessToken": "jwt",
"refreshToken": "jwt"
},
"error": null
}
POST /auth/link
认证:JWT(游客 Token) 限流:10 次/分钟
用途:将当前登录的游客账号关联到第三方正式账号(当前支持 Apple Sign In)。支持两种场景:
- 场景 A(新用户):该 Apple ID 未注册过,游客行原地升级为 Apple 账号,无需数据迁移。
- 场景 B(老用户):该 Apple ID 已有正式账号,在事务内将游客的答题记录、奖励流水等数据合并到正式账号,不覆盖老账号的订阅、余额、库存、连续学习等核心资产。
请求:
{
"provider": "apple",
"credential": {
"identityToken": "apple-identity-token",
"authorizationCode": "optional-authorization-code"
},
"mergePolicy": "server_account_first",
"clientMigrationId": "client-generated-uuid"
}
provider:当前仅支持apple。credential.identityToken:Apple Sign In 返回的 identity token(必填)。credential.authorizationCode:Apple 授权码(可选)。mergePolicy:当前固定为server_account_first,以服务端已有数据为主。clientMigrationId:客户端生成的唯一 ID,用于幂等保证,防止网络重试导致重复合并。
成功响应:
{
"success": true,
"data": {
"session": {
"user": {
"id": "uuid",
"nickname": null,
"avatarUrl": null,
"tier": "free"
},
"tokens": {
"accessToken": "jwt",
"refreshToken": "jwt"
}
},
"migrationSummary": {
"policy": "server_account_first",
"imported": {
"challengeAttempts": 5
},
"skipped": {
"subscriptions": 1
},
"conflicts": []
},
"bootstrap": null
},
"error": null
}
session.tokens:关联成功后返回正式账号的新 JWT,客户端应替换本地存储的 token。migrationSummary.imported:从游客账号导入的数据类型和数量。migrationSummary.skipped:因合并策略跳过的数据(不覆盖老账号已有的数据)。migrationSummary.conflicts:合并过程中发现的冲突项列表,通常为空。bootstrap:当前固定为null,客户端需调用GET /app/bootstrap获取最新状态。
错误响应:
{
"success": false,
"data": null,
"error": {
"code": "CONFLICT",
"message": "Migration already completed"
}
}
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid Apple identity token"
}
}
重复提交相同
clientMigrationId时返回幂等结果,不会触发重复合并。游客账号只能关联一次,已关联后再次调用返回CONFLICT。
POST /auth/phone
认证:无 限流:10 次/分钟
当前已注册但未实现,固定返回 HTTP 501:
{
"success": false,
"data": null,
"error": {
"code": "NOT_IMPLEMENTED",
"message": "Phone login not implemented yet"
}
}
GET /auth/me
认证:JWT
响应:
{
"success": true,
"data": {
"id": "uuid",
"nickname": "用户昵称",
"avatarUrl": "https://example.com/avatar.png",
"tier": "free",
"xpTotal": 150,
"streakDays": 3,
"heartsRemaining": 5,
"dailyXpEarned": 20,
"dailyXpGoal": 50
},
"error": null
}
Flutter 客户端聚合 API
这组接口对应 Flutter 原型使用的主题路线、挑战、资源摘要、商店和订阅合同。
GET /app/bootstrap
认证:JWT
响应:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"nickname": "知识探险家",
"avatarUrl": null,
"tier": "free",
"level": 1
},
"progress": {
"hearts": 5,
"maxHearts": 5,
"nextHeartRestoreAt": null,
"dailyAttemptsLeft": 5,
"dailyAttemptsMax": 5,
"nextAttemptResetAt": "2026-05-06T00:00:00.000Z",
"highRewardSessionsLeft": 3,
"highRewardSessionsMax": 3,
"xp": 0,
"level": 1,
"xpToNextLevel": 400,
"streakDays": 0,
"checkInDays": 0,
"streakProtectedUntil": null,
"activeTrackId": null,
"isSubscribed": false
},
"tracks": [],
"shopBenefits": [],
"shop": {
"benefits": [],
"products": [
{
"id": "hint-feather",
"type": "item",
"itemId": "hint_feather",
"title": "提示羽毛",
"description": "答题时排除 1 个错误选项",
"priceCoins": 80,
"quantity": 1,
"enabled": true
}
]
},
"wallet": {
"coinsBalance": 260
},
"inventory": {
"items": [
{
"itemId": "hint_feather",
"quantity": 2,
"activeUntil": null,
"metadata": null
}
]
},
"subscription": {
"status": "none",
"tier": "free",
"expiresAt": null,
"autoRenew": false
}
},
"error": null
}
说明:shopBenefits 为兼容旧客户端保留,内容等同于 shop.benefits。新客户端应优先读取 shop.products、wallet.coinsBalance 和 inventory.items 来展示金币、背包和可购买商品。
GET /tracks
认证:JWT
响应:
{
"success": true,
"data": [
{
"id": "history",
"name": "历史",
"icon": "🏛",
"progress": 25,
"nodes": [
{
"id": "uuid",
"title": "第一章",
"status": "current",
"reward": "+40 XP",
"questionCount": 4
}
]
}
],
"error": null
}
nodes[].status 取值:done, current, locked, chest。
GET /tracks/:trackId
认证:JWT
路径参数:trackId 可以是分类 id 或 slug。
响应:单个 ThemeTrackDto,不存在时 data 为 null。
GET /challenges/next
认证:JWT
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
trackId |
string | 是 | 分类 id 或 slug |
响应:
{
"success": true,
"data": {
"challengeId": "challenge-session-uuid",
"trackId": "history",
"nodeId": "chapter-uuid",
"totalQuestions": 5,
"highRewardEligible": true,
"questions": [
{
"challengeId": "challenge-session-uuid",
"trackId": "history",
"nodeId": "chapter-uuid",
"question": {
"id": "question-uuid-1",
"prompt": "题目文本",
"options": [
{ "id": "a", "text": "选项 A" },
{ "id": "b", "text": "选项 B" },
{ "id": "c", "text": "选项 C" }
]
}
}
]
},
"error": null
}
服务端会创建挑战组会话并一次返回 5 题,题目选项不包含正确答案标记。highRewardEligible=false 表示今日高奖励挑战次数已用尽,本轮 XP 和宝箱掉落按降级规则结算。题库不足 5 题或没有可用题目时 data 为 null。
POST /challenges/answer
认证:JWT
请求:
{
"challengeId": "challenge-session-uuid",
"questionId": "question-uuid",
"selectedOptionId": "a",
"timeMs": 1500,
"comboCount": 0,
"submitRequestId": "client-submit-id"
}
submitRequestId 可选;未传时服务端会使用 challengeId + questionId 作为默认幂等 key。同一挑战组内重复提交同一道题会返回第一次裁决结果,不会重复扣资源或重复发放奖励。
响应:
{
"success": true,
"data": {
"challengeId": "challenge-session-uuid",
"answerState": "correct",
"correctOptionId": "a",
"xpDelta": 10,
"progress": {
"hearts": 5,
"dailyAttemptsLeft": 5,
"highRewardSessionsLeft": 2,
"highRewardSessionsMax": 3,
"xp": 10,
"streakDays": 0
},
"knowledgeCard": {
"id": "card-uuid",
"title": "知识点摘要",
"summary": "知识点摘要",
"fact": "深入解析"
},
"rewards": [
{ "type": "xp", "amount": 10, "title": "+10 XP" }
]
},
"error": null
}
answerState 取值:correct, wrong。
GET /progress/summary
认证:JWT
响应:ProgressSummaryDto,字段同 /app/bootstrap 中的 progress,包含 highRewardSessionsLeft 和 highRewardSessionsMax。
PATCH /progress/preferences
认证:JWT
请求:
{
"activeTrackId": "history"
}
响应:更新后的 ProgressSummaryDto。
POST /progress/check-in
认证:JWT 请求:无
响应:更新后的 ProgressSummaryDto。
GET /leaderboards
认证:JWT
查询参数:
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
scope |
region 或 topic |
region |
排行榜范围 |
trackId |
string | - | scope=topic 时可传 |
page |
number | 1 | 页码 |
limit |
number | 20 | 1-100 |
响应:
{
"success": true,
"data": [
{
"rank": 1,
"userId": "uuid",
"displayName": "玩家昵称",
"avatarUrl": null,
"xp": 500,
"badge": "榜首",
"isMe": false
}
],
"meta": {
"weekStart": "2026-05-11",
"weekEnd": "2026-05-17",
"nextRefreshAt": "2026-05-18",
"groupId": "week-2026-05-11-group-1",
"rewardPreview": [
{ "rank": 1, "coins": 300 },
{ "rank": 2, "coins": 150 },
{ "rank": 3, "coins": 50 }
]
},
"pagination": {
"total": 25,
"page": 1,
"limit": 20
},
"error": null
}
xp为本周累计 XP(非全局累计),排名基于用户所在 20-30 人分组内。meta.rewardPreview展示各组前 3 名的金币奖励,激励用户冲榜。
GET /leaderboards/me
认证:JWT
查询参数同 /leaderboards。
响应:
{
"success": true,
"data": {
"rank": 3,
"userId": "uuid",
"displayName": "我",
"avatarUrl": null,
"xp": 150,
"badge": "前三",
"isMe": true
},
"meta": {
"weekStart": "2026-05-11",
"weekEnd": "2026-05-17",
"nextRefreshAt": "2026-05-18",
"groupId": "week-2026-05-11-group-1",
"rank": 3,
"rewardPreview": [
{ "rank": 1, "coins": 300 },
{ "rank": 2, "coins": 150 },
{ "rank": 3, "coins": 50 }
]
},
"error": null
}
GET /shop
认证:JWT
响应:
{
"success": true,
"data": {
"benefits": [
{
"id": "restore-hearts",
"type": "hearts",
"title": "恢复满血",
"description": "血量不足时继续挑战",
"enabled": true,
"requiresAd": true
}
],
"products": [
{
"id": "hint-feather",
"type": "item",
"itemId": "hint_feather",
"title": "提示羽毛",
"description": "答题时排除 1 个错误选项",
"priceCoins": 80,
"quantity": 1,
"enabled": true
}
]
},
"error": null
}
说明:requiresAd=true 的权益应通过 /rewards/ad-recovery/session 和 /rewards/ad-recovery/complete 完成资格检查和结算。
可购买商品价格:提示羽毛 80 金币,爱心补给 150 金币,双倍 XP 药水 250 金币,连胜护盾 400 金币,第一版吉祥物装扮 800 金币。
POST /shop/purchase
认证:JWT
请求:
{
"productId": "hint-feather",
"clientRequestId": "request-uuid"
}
productId 取值:hint-feather, heart-supply, double-xp-potion, streak-shield, mascot-outfit-starter。
响应:
{
"success": true,
"data": {
"product": {
"id": "hint-feather",
"type": "item",
"itemId": "hint_feather",
"title": "提示羽毛",
"description": "答题时排除 1 个错误选项",
"priceCoins": 80,
"quantity": 1,
"enabled": true
},
"coinsSpent": 80,
"coinsBalance": 220,
"item": {
"itemId": "hint_feather",
"quantity": 3,
"activeUntil": null,
"metadata": null
},
"rewards": [
{
"type": "item",
"source": "inventory",
"itemId": "hint_feather",
"quantity": 1,
"title": "提示羽毛 x1"
}
]
},
"error": null
}
购买使用 clientRequestId 作为幂等边界;金币不足时返回统一错误格式,error.code 为 VALIDATION_ERROR。
POST /inventory/items/use
认证:JWT
请求:
{
"itemId": "hint_feather",
"clientRequestId": "request-uuid",
"questionId": "question-uuid"
}
itemId 取值:heart_supply, double_xp_potion, hint_feather, streak_shield。使用 hint_feather 时必须传 questionId。
响应:
{
"success": true,
"data": {
"itemId": "hint_feather",
"quantityRemaining": 2,
"effect": {
"type": "hint",
"excludedOptions": ["错误选项 A"]
}
},
"error": null
}
效果说明:heart_supply 恢复当前用户爱心到上限;double_xp_potion 返回 15 分钟有效期 activeUntil;hint_feather 返回可排除选项;streak_shield 返回 streakProtectedUntil。clientRequestId 用于道具消耗幂等。
GET /subscription
认证:JWT
响应:
{
"success": true,
"data": {
"status": "active",
"tier": "pro",
"expiresAt": "2026-06-05T00:00:00.000Z",
"autoRenew": true
},
"error": null
}
status 取值:none, active, expired, cancelled。tier 取值:free, pro, proplus。
POST /subscription/verify
认证:JWT
请求:
{
"platform": "huawei",
"purchaseToken": "purchase-token",
"productId": "duoqi_plus_monthly",
"tier": "pro"
}
响应:更新后的订阅 DTO。当前仅支持 platform=huawei,其他平台返回 UNSUPPORTED_PLATFORM。
激励广告恢复
POST /rewards/ad-recovery/session
认证:JWT
用途:展示广告前创建恢复会话,服务端返回会话 ID、广告位和资格状态。
请求:
{
"type": "hearts",
"clientRequestId": "uuid-from-client",
"platform": "ios",
"adProvider": "mock"
}
type 取值:hearts, bonusAttempts, streakProtection。
platform 取值:ios, android, harmony, web。
符合资格响应:
{
"success": true,
"data": {
"sessionId": "uuid",
"eligible": true,
"type": "hearts",
"adPlacementId": "duoqi_restore_hearts_ios",
"remainingToday": 2,
"expiresAt": "2026-05-05T12:30:00.000Z"
},
"error": null
}
不符合资格响应:
{
"success": true,
"data": {
"sessionId": null,
"eligible": false,
"reason": "daily_limit_reached",
"nextAvailableAt": "2026-05-06T00:00:00.000Z"
},
"error": null
}
Plus 用户响应(无需看广告,返回订阅权益摘要):
{
"success": true,
"data": {
"sessionId": null,
"eligible": false,
"reason": "already_subscribed",
"subscriptionBenefits": {
"tier": "pro",
"unlimitedHearts": true,
"dailyHighRewardSessions": null
}
},
"error": null
}
subscriptionBenefits.dailyHighRewardSessions为null表示无限制。客户端可据此展示替代提示,引导用户享受订阅权益。
POST /rewards/ad-recovery/complete
认证:JWT
用途:广告 SDK 返回完整播放后提交凭证,由服务端幂等结算奖励。mock provider 用于测试;真实 provider 需要提交 providerRewardToken。
请求:
{
"sessionId": "uuid",
"clientRequestId": "uuid-from-client",
"adProvider": "admob",
"providerRewardToken": "opaque-provider-token",
"completedAt": "2026-05-05T12:03:00.000Z"
}
成功响应:
{
"success": true,
"data": {
"status": "completed",
"type": "hearts",
"reward": {
"heartsDelta": 3,
"dailyAttemptsDelta": 0,
"streakProtectionGranted": false
},
"progress": {
"hearts": 5,
"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,
"checkInDays": 8,
"streakProtectedUntil": null,
"activeTrackId": "history",
"isSubscribed": false
},
"limits": {
"remainingHeartsRecoveriesToday": 2,
"remainingAttemptRecoveriesToday": 3,
"nextStreakProtectionAvailableAt": "2026-05-12T00:00:00.000Z"
}
},
"error": null
}
失败响应:
{
"success": true,
"data": {
"status": "failed",
"reason": "provider_verification_failed",
"message": "广告未完整播放,未发放奖励。",
"progress": {
"hearts": 2,
"maxHearts": 5,
"dailyAttemptsLeft": 1,
"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
}
reason 取值:ad_not_completed, provider_verification_failed, session_expired, daily_limit_reached, cooldown_active, already_subscribed, invalid_type。
广告恢复奖励通过统一奖励结算层(
rewardLedger)发放,以ad_recovery:{sessionId}为幂等 key,记录发放前后资源快照。旧接口POST /rewards/hearts/restore、POST /rewards/attempts/restore、POST /rewards/streak/protect、POST /progress/hearts/restore已废弃,请使用上述 session + complete 两步流程。
管理端 API
管理端路由统一带 /v1/admin 前缀。
管理端认证
POST /admin/login
认证:无
请求:
{
"username": "admin",
"password": "password123"
}
响应:
{
"success": true,
"data": {
"accessToken": "jwt",
"refreshToken": "jwt",
"admin": {
"id": "uuid",
"username": "admin",
"role": "super_admin"
}
},
"error": null
}
PUT /admin/change-password
认证:Admin JWT
请求:
{
"currentPassword": "old-password",
"newPassword": "new-password"
}
响应:
{
"success": true,
"data": {
"message": "Password changed successfully"
},
"error": null
}
POST /admin/auth
认证:无
兼容旧管理端的 ADMIN_TOKEN 验证接口。新管理端应使用 /admin/login 获取 Admin JWT。
请求:
{
"token": "admin-token"
}
响应:
{
"success": true,
"data": {
"authenticated": true
},
"error": null
}
管理员管理
GET /admin/admins
认证:Admin JWT
查询参数:page, limit, role, isActive。
响应:管理员数组 + pagination。
GET /admin/admins/:id
认证:Admin JWT
POST /admin/admins
认证:Admin JWT,且 role=super_admin
请求:
{
"username": "newadmin",
"password": "password123",
"role": "admin"
}
PUT /admin/admins/:id
认证:Admin JWT,且 role=super_admin
请求:
{
"username": "updated",
"role": "admin",
"isActive": 1
}
DELETE /admin/admins/:id
认证:Admin JWT,且 role=super_admin。软删除管理员。
POST /admin/admins/:id/reset-password
认证:Admin JWT,且 role=super_admin。响应包含一次性明文密码 plainPassword。
题目管理
GET /admin/questions
认证:Admin JWT
查询参数:page, limit, status, categoryId, keyword, difficulty, source, sortBy, sortOrder。
sortBy 取值:createdAt, updatedAt, difficulty。sortOrder 取值:asc, desc。
GET /admin/questions/:id
认证:Admin JWT
POST /admin/questions
认证:Admin JWT
请求:
{
"stem": { "text": "题目内容" },
"contentType": "text",
"correctAnswer": "正确答案",
"distractors": ["干扰项1", "干扰项2"],
"categoryId": "history",
"difficulty": 3,
"knowledgeCard": {
"summary": "知识点摘要",
"deepDive": "深入解析",
"sourceRef": "来源"
}
}
contentType 取值:text, image, video, audio。
PUT /admin/questions/:id
认证:Admin JWT。请求字段同创建接口,均可选,额外支持 status。
PATCH /admin/questions/:id/status
认证:Admin JWT
请求:
{
"status": "published"
}
status 取值:draft, reviewing, published, archived。服务会校验状态流转。
DELETE /admin/questions/:id
认证:Admin JWT。归档题目。
POST /admin/questions/batch-publish
认证:Admin JWT
请求:
{
"ids": ["uuid"]
}
POST /admin/questions/batch-archive
认证:Admin JWT。请求同 batch-publish。
POST /admin/questions/batch-delete
认证:Admin JWT。软删除,等同批量归档。
POST /admin/questions/import
认证:Admin JWT
请求:
{
"questions": [
{
"stem": { "text": "题目内容" },
"contentType": "text",
"correctAnswer": "正确答案",
"distractors": ["干扰项1", "干扰项2"],
"categoryId": "history",
"difficulty": 3,
"knowledgeCard": {
"summary": "知识点摘要",
"deepDive": "深入解析",
"sourceRef": "来源"
}
}
]
}
单次 1-200 条,全有或全无。
POST /admin/questions/import-csv
认证:Admin JWT
Content-Type: text/plain
CSV 表头:
categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2,distractor3,distractor4,distractor5,cardSummary,cardDeepDive,cardSourceRef
分类管理
GET /admin/categories
认证:Admin JWT
查询参数:page, limit。
响应:分类数组 + pagination。
POST /admin/categories
认证:Admin JWT
请求:
{
"id": "history",
"name": "历史",
"slug": "history",
"parentId": null,
"sortOrder": 1
}
PUT /admin/categories/:id
认证:Admin JWT
请求:
{
"name": "历史",
"slug": "history",
"parentId": null,
"sortOrder": 1,
"status": "active"
}
DELETE /admin/categories/:id
认证:Admin JWT。归档分类。
知识点卡片
GET /admin/knowledge-cards
认证:Admin JWT
查询参数:page, limit。
响应:知识点卡片数组 + pagination。
GET /admin/knowledge-cards/by-question/:questionId
认证:Admin JWT
PUT /admin/knowledge-cards/:id
认证:Admin JWT
请求:
{
"summary": "摘要",
"deepDive": "深入解析",
"sourceRef": "来源"
}
所有字段均可选,但至少应传一个需要更新的字段。
技能树管理
GET /admin/skill-tree
认证:Admin JWT
查询参数:categoryId。
POST /admin/skill-tree
认证:Admin JWT
请求:
{
"categoryId": "history",
"title": "第一章",
"parentId": null,
"sortOrder": 1,
"questionsRequired": 4,
"passThreshold": 2
}
PUT /admin/skill-tree/:id
认证:Admin JWT。请求字段同创建接口,均可选。
DELETE /admin/skill-tree/:id
认证:Admin JWT。
用户管理
GET /admin/users
认证:Admin JWT
查询参数:page, limit, search。
响应:用户数组 + pagination。
GET /admin/users/:id
认证:Admin JWT
PUT /admin/users/:id/ban
认证:Admin JWT
PUT /admin/users/:id/unban
认证:Admin JWT
统计数据
GET /admin/stats
认证:Admin JWT
响应:
{
"success": true,
"data": {
"totalUsers": 1000,
"activeUsers": 150,
"totalQuestions": 500,
"publishedQuestions": 450,
"averageXp": 200
},
"error": null
}
反馈管理
GET /admin/feedback
认证: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
响应:
{
"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
请求:
{
"job": "weekly-settlement",
"dryRun": true
}
job 取值:weekly-settlement, expire-subscriptions。dryRun 可选,默认 false。
响应:
{
"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 |
错误代码
| 代码 | 说明 |
|---|---|
VALIDATION_ERROR |
请求参数验证失败 |
VALIDATION_FAILED |
批量导入中部分题目校验失败 |
CSV_PARSE_ERROR |
CSV 解析失败 |
UNAUTHORIZED |
未认证或认证失败 |
FORBIDDEN |
权限不足 |
NOT_FOUND |
资源不存在 |
CONFLICT |
资源冲突(如重复迁移、已关联账号) |
INVALID_STATUS_TRANSITION |
题目状态流转不合法 |
INVALID_RECEIPT |
支付收据验证失败 |
UNSUPPORTED_PLATFORM |
订阅平台暂不支持 |
NOT_IMPLEMENTED |
路由已注册但功能未实现 |
INTERNAL_ERROR |
服务器内部错误 |
客户端应把 error.code 当作稳定的机器分支键,error.message 只用于调试或兜底展示。广告恢复等业务失败会以 success=true 返回,并把状态放在 data.status / data.reason 中;这类流程不要只依赖 error.code 判断。