duoqi-api/docs/api-reference.md
Wang Zhuoxuan 3991a02a8c feat: 添加管理员用户名密码登录功能
新增 /v1/admin/auth/login 接口,支持用户名密码登录获取 JWT Token。
- 添加 admin_users 表存储管理员账号和哈希密码
- 使用 bcryptjs 进行密码哈希(cost=10)
- JWT Token 认证优先,保留 ADMIN_TOKEN 作为向后兼容
- 记录登录审计日志到 admin_audit_log
- 种子数据创建默认管理员(username: admin, password: admin123)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 15:25:31 +08:00

18 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"
  }
}

题目管理

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
}

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 未认证或认证失败
NOT_FOUND 资源不存在
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;   // 通过阈值
}