新增 PATCH /admin/questions/:id/status 接口,支持题目状态流转并校验合法性: - draft → reviewing, archived - reviewing → published, draft, archived - published → archived - archived → draft
24 KiB
Duoqi API Reference
多奇服务端 API 接口文档 Base URL:
http://<host>:3000/v1
目录
通用约定
认证方式
| 类型 | Header | 适用路径 |
|---|---|---|
| 无需认证 | - | /v1/health, /v1/auth/* |
| JWT | Authorization: Bearer <jwt_token> |
大多数客户端 API |
| Admin JWT | Authorization: Bearer <admin_jwt_token> |
/v1/admin/* (推荐) |
| Admin Token | Authorization: Bearer <admin_token> |
/v1/admin/* (向后兼容) |
统一响应格式
// 成功响应
{
"success": true,
"data": <T>,
"error": null
}
// 错误响应
{
"success": false,
"data": null,
"error": {
"code": "ERROR_CODE",
"message": "错误描述"
}
}
// 分页响应(额外包含)
{
"success": true,
"data": [...],
"pagination": {
"total": 100,
"page": 1,
"limit": 20
},
"error": null
}
状态码
| 状态码 | 含义 |
|---|---|
| 200 | 请求成功 |
| 400 | 请求参数验证失败 |
| 401 | 未认证 / 认证失败 |
| 404 | 资源不存在 |
| 501 | 功能未实现 |
客户端 API
健康检查
GET /health
健康检查端点,用于服务可用性探测。
认证: 无
请求: 无
响应:
{
"success": true,
"data": {
"status": "ok",
"timestamp": "2026-04-10T12:00:00.000Z"
},
"error": null
}
认证
POST /auth/guest
游客登录,通过设备 ID 创建或获取用户账号。
认证: 无 限流: 10 次/分钟
请求体:
{
"deviceId": "string (必填)"
}
响应:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"nickname": null,
"avatarUrl": null,
"tier": "free"
},
"tokens": {
"accessToken": "jwt_token",
"refreshToken": "jwt_token"
}
},
"error": null
}
POST /auth/huawei
华为账号登录。
认证: 无 限流: 10 次/分钟
请求体:
{
"authorizationCode": "string (必填)"
}
响应:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"nickname": "用户昵称",
"avatarUrl": "头像URL",
"tier": "free"
},
"tokens": {
"accessToken": "jwt_token",
"refreshToken": "jwt_token"
}
},
"error": null
}
POST /auth/phone
手机号登录(未实现)。
认证: 无 状态: 501 Not Implemented
POST /auth/refresh
刷新访问令牌。
认证: 无 限流: 10 次/分钟
请求体:
{
"refreshToken": "string (必填)"
}
响应:
{
"success": true,
"data": {
"accessToken": "new_jwt_token",
"refreshToken": "new_refresh_token"
},
"error": null
}
GET /auth/me
获取当前用户信息。
认证: JWT
响应:
{
"success": true,
"data": {
"id": "uuid",
"nickname": "用户昵称",
"avatarUrl": "头像URL",
"tier": "free | pro | proplus",
"xpTotal": 150,
"streakDays": 3,
"heartsRemaining": 5,
"dailyXpEarned": 20,
"dailyXpGoal": 50
},
"error": null
}
答题
GET /quiz/categories
获取所有题目分类列表。
认证: JWT
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "历史",
"slug": "history",
"parentId": null
}
],
"error": null
}
GET /quiz/categories/:id/chapters
获取指定分类下的章节列表。
认证: JWT
路径参数:
id: 分类 ID
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"categoryId": "uuid",
"title": "第一章",
"parentId": null,
"sortOrder": 1
}
],
"error": null
}
GET /quiz/chapters/:id/questions
获取章节下的题目(包含用户答题状态)。
认证: JWT
路径参数:
id: 章节 ID
响应:
{
"success": true,
"data": {
"chapterId": "uuid",
"title": "第一章",
"questionsRequired": 5,
"passThreshold": 3,
"questions": [
{
"id": "uuid",
"stem": { "text": "题目内容" },
"contentType": "text",
"options": ["A", "B", "C", "D"],
"answered": false,
"isCorrect": null
}
]
},
"error": null
}
POST /quiz/answer
提交答案。
认证: JWT
请求体:
{
"questionId": "uuid (必填)",
"selectedAnswer": "string (必填)",
"timeMs": 1500
}
响应:
{
"success": true,
"data": {
"correct": true,
"correctAnswer": "B",
"xpEarned": 10,
"streakBonus": 0,
"chapterCompleted": false
},
"error": null
}
POST /quiz/rate
评价题目质量。
认证: JWT
请求体:
{
"questionId": "uuid (必填)",
"rating": "good | bad"
}
响应:
{
"success": true,
"data": null,
"error": null
}
进度
GET /progress/dashboard
获取用户进度概览。
认证: JWT
响应:
{
"success": true,
"data": {
"xpTotal": 150,
"streakDays": 3,
"heartsRemaining": 5,
"dailyXpEarned": 20,
"dailyXpGoal": 50,
"categoriesCompleted": 1,
"totalCategories": 5
},
"error": null
}
GET /progress/streak
获取连胜信息。
认证: JWT
响应:
{
"success": true,
"data": {
"currentStreak": 3,
"longestStreak": 7,
"lastActiveDate": "2026-04-10"
},
"error": null
}
GET /progress/hearts
获取红心信息。
认证: JWT
响应:
{
"success": true,
"data": {
"remaining": 5,
"max": 5,
"nextRestoreAt": "2026-04-10T13:00:00.000Z"
},
"error": null
}
POST /progress/hearts/restore
恢复红心。
认证: JWT
请求体:
{
"method": "ad | wait | upgrade"
}
响应:
{
"success": true,
"data": {
"remaining": 5,
"restored": 1
},
"error": null
}
GET /progress/chapters
获取所有章节进度。
认证: JWT
响应:
{
"success": true,
"data": [
{
"chapterId": "uuid",
"title": "第一章",
"completedQuestions": 3,
"totalQuestions": 5,
"passed": false,
"passedAt": null
}
],
"error": null
}
POST /feedback
提交用户反馈。
认证: JWT
请求体:
{
"content": "string (必填, 1-2000字符)",
"contact": "string (可选, 最多255字符)",
"pageContext": "string (可选, 最多200字符)"
}
响应:
{
"success": true,
"data": null,
"error": null
}
游戏化
GET /leaderboard
获取排行榜。
认证: JWT
查询参数:
tier: "free" | "pro" | "proplus" (可选)page: 页码 (默认: 1)limit: 每页数量 (默认: 20)
响应:
{
"success": true,
"data": [
{
"rank": 1,
"userId": "uuid",
"nickname": "玩家昵称",
"avatarUrl": "头像URL",
"xpTotal": 5000
}
],
"pagination": {
"total": 100,
"page": 1,
"limit": 20
},
"error": null
}
GET /leaderboard/me
获取当前用户排名。
认证: JWT
响应:
{
"success": true,
"data": {
"rank": 15,
"xpTotal": 1500
},
"error": null
}
GET /achievements
获取成就列表。
认证: JWT
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"code": "first_win",
"name": "初出茅庐",
"description": "完成第一道题",
"iconUrl": "图标URL",
"unlocked": true,
"unlockedAt": "2026-04-10T10:00:00.000Z"
}
],
"error": null
}
POST /achievements/check
检查并解锁新成就。
认证: JWT
响应:
{
"success": true,
"data": {
"newlyUnlocked": [
{
"id": "uuid",
"code": "streak_7",
"name": "连胜达人",
"description": "连续7天活跃"
}
]
},
"error": null
}
支付
POST /payment/verify-huawei
验证华为 IAP 收据并激活订阅。
认证: JWT
请求体:
{
"purchaseToken": "string (必填)",
"productId": "string (必填)",
"tier": "pro | proplus"
}
响应:
{
"success": true,
"data": {
"tier": "pro",
"provider": "huawei",
"active": true,
"expiresAt": "2026-05-10T00:00:00.000Z"
},
"error": null
}
GET /payment/subscription
获取当前订阅状态。
认证: JWT
响应:
{
"success": true,
"data": {
"tier": "pro",
"provider": "huawei",
"active": true,
"expiresAt": "2026-05-10T00:00:00.000Z",
"autoRenew": true
},
"error": null
}
管理端 API
管理端认证
POST /admin/auth/login
管理员用户名密码登录。
认证: 无
请求体:
{
"username": "string (必填)",
"password": "string (必填, 最少8字符)"
}
响应:
{
"success": true,
"data": {
"accessToken": "jwt_token (1h有效)",
"refreshToken": "jwt_token (30d有效)",
"admin": {
"id": "uuid",
"username": "admin",
"role": "super_admin"
}
},
"error": null
}
错误 (401):
{
"success": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid username or password"
}
}
POST /admin/auth
管理端 Token 认证(向后兼容,推荐使用 /admin/auth/login)。
认证: 无
请求体:
{
"token": "string (必填)"
}
响应:
{
"success": true,
"data": {
"authenticated": true
},
"error": null
}
错误 (401):
{
"success": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid admin token"
}
}
管理员管理
仅 super_admin 角色可执行写操作(POST、PUT、DELETE),读操作(GET)所有管理员均可访问。
GET /admin/admins
获取管理员列表。
认证: Admin JWT
查询参数:
page: 页码 (默认: 1, 必须 ≥ 1)limit: 每页数量 (默认: 20, 范围: 1-50)role: "admin" | "super_admin" (可选,按角色筛选)isActive: 0 | 1 (可选,按状态筛选)
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"username": "admin",
"role": "super_admin",
"isActive": 1,
"lastLoginAt": "2026-04-11T10:00:00.000Z",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-11T10:00:00.000Z"
}
],
"pagination": {
"total": 5,
"page": 1,
"limit": 20
},
"error": null
}
GET /admin/admins/:id
获取管理员详情。
认证: Admin JWT
路径参数:
id: 管理员 ID
响应:
{
"success": true,
"data": {
"id": "uuid",
"username": "admin",
"role": "super_admin",
"isActive": 1,
"lastLoginAt": "2026-04-11T10:00:00.000Z",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-11T10:00:00.000Z"
},
"error": null
}
错误 (404):
{
"success": false,
"data": null,
"error": {
"code": "NOT_FOUND",
"message": "Admin user not found"
}
}
POST /admin/admins
创建新管理员(super_admin 专属)。
认证: Admin JWT (super_admin)
请求体:
{
"username": "string (必填, 3-50字符)",
"password": "string (必填, 8-128字符)",
"role": "admin | super_admin (必填)"
}
响应:
{
"success": true,
"data": {
"id": "uuid",
"username": "newadmin",
"role": "admin",
"isActive": 1,
"lastLoginAt": null,
"createdAt": "2026-04-11T12:00:00.000Z",
"updatedAt": "2026-04-11T12:00:00.000Z",
"plainPassword": "随机生成的初始密码"
},
"error": null
}
错误 (403):
{
"success": false,
"data": null,
"error": {
"code": "FORBIDDEN",
"message": "Super admin privileges required"
}
}
错误 (400):
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_ERROR",
"message": "Username \"admin\" already exists"
}
}
PUT /admin/admins/:id
更新管理员信息(super_admin 专属)。
认证: Admin JWT (super_admin)
路径参数:
id: 管理员 ID
请求体:
{
"username": "string (可选, 3-50字符)",
"role": "admin | super_admin (可选)",
"isActive": 0 | 1 (可选)"
}
响应:
{
"success": true,
"data": {
"id": "uuid",
"username": "updated_username",
"role": "admin",
"isActive": 0,
"lastLoginAt": "2026-04-11T10:00:00.000Z",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-11T12:00:00.000Z"
},
"error": null
}
安全规则:
- 禁止删除或降级最后一个 super_admin
- 用户名必须唯一
DELETE /admin/admins/:id
软删除管理员(super_admin 专属)。
认证: Admin JWT (super_admin)
路径参数:
id: 管理员 ID
响应:
{
"success": true,
"data": {
"id": "uuid",
"username": "deleted_admin",
"role": "admin",
"isActive": 0,
"lastLoginAt": "2026-04-11T10:00:00.000Z",
"createdAt": "2026-04-01T00:00:00.000Z",
"updatedAt": "2026-04-11T12:00:00.000Z"
},
"error": null
}
说明:
- 软删除:将
isActive设为 0,不删除记录 - 禁止删除最后一个 super_admin
POST /admin/admins/:id/reset-password
重置管理员密码(super_admin 专属)。
认证: Admin JWT (super_admin)
路径参数:
id: 管理员 ID
响应:
{
"success": true,
"data": {
"adminId": "uuid",
"username": "admin",
"plainPassword": "新随机生成的12位密码"
},
"error": null
}
说明:
- 生成 12 位随机密码,包含大小写字母、数字、符号
- 明文密码仅在响应中返回一次,请妥善保存
- 密码使用 BCrypt 哈希后存储到数据库
题目管理
GET /admin/questions
获取题目列表。
认证: Admin Token
查询参数:
page: 页码 (默认: 1)limit: 每页数量 (默认: 20)status: draft | reviewing | published | archived (可选)categoryId: 分类 ID (可选)
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"stem": { "text": "题目内容" },
"contentType": "text",
"correctAnswer": "B",
"distractors": ["A", "C", "D"],
"categoryId": "uuid",
"difficulty": 3,
"status": "published"
}
],
"pagination": {
"total": 100,
"page": 1,
"limit": 20
},
"error": null
}
GET /admin/questions/:id
获取题目详情。
认证: Admin Token
路径参数:
id: 题目 ID
响应:
{
"success": true,
"data": {
"id": "uuid",
"stem": { "text": "题目内容" },
"contentType": "text",
"correctAnswer": "B",
"distractors": ["A", "C", "D"],
"categoryId": "uuid",
"difficulty": 3,
"status": "published",
"knowledgeCard": {
"id": "uuid",
"summary": "知识点摘要",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
},
"error": null
}
POST /admin/questions
创建新题目。
认证: Admin Token
请求体:
{
"stem": { "text": "题目内容" },
"contentType": "text | image | video | audio",
"correctAnswer": "B (必填)",
"distractors": ["A", "C", "D"],
"categoryId": "uuid (必填)",
"difficulty": 3,
"knowledgeCard": {
"summary": "知识点摘要 (必填)",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
}
响应:
{
"success": true,
"data": {
"id": "uuid",
"status": "draft"
},
"error": null
}
PUT /admin/questions/:id
更新题目。
认证: Admin Token
路径参数:
id: 题目 ID
请求体:
{
"stem": { "text": "题目内容" },
"contentType": "text | image | video | audio",
"correctAnswer": "B",
"distractors": ["A", "C", "D"],
"categoryId": "uuid",
"difficulty": 3,
"status": "draft | reviewing | published | archived"
}
DELETE /admin/questions/:id
归档题目。
认证: Admin Token
路径参数:
id: 题目 ID
响应:
{
"success": true,
"data": null,
"error": null
}
PATCH /admin/questions/:id/status
变更题目状态(带流转校验)。
认证: Admin Token
路径参数:
id: 题目 ID
请求体:
{
"status": "draft | reviewing | published | archived (必填)"
}
允许的状态流转:
| 当前状态 | 可变更到 |
|---|---|
| draft | reviewing, archived |
| reviewing | published, draft, archived |
| published | archived |
| archived | draft |
响应:
{
"success": true,
"data": {
"id": "uuid",
"stem": { "text": "题目内容" },
"contentType": "text",
"correctAnswer": "B",
"distractors": ["A", "C", "D"],
"categoryId": "uuid",
"difficulty": 3,
"status": "published",
"knowledgeCard": { ... }
},
"error": null
}
错误 (404):
{
"success": false,
"data": null,
"error": {
"code": "NOT_FOUND",
"message": "题目不存在"
}
}
错误 (400):
{
"success": false,
"data": null,
"error": {
"code": "INVALID_STATUS_TRANSITION",
"message": "不允许从 published 变更为 reviewing"
}
}
POST /admin/questions/batch-publish
批量发布题目。
认证: Admin Token
请求体:
{
"ids": ["uuid1", "uuid2"]
}
响应:
{
"success": true,
"data": null,
"error": null
}
分类管理
GET /admin/categories
获取分类列表。
认证: Admin Token
查询参数:
page: 页码 (默认: 1, 必须 ≥ 1)limit: 每页数量 (默认: 20, 范围: 1-50)
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "历史",
"slug": "history",
"parentId": null,
"sortOrder": 1,
"questionCount": 120,
"status": "active"
}
],
"pagination": {
"total": 45,
"page": 1,
"limit": 20
},
"error": null
}
POST /admin/categories
创建分类。
认证: Admin Token
请求体:
{
"id": "uuid (必填)",
"name": "分类名称 (必填)",
"slug": "分类slug (必填)",
"parentId": "uuid",
"sortOrder": 1
}
PUT /admin/categories/:id
更新分类。
认证: Admin Token
请求体:
{
"name": "分类名称",
"slug": "分类slug",
"parentId": "uuid | null",
"sortOrder": 1,
"status": "active | inactive"
}
DELETE /admin/categories/:id
归档分类。
认证: Admin Token
知识点卡片
GET /admin/knowledge-cards
获取知识点卡片列表。
认证: Admin Token
查询参数:
page: 页码 (默认: 1)limit: 每页数量 (默认: 20)
GET /admin/knowledge-cards/by-question/:questionId
根据题目 ID 获取知识点卡片。
认证: Admin Token
PUT /admin/knowledge-cards/:id
更新知识点卡片。
认证: Admin Token
请求体:
{
"summary": "摘要 (必填)",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
技能树管理
GET /admin/skill-tree
获取章节列表。
认证: Admin Token
查询参数:
categoryId: 分类 ID (可选)
POST /admin/skill-tree
创建章节。
认证: Admin Token
请求体:
{
"categoryId": "uuid (必填)",
"title": "章节标题 (必填)",
"parentId": "uuid",
"sortOrder": 1,
"questionsRequired": 5,
"passThreshold": 3
}
PUT /admin/skill-tree/:id
更新章节。
认证: Admin Token
请求体:
{
"title": "章节标题",
"parentId": "uuid | null",
"sortOrder": 1,
"questionsRequired": 5,
"passThreshold": 3
}
DELETE /admin/skill-tree/:id
删除章节。
认证: Admin Token
用户管理
GET /admin/users
获取用户列表。
认证: Admin Token
查询参数:
page: 页码 (默认: 1)limit: 每页数量 (默认: 20)search: 搜索关键词 (昵称/ID)
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"nickname": "玩家昵称",
"avatarUrl": "头像URL",
"tier": "free",
"xpTotal": 150,
"streakDays": 3,
"banned": false
}
],
"pagination": { ... },
"error": null
}
GET /admin/users/:id
获取用户详情。
认证: Admin Token
PUT /admin/users/:id/ban
封禁用户。
认证: Admin Token
PUT /admin/users/:id/unban
解封用户。
认证: Admin Token
统计数据
GET /admin/stats
获取仪表盘统计数据。
认证: Admin Token
响应:
{
"success": true,
"data": {
"totalUsers": 1000,
"activeUsers": 150,
"totalQuestions": 500,
"publishedQuestions": 450,
"averageXp": 200
},
"error": null
}
反馈管理
GET /admin/feedback
获取用户反馈列表。
认证: Admin Token
查询参数:
page: 页码 (默认: 1)limit: 每页数量 (默认: 20)
响应:
{
"success": true,
"data": [
{
"id": "uuid",
"userId": "uuid",
"content": "反馈内容",
"contact": "联系方式",
"pageContext": "页面上下文",
"createdAt": "2026-04-10T10:00:00.000Z"
}
],
"pagination": { ... },
"error": null
}
附录
错误代码
| 代码 | 说明 |
|---|---|
| VALIDATION_ERROR | 请求参数验证失败 |
| UNAUTHORIZED | 未认证或认证失败 |
| FORBIDDEN | 权限不足(需要 super_admin) |
| NOT_FOUND | 资源不存在 |
| INVALID_STATUS_TRANSITION | 题目状态流转不合法 |
| INVALID_RECEIPT | 支付收据验证失败 |
| NOT_IMPLEMENTED | 功能未实现 |
| INTERNAL_ERROR | 服务器内部错误 |
数据模型
User (用户)
{
id: string; // UUID
nickname: string | null; // 昵称
avatarUrl: string | null; // 头像URL
tier: 'free' | 'pro' | 'proplus'; // 会员等级
xpTotal: number; // 总经验值
streakDays: number; // 连续天数
heartsRemaining: number; // 剩余红心
banned: boolean; // 是否封禁
}
Question (题目)
{
id: string; // UUID
stem: Record<string, unknown>; // 题目内容(支持多语言)
contentType: 'text' | 'image' | 'video' | 'audio';
correctAnswer: string; // 正确答案
distractors: string[]; // 干扰项
categoryId: string; // 分类ID
difficulty: 1-5; // 难度等级
status: 'draft' | 'reviewing' | 'published' | 'archived';
}
Chapter (章节)
{
id: string; // UUID
categoryId: string; // 分类ID
title: string; // 章节标题
parentId: string | null; // 父章节ID
sortOrder: number; // 排序
questionsRequired: number; // 需要答题数
passThreshold: number; // 通过阈值
}