- 新增 batchUpdateStatus 通用方法,带状态流转校验和 BatchResult 报告 - 改造 batchPublish 使用新方法,返回成功/失败详情 - 新增 batchArchive 和 batch-delete 端点(软删除) - 使用 inArray 批量查询和更新,优化数据库往返 - 更新 API 文档,补充三个批量接口说明
1708 lines
25 KiB
Markdown
1708 lines
25 KiB
Markdown
# 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 (可选)
|
||
|
||
**响应**:
|
||
```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` 格式。
|
||
|
||
---
|
||
|
||
### 分类管理
|
||
|
||
#### 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 | 请求参数验证失败 |
|
||
| 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; // 通过阈值
|
||
}
|
||
```
|