duoqi-api/docs/api-reference.md
Wang Zhuoxuan db2f3af8a3 feat: 完善题目列表查询接口,支持搜索、多维筛选和排序
- 新增关键词搜索(同时匹配题干 stem 和选项 distractors)
- 新增按难度(difficulty)、来源(source)筛选
- 新增动态排序:支持 createdAt/updatedAt/difficulty,可选 asc/desc
- 路由层增加 sortBy/sortOrder 白名单校验
2026-04-12 00:04:11 +08:00

1849 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Duoqi API Reference
> 多奇服务端 API 接口文档
> Base URL: `http://<host>:3000/v1`
## 目录
- [通用约定](#通用约定)
- [客户端 API](#客户端-api)
- [健康检查](#健康检查)
- [认证](#认证)
- [答题](#答题)
- [进度](#进度)
- [游戏化](#游戏化)
- [支付](#支付)
- [管理端 API](#管理端-api)
- [管理端认证](#管理端认证)
- [管理员管理](#管理员管理)
- [题目管理](#题目管理)
- [分类管理](#分类管理)
- [知识点卡片](#知识点卡片)
- [技能树管理](#技能树管理)
- [用户管理](#用户管理)
- [统计数据](#统计数据)
- [反馈管理](#反馈管理)
---
## 通用约定
### 认证方式
| 类型 | 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/*` (向后兼容) |
### 统一响应格式
```typescript
// 成功响应
{
"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
健康检查端点,用于服务可用性探测。
**认证**: 无
**请求**: 无
**响应**:
```json
{
"success": true,
"data": {
"status": "ok",
"timestamp": "2026-04-10T12:00:00.000Z"
},
"error": null
}
```
---
### 认证
#### POST /auth/guest
游客登录,通过设备 ID 创建或获取用户账号。
**认证**: 无
**限流**: 10 次/分钟
**请求体**:
```json
{
"deviceId": "string (必填)"
}
```
**响应**:
```json
{
"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 次/分钟
**请求体**:
```json
{
"authorizationCode": "string (必填)"
}
```
**响应**:
```json
{
"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 次/分钟
**请求体**:
```json
{
"refreshToken": "string (必填)"
}
```
**响应**:
```json
{
"success": true,
"data": {
"accessToken": "new_jwt_token",
"refreshToken": "new_refresh_token"
},
"error": null
}
```
---
#### GET /auth/me
获取当前用户信息。
**认证**: JWT
**响应**:
```json
{
"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
**响应**:
```json
{
"success": true,
"data": [
{
"id": "uuid",
"name": "历史",
"slug": "history",
"parentId": null
}
],
"error": null
}
```
---
#### GET /quiz/categories/:id/chapters
获取指定分类下的章节列表。
**认证**: JWT
**路径参数**:
- `id`: 分类 ID
**响应**:
```json
{
"success": true,
"data": [
{
"id": "uuid",
"categoryId": "uuid",
"title": "第一章",
"parentId": null,
"sortOrder": 1
}
],
"error": null
}
```
---
#### GET /quiz/chapters/:id/questions
获取章节下的题目(包含用户答题状态)。
**认证**: JWT
**路径参数**:
- `id`: 章节 ID
**响应**:
```json
{
"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
**请求体**:
```json
{
"questionId": "uuid (必填)",
"selectedAnswer": "string (必填)",
"timeMs": 1500
}
```
**响应**:
```json
{
"success": true,
"data": {
"correct": true,
"correctAnswer": "B",
"xpEarned": 10,
"streakBonus": 0,
"chapterCompleted": false
},
"error": null
}
```
---
#### POST /quiz/rate
评价题目质量。
**认证**: JWT
**请求体**:
```json
{
"questionId": "uuid (必填)",
"rating": "good | bad"
}
```
**响应**:
```json
{
"success": true,
"data": null,
"error": null
}
```
---
### 进度
#### GET /progress/dashboard
获取用户进度概览。
**认证**: JWT
**响应**:
```json
{
"success": true,
"data": {
"xpTotal": 150,
"streakDays": 3,
"heartsRemaining": 5,
"dailyXpEarned": 20,
"dailyXpGoal": 50,
"categoriesCompleted": 1,
"totalCategories": 5
},
"error": null
}
```
---
#### GET /progress/streak
获取连胜信息。
**认证**: JWT
**响应**:
```json
{
"success": true,
"data": {
"currentStreak": 3,
"longestStreak": 7,
"lastActiveDate": "2026-04-10"
},
"error": null
}
```
---
#### GET /progress/hearts
获取红心信息。
**认证**: JWT
**响应**:
```json
{
"success": true,
"data": {
"remaining": 5,
"max": 5,
"nextRestoreAt": "2026-04-10T13:00:00.000Z"
},
"error": null
}
```
---
#### POST /progress/hearts/restore
恢复红心。
**认证**: JWT
**请求体**:
```json
{
"method": "ad | wait | upgrade"
}
```
**响应**:
```json
{
"success": true,
"data": {
"remaining": 5,
"restored": 1
},
"error": null
}
```
---
#### GET /progress/chapters
获取所有章节进度。
**认证**: JWT
**响应**:
```json
{
"success": true,
"data": [
{
"chapterId": "uuid",
"title": "第一章",
"completedQuestions": 3,
"totalQuestions": 5,
"passed": false,
"passedAt": null
}
],
"error": null
}
```
---
#### POST /feedback
提交用户反馈。
**认证**: JWT
**请求体**:
```json
{
"content": "string (必填, 1-2000字符)",
"contact": "string (可选, 最多255字符)",
"pageContext": "string (可选, 最多200字符)"
}
```
**响应**:
```json
{
"success": true,
"data": null,
"error": null
}
```
---
### 游戏化
#### GET /leaderboard
获取排行榜。
**认证**: JWT
**查询参数**:
- `tier`: "free" | "pro" | "proplus" (可选)
- `page`: 页码 (默认: 1)
- `limit`: 每页数量 (默认: 20)
**响应**:
```json
{
"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
**响应**:
```json
{
"success": true,
"data": {
"rank": 15,
"xpTotal": 1500
},
"error": null
}
```
---
#### GET /achievements
获取成就列表。
**认证**: JWT
**响应**:
```json
{
"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
**响应**:
```json
{
"success": true,
"data": {
"newlyUnlocked": [
{
"id": "uuid",
"code": "streak_7",
"name": "连胜达人",
"description": "连续7天活跃"
}
]
},
"error": null
}
```
---
### 支付
#### POST /payment/verify-huawei
验证华为 IAP 收据并激活订阅。
**认证**: JWT
**请求体**:
```json
{
"purchaseToken": "string (必填)",
"productId": "string (必填)",
"tier": "pro | proplus"
}
```
**响应**:
```json
{
"success": true,
"data": {
"tier": "pro",
"provider": "huawei",
"active": true,
"expiresAt": "2026-05-10T00:00:00.000Z"
},
"error": null
}
```
---
#### GET /payment/subscription
获取当前订阅状态。
**认证**: JWT
**响应**:
```json
{
"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
管理员用户名密码登录。
**认证**: 无
**请求体**:
```json
{
"username": "string (必填)",
"password": "string (必填, 最少8字符)"
}
```
**响应**:
```json
{
"success": true,
"data": {
"accessToken": "jwt_token (1h有效)",
"refreshToken": "jwt_token (30d有效)",
"admin": {
"id": "uuid",
"username": "admin",
"role": "super_admin"
}
},
"error": null
}
```
**错误 (401)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid username or password"
}
}
```
---
#### POST /admin/auth
管理端 Token 认证(向后兼容,推荐使用 `/admin/auth/login`)。
**认证**: 无
**请求体**:
```json
{
"token": "string (必填)"
}
```
**响应**:
```json
{
"success": true,
"data": {
"authenticated": true
},
"error": null
}
```
**错误 (401)**:
```json
{
"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 (可选,按状态筛选)
**响应**:
```json
{
"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
**响应**:
```json
{
"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)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "NOT_FOUND",
"message": "Admin user not found"
}
}
```
---
#### POST /admin/admins
创建新管理员super_admin 专属)。
**认证**: Admin JWT (super_admin)
**请求体**:
```json
{
"username": "string (必填, 3-50字符)",
"password": "string (必填, 8-128字符)",
"role": "admin | super_admin (必填)"
}
```
**响应**:
```json
{
"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)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "FORBIDDEN",
"message": "Super admin privileges required"
}
}
```
**错误 (400)**:
```json
{
"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
**请求体**:
```json
{
"username": "string (可选, 3-50字符)",
"role": "admin | super_admin (可选)",
"isActive": 0 | 1 (可选)"
}
```
**响应**:
```json
{
"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
**响应**:
```json
{
"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
**响应**:
```json
{
"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 (可选)
- `keyword`: 关键词搜索,匹配题干(stem)和选项(distractors)内容 (可选)
- `difficulty`: 难度值 1-5精确匹配 (可选)
- `source`: system | ugc按来源筛选 (可选)
- `sortBy`: 排序字段createdAt | updatedAt | difficulty (默认: createdAt)
- `sortOrder`: asc | desc (默认: desc)
**响应**:
```json
{
"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
**响应**:
```json
{
"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
**请求体**:
```json
{
"stem": { "text": "题目内容" },
"contentType": "text | image | video | audio",
"correctAnswer": "B (必填)",
"distractors": ["A", "C", "D"],
"categoryId": "uuid (必填)",
"difficulty": 3,
"knowledgeCard": {
"summary": "知识点摘要 (必填)",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
}
```
**响应**:
```json
{
"success": true,
"data": {
"id": "uuid",
"status": "draft"
},
"error": null
}
```
---
#### PUT /admin/questions/:id
更新题目。
**认证**: Admin Token
**路径参数**:
- `id`: 题目 ID
**请求体**:
```json
{
"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
**响应**:
```json
{
"success": true,
"data": null,
"error": null
}
```
---
#### PATCH /admin/questions/:id/status
变更题目状态(带流转校验)。
**认证**: Admin Token
**路径参数**:
- `id`: 题目 ID
**请求体**:
```json
{
"status": "draft | reviewing | published | archived (必填)"
}
```
**允许的状态流转**:
| 当前状态 | 可变更到 |
|----------|----------|
| draft | reviewing, archived |
| reviewing | published, draft, archived |
| published | archived |
| archived | draft |
**响应**:
```json
{
"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)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "NOT_FOUND",
"message": "题目不存在"
}
}
```
**错误 (400)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "INVALID_STATUS_TRANSITION",
"message": "不允许从 published 变更为 reviewing"
}
}
```
---
#### POST /admin/questions/batch-publish
批量发布题目(带状态流转校验,仅 reviewing 状态可发布)。
**认证**: Admin Token
**请求体**:
```json
{
"ids": ["uuid1", "uuid2"]
}
```
**参数说明**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ids | string[] | 是 | 题目 ID 数组1-200 个,每个为合法 UUID |
**响应**:
```json
{
"success": true,
"data": {
"total": 3,
"succeeded": 2,
"failed": [
{ "id": "uuid3", "reason": "不允许从 draft 变更为 published" }
]
},
"error": null
}
```
**data 字段说明**:
| 字段 | 类型 | 说明 |
|------|------|------|
| total | number | 提交的 ID 总数 |
| succeeded | number | 成功更新的数量 |
| failed | array | 失败记录列表(包含 id 和 reason |
---
#### POST /admin/questions/batch-archive
批量归档题目带状态流转校验draft/reviewing/published 状态可归档)。
**认证**: Admin Token
**请求体**:
```json
{
"ids": ["uuid1", "uuid2"]
}
```
**参数说明**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ids | string[] | 是 | 题目 ID 数组1-200 个,每个为合法 UUID |
**响应**: 与 batch-publish 相同的 `BatchResult` 格式。
---
#### POST /admin/questions/batch-delete
批量删除题目(软删除,等同于批量归档)。
**认证**: Admin Token
**请求体**:
```json
{
"ids": ["uuid1", "uuid2"]
}
```
**参数说明**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ids | string[] | 是 | 题目 ID 数组1-200 个,每个为合法 UUID |
**响应**: 与 batch-publish 相同的 `BatchResult` 格式。
---
#### POST /admin/questions/import
批量导入题目JSON 格式,全有或全无策略)。
**认证**: Admin Token
**请求体**:
```json
{
"questions": [
{
"stem": { "text": "题目内容" },
"contentType": "text | image | video | audio",
"correctAnswer": "正确答案 (必填)",
"distractors": ["干扰项1", "干扰项2"],
"categoryId": "分类ID (必填)",
"difficulty": 3,
"knowledgeCard": {
"summary": "知识点摘要 (必填)",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
}
]
}
```
**参数说明**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| questions | array | 是 | 题目数组1-200 条 |
| questions[].stem | object | 是 | 题干,至少包含 text 字段 |
| questions[].contentType | string | 是 | 内容类型text/image/video/audio |
| questions[].correctAnswer | string | 是 | 正确答案至少1字符 |
| questions[].distractors | string[] | 是 | 干扰项至少2个 |
| questions[].categoryId | string | 是 | 所属分类 ID |
| questions[].difficulty | number | 否 | 难度 1-5 |
| questions[].knowledgeCard | object | 否 | 知识点卡片 |
| questions[].knowledgeCard.summary | string | 是* | 知识点摘要 |
| questions[].knowledgeCard.deepDive | string | 否 | 深入解析 |
| questions[].knowledgeCard.sourceRef | string | 否 | 来源引用 |
**成功响应**:
```json
{
"success": true,
"data": {
"total": 10,
"succeeded": 10,
"ids": ["uuid1", "uuid2", "..."]
},
"error": null
}
```
**校验失败响应 (400)**:
```json
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_FAILED",
"message": "部分题目校验失败",
"details": [
{ "index": 2, "errors": ["distractors: 必须包含至少2个元素"] },
{ "index": 5, "errors": ["categoryId: 必填字段"] }
]
}
}
```
**说明**:
- 导入的题目默认状态为 `draft`
- 全有或全无:任一条目校验失败则全部不导入
- 所有条目先校验完毕,再统一报告错误,最后才执行事务插入
- 校验包含 categoryId 外键存在性检查:不存在的分类 ID 会触发 `VALIDATION_FAILED` 错误
---
#### POST /admin/questions/import-csv
批量导入题目CSV 格式,全有或全无策略)。
**认证**: Admin Token
**Content-Type**: `text/plain`
**请求体**: CSV 文本,首行为表头
**CSV 表头(固定列顺序)**:
```
categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2,distractor3,distractor4,distractor5,cardSummary,cardDeepDive,cardSourceRef
```
**CSV 示例**:
```csv
categoryId,contentType,difficulty,stemText,correctAnswer,distractor1,distractor2,distractor3,distractor4,distractor5,cardSummary,cardDeepDive,cardSourceRef
history,text,1,秦始皇统一六国是在哪一年?,公元前221年,公元前206年,公元前256年,公元前230年,,,秦始皇嬴政于公元前221年完成统一,建立了中国历史上第一个大一统王朝,《史记·秦始皇本纪》
history,text,2,被称为'诗仙'的唐代诗人是谁?,李白,杜甫,白居易,王维,,李白是唐代最伟大的浪漫主义诗人,,
```
**列说明**:
| 列名 | 必填 | 说明 |
|------|------|------|
| categoryId | 是 | 分类 ID |
| contentType | 是 | text/image/video/audio |
| difficulty | 否 | 难度 1-5留空则不设置 |
| stemText | 是 | 题目文本 |
| correctAnswer | 是 | 正确答案 |
| distractor1-5 | 至少填2个 | 干扰项,留空的列会被忽略 |
| cardSummary | 否 | 知识点摘要(填则整行知识卡片必填 summary |
| cardDeepDive | 否 | 深入解析 |
| cardSourceRef | 否 | 来源引用 |
**成功/失败响应**: 与 JSON 导入相同格式。
**说明**:
- CSV 字段可用双引号包裹,支持字段内逗号和换行
- 字段内的双引号用 `""` 表示
- categoryId 外键存在性校验与 JSON 导入一致
- 单次导入上限 200 条,超出返回 `VALIDATION_ERROR`
**额外错误**:
```json
{
"success": false,
"data": null,
"error": {
"code": "CSV_PARSE_ERROR",
"message": "CSV 表头列数应为 13实际 10"
}
}
```
---
#### GET /admin/categories
获取分类列表。
**认证**: Admin Token
**查询参数**:
- `page`: 页码 (默认: 1, 必须 ≥ 1)
- `limit`: 每页数量 (默认: 20, 范围: 1-50)
**响应**:
```json
{
"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
**请求体**:
```json
{
"id": "uuid (必填)",
"name": "分类名称 (必填)",
"slug": "分类slug (必填)",
"parentId": "uuid",
"sortOrder": 1
}
```
---
#### PUT /admin/categories/:id
更新分类。
**认证**: Admin Token
**请求体**:
```json
{
"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
**请求体**:
```json
{
"summary": "摘要 (必填)",
"deepDive": "深入解析",
"sourceRef": "来源引用"
}
```
---
### 技能树管理
#### GET /admin/skill-tree
获取章节列表。
**认证**: Admin Token
**查询参数**:
- `categoryId`: 分类 ID (可选)
---
#### POST /admin/skill-tree
创建章节。
**认证**: Admin Token
**请求体**:
```json
{
"categoryId": "uuid (必填)",
"title": "章节标题 (必填)",
"parentId": "uuid",
"sortOrder": 1,
"questionsRequired": 5,
"passThreshold": 3
}
```
---
#### PUT /admin/skill-tree/:id
更新章节。
**认证**: Admin Token
**请求体**:
```json
{
"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)
**响应**:
```json
{
"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
**响应**:
```json
{
"success": true,
"data": {
"totalUsers": 1000,
"activeUsers": 150,
"totalQuestions": 500,
"publishedQuestions": 450,
"averageXp": 200
},
"error": null
}
```
---
### 反馈管理
#### GET /admin/feedback
获取用户反馈列表。
**认证**: Admin Token
**查询参数**:
- `page`: 页码 (默认: 1)
- `limit`: 每页数量 (默认: 20)
**响应**:
```json
{
"success": true,
"data": [
{
"id": "uuid",
"userId": "uuid",
"content": "反馈内容",
"contact": "联系方式",
"pageContext": "页面上下文",
"createdAt": "2026-04-10T10:00:00.000Z"
}
],
"pagination": { ... },
"error": null
}
```
---
## 附录
### 错误代码
| 代码 | 说明 |
|------|------|
| VALIDATION_ERROR | 请求参数验证失败 |
| VALIDATION_FAILED | 批量导入中部分题目校验失败 |
| CSV_PARSE_ERROR | CSV 解析失败(格式或表头不匹配) |
| UNAUTHORIZED | 未认证或认证失败 |
| FORBIDDEN | 权限不足(需要 super_admin |
| NOT_FOUND | 资源不存在 |
| INVALID_STATUS_TRANSITION | 题目状态流转不合法 |
| INVALID_RECEIPT | 支付收据验证失败 |
| NOT_IMPLEMENTED | 功能未实现 |
| INTERNAL_ERROR | 服务器内部错误 |
### 数据模型
#### User (用户)
```typescript
{
id: string; // UUID
nickname: string | null; // 昵称
avatarUrl: string | null; // 头像URL
tier: 'free' | 'pro' | 'proplus'; // 会员等级
xpTotal: number; // 总经验值
streakDays: number; // 连续天数
heartsRemaining: number; // 剩余红心
banned: boolean; // 是否封禁
}
```
#### Question (题目)
```typescript
{
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 (章节)
```typescript
{
id: string; // UUID
categoryId: string; // 分类ID
title: string; // 章节标题
parentId: string | null; // 父章节ID
sortOrder: number; // 排序
questionsRequired: number; // 需要答题数
passThreshold: number; // 通过阈值
}
```