docs: iOS integration audit report + 5 CAPI docs + nginx fix
- iOS integration readiness audit (blocker list, module map, deployment check) - CAPI contract for iOS (15 modules, 80+ endpoints) - CAPI error codes and status enums - iOS auth flow, file upload import flow, learning review flow - Web product page nginx fix (reenable longde.cloud) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
89e7e580ff
commit
0eb5f53873
240
技术设计/capi-contract-for-ios.md
Normal file
240
技术设计/capi-contract-for-ios.md
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
# iOS CAPI 接口契约
|
||||||
|
|
||||||
|
> 版本:0.1.0 | 更新:2026-05-24 | 基础 URL:`https://api.longde.cloud`
|
||||||
|
|
||||||
|
## 通用约定
|
||||||
|
|
||||||
|
### 认证
|
||||||
|
|
||||||
|
所有 `/api/*` 端点需要 JWT Bearer Token:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer <access_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Token 通过 Apple 登录获取,有效期 1 小时,用 refresh token 续期。
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
// 成功
|
||||||
|
{ "success": true, "data": <资源>, "timestamp": "2026-05-24T..." }
|
||||||
|
|
||||||
|
// 失败
|
||||||
|
{ "success": false, "statusCode": 401, "message": "请先登录" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分页
|
||||||
|
|
||||||
|
请求参数:`?page=1&limit=20`(limit 上限 100)
|
||||||
|
|
||||||
|
响应格式因端点而异,无统一分页包装。常见结构为数据数组 + 通过 Data 字段间接判断。
|
||||||
|
|
||||||
|
### 错误码
|
||||||
|
|
||||||
|
| HTTP | 含义 |
|
||||||
|
|------|------|
|
||||||
|
| 200 | 成功 |
|
||||||
|
| 201 | 创建成功 |
|
||||||
|
| 400 | 请求参数错误(DTO 校验失败) |
|
||||||
|
| 401 | 未登录 / Token 过期 / 账号禁用 |
|
||||||
|
| 403 | 无权限 |
|
||||||
|
| 404 | 资源不存在 |
|
||||||
|
| 500 | 服务器内部错误 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 接口清单
|
||||||
|
|
||||||
|
### 1. 认证 `/api/auth`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body |
|
||||||
|
|------|------|------|------|
|
||||||
|
| POST | `/api/auth/apple` | Apple 登录 | `{ identityToken, fullName?, email? }` |
|
||||||
|
| POST | `/api/auth/refresh` | 刷新 Token | `{ refreshToken }` |
|
||||||
|
| POST | `/api/auth/logout` | 登出 | `{ refreshToken }` |
|
||||||
|
|
||||||
|
**Apple 登录响应:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"accessToken": "eyJ...",
|
||||||
|
"refreshToken": "abc123...",
|
||||||
|
"user": {
|
||||||
|
"id": "cuid",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"nickname": "用户",
|
||||||
|
"avatarUrl": null,
|
||||||
|
"role": "USER",
|
||||||
|
"status": "active",
|
||||||
|
"onboardingCompleted": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 用户 `/api/users`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/users/me` | 当前用户信息 |
|
||||||
|
| PATCH | `/api/users/me` | 更新用户信息 |
|
||||||
|
| GET | `/api/users/me/profile` | 用户资料 |
|
||||||
|
| PATCH | `/api/users/me/profile` | 更新资料 |
|
||||||
|
| PATCH | `/api/users/me/preferences` | 偏好设置 |
|
||||||
|
| GET | `/api/users/me/membership` | 会员信息 |
|
||||||
|
| DELETE | `/api/users/me` | 注销账号 |
|
||||||
|
|
||||||
|
### 3. 文件上传 `/api/files`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body |
|
||||||
|
|------|------|------|------|
|
||||||
|
| POST | `/api/files/upload-url` | 获取预签名上传 URL | `{ filename, mimeType, sizeBytes }` |
|
||||||
|
| POST | `/api/files/complete` | 确认上传完成 | `{ objectKey, checksum? }` |
|
||||||
|
| GET | `/api/files/:id` | 获取文件信息 |
|
||||||
|
| DELETE | `/api/files/:id` | 删除文件 |
|
||||||
|
|
||||||
|
**上传流程:**
|
||||||
|
1. `POST /api/files/upload-url` → 获取 `{ uploadUrl, objectKey }`
|
||||||
|
2. iOS 直接 `PUT <uploadUrl>` 到 COS(腾讯云对象存储)
|
||||||
|
3. `POST /api/files/complete` → 确认完成,获取 `fileId`
|
||||||
|
|
||||||
|
### 4. 知识库 `/api/knowledge-bases`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/api/knowledge-bases` | 创建知识库 |
|
||||||
|
| GET | `/api/knowledge-bases` | 知识库列表 |
|
||||||
|
| GET | `/api/knowledge-bases/:id` | 知识库详情 |
|
||||||
|
| PATCH | `/api/knowledge-bases/:id` | 更新 |
|
||||||
|
| DELETE | `/api/knowledge-bases/:id` | 删除 |
|
||||||
|
|
||||||
|
### 5. 知识源 `/api/knowledge-bases/:kbId/sources`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body DTO |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| POST | `/api/knowledge-bases/:kbId/sources` | 添加资料来源 | `AddSourceDto` |
|
||||||
|
| GET | `/api/knowledge-bases/:kbId/sources` | 资料列表 | — |
|
||||||
|
| GET | `/api/knowledge-bases/:kbId/sources/:id` | 资料详情 | — |
|
||||||
|
| DELETE | `/api/knowledge-bases/:kbId/sources/:id` | 删除 | — |
|
||||||
|
|
||||||
|
**AddSourceDto:** `{ fileId?, type?, title?, originalFilename?, mimeType?, sizeBytes?, originalObjectKey? }`
|
||||||
|
- `type` 枚举:`file` | `link` | `manual` | `paste`
|
||||||
|
|
||||||
|
### 6. 文档导入 `/api/imports`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body DTO |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| POST | `/api/imports` | 创建导入任务 | `CreateImportDto` |
|
||||||
|
| GET | `/api/imports/:id/status` | 查询导入状态 | — |
|
||||||
|
|
||||||
|
**CreateImportDto:** `{ userId?, knowledgeBaseId?, fileName?, sourceType?, rawText? }`
|
||||||
|
|
||||||
|
**状态响应:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "import-001",
|
||||||
|
"fileName": "笔记.pdf",
|
||||||
|
"status": "QUEUED",
|
||||||
|
"progress": 0,
|
||||||
|
"message": "任务已加入队列"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 学习会话 `/api/learning-sessions`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body DTO |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| POST | `/api/learning-sessions` | 开始学习 | `StartSessionDto` |
|
||||||
|
| POST | `/api/learning-sessions/:id/end` | 结束会话 | — |
|
||||||
|
| GET | `/api/learning-sessions` | 会话列表 | Query: `?page=1&limit=20` |
|
||||||
|
|
||||||
|
**StartSessionDto:** `{ knowledgeItemId?, knowledgeBaseId?, mode? }`
|
||||||
|
- `mode` 枚举:`active_recall` | `feynman` | `review` | `reading`
|
||||||
|
|
||||||
|
### 8. 主动回忆 `/api/active-recalls`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/active-recalls` | 获取题目列表 |
|
||||||
|
| POST | `/api/active-recalls/:id/submit` | 提交回答 |
|
||||||
|
|
||||||
|
### 9. AI 分析 `/api/ai-analysis`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/api/ai-analysis` | 发起主动回忆分析 |
|
||||||
|
| POST | `/api/ai-analysis/feynman` | 发起费曼评估 |
|
||||||
|
| GET | `/api/ai-analysis/jobs/:id` | 查询作业状态 |
|
||||||
|
| GET | `/api/ai-analysis/:id` | 获取分析结果 |
|
||||||
|
|
||||||
|
### 10. 复习 `/api/reviews`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | Body |
|
||||||
|
|------|------|------|------|
|
||||||
|
| GET | `/api/reviews/due` | 今日待复习 | — |
|
||||||
|
| POST | `/api/reviews/:id/submit` | 提交复习结果 | `SubmitReviewDto` |
|
||||||
|
| POST | `/api/reviews/generate-cards` | 生成卡片 | — |
|
||||||
|
|
||||||
|
### 11. 薄弱项 `/api/focus-items`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/focus-items` | 薄弱项列表 |
|
||||||
|
| POST | `/api/focus-items` | 创建 |
|
||||||
|
| PATCH | `/api/focus-items/:id` | 更新 |
|
||||||
|
| POST | `/api/focus-items/:id/complete` | 标记完成 |
|
||||||
|
|
||||||
|
### 12. 学习活动 `/api/activity`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/activity/heatmap` | 学习热力图 |
|
||||||
|
| GET | `/api/activity/summary` | 活动摘要 |
|
||||||
|
| GET | `/api/activity/trend` | 学习趋势 |
|
||||||
|
| GET | `/api/activity/streak` | 连续打卡天数 |
|
||||||
|
| GET | `/api/activity/recommendations` | 学习推荐 |
|
||||||
|
|
||||||
|
### 13. RAG 对话 `/api/rag-chat`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/api/rag-chat/sessions` | 创建对话 |
|
||||||
|
| GET | `/api/rag-chat/sessions` | 对话列表 |
|
||||||
|
| POST | `/api/rag-chat/sessions/:id/messages` | 发送消息 |
|
||||||
|
| GET | `/api/rag-chat/sessions/:id/messages` | 消息历史 |
|
||||||
|
| DELETE | `/api/rag-chat/sessions/:id` | 删除对话 |
|
||||||
|
|
||||||
|
### 14. 通知 `/api/notifications`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/notifications` | 通知列表 |
|
||||||
|
| POST | `/api/notifications/:id/read` | 标记已读 |
|
||||||
|
| POST | `/api/notifications/read-all` | 全部已读 |
|
||||||
|
| GET/PATCH | `/api/notifications/preferences` | 通知偏好 |
|
||||||
|
| GET/POST/DELETE | `/api/notifications/push-tokens` | Push Token 管理 |
|
||||||
|
|
||||||
|
### 15. 其他
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/api/feedback` | 提交反馈(type: bug/feature/general) |
|
||||||
|
| GET | `/api/workspace/dashboard` | 工作台首页 |
|
||||||
|
| GET | `/api/workspace/recent` | 最近记录 |
|
||||||
|
| POST | `/api/workspace/favorites` | 收藏 |
|
||||||
|
| GET | `/api/workspace/search` | 搜索 |
|
||||||
|
| GET | `/api/workspace/search-history` | 搜索历史 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 全局请求头
|
||||||
|
|
||||||
|
| Header | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `Authorization` | `Bearer <access_token>` |
|
||||||
|
| `Content-Type` | `application/json` |
|
||||||
|
|
||||||
|
## 限制
|
||||||
|
|
||||||
|
- 请求体大小:10MB(全局 pipe 限制)
|
||||||
|
- 文件上传大小:20MB(单个文件)
|
||||||
|
- Token 有效期:1 小时(access),7 天(refresh)
|
||||||
122
技术设计/capi-error-codes-and-status-enums.md
Normal file
122
技术设计/capi-error-codes-and-status-enums.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# CAPI 错误码与状态枚举
|
||||||
|
|
||||||
|
> 版本:0.1.0 | 更新:2026-05-24
|
||||||
|
|
||||||
|
## HTTP 错误码
|
||||||
|
|
||||||
|
| HTTP | message | 触发场景 |
|
||||||
|
|------|---------|----------|
|
||||||
|
| 200 | — | 成功 |
|
||||||
|
| 201 | — | 创建成功 |
|
||||||
|
| 400 | — | 请求参数校验失败(DTO validation) |
|
||||||
|
| 401 | "请先登录" | 未携带 Token |
|
||||||
|
| 401 | "登录已过期,请重新登录" | Token 过期或无效 |
|
||||||
|
| 401 | "账号已被禁用" | 用户 status ≠ active |
|
||||||
|
| 401 | "账号不存在或已注销" | 用户不存在或 deletedAt 不为空 |
|
||||||
|
| 401 | "无效的访问令牌" | 管理员 Token 访问 CAPI |
|
||||||
|
| 401 | "Apple 登录未配置,请联系管理员" | Apple 登录未配置 |
|
||||||
|
| 401 | "刷新令牌无效或已过期" | Refresh Token 无效 |
|
||||||
|
| 401 | "账号已注销" | 刷新时发现用户已注销 |
|
||||||
|
| 403 | "dev-login is disabled in production" | 生产环境尝试 dev-login |
|
||||||
|
| 404 | "资源不存在" | 资源未找到 |
|
||||||
|
| 500 | — | 服务器内部错误 |
|
||||||
|
|
||||||
|
## 模型状态枚举
|
||||||
|
|
||||||
|
### User
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `disabled` |
|
||||||
|
| `role` | `USER`, `ADMIN` |
|
||||||
|
|
||||||
|
### KnowledgeBase
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `archived`, `deleted` |
|
||||||
|
|
||||||
|
### KnowledgeItem
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `archived` |
|
||||||
|
| `itemType` | `note`, `flashcard`, `concept` |
|
||||||
|
|
||||||
|
### KnowledgeSource
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `parseStatus` | `pending`, `parsing`, `completed`, `failed` |
|
||||||
|
| `indexStatus` | `pending`, `indexing`, `completed`, `failed` |
|
||||||
|
| `learningStatus` | `pending`, `learning`, `completed`, `skipped` |
|
||||||
|
| `type` | `file`, `link`, `manual`, `paste` |
|
||||||
|
|
||||||
|
### DocumentImport
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `QUEUED`, `CLAIMED`, `DOWNLOADING`, `PARSING`, `OCR_PROCESSING`, `VISION_PROCESSING`, `CLEANING`, `CHUNKING`, `EMBEDDING`, `INDEXING`, `GENERATING_CANDIDATES`, `COMPLETED`, `FAILED`, `FAILED_FINAL` |
|
||||||
|
|
||||||
|
### ImportCandidate
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `PENDING`, `ACCEPTED`, `REJECTED` |
|
||||||
|
|
||||||
|
### LearningSession
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `completed`, `cancelled` |
|
||||||
|
| `mode` | `active_recall`, `feynman`, `review`, `reading` |
|
||||||
|
|
||||||
|
### AiAnalysisJob
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `pending`, `processing`, `completed`, `failed` |
|
||||||
|
| `jobType` | `active_recall_analysis`, `feynman_evaluation` |
|
||||||
|
|
||||||
|
### ReviewCard
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `completed`, `archived` |
|
||||||
|
| `scheduleState` | `new`, `learning`, `review`, `relearning` |
|
||||||
|
| `difficulty` | 数值(0.0–1.0,SM-2 算法) |
|
||||||
|
|
||||||
|
### ReviewPlan
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `active`, `completed`, `skipped` |
|
||||||
|
|
||||||
|
### FocusItem
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `status` | `open`, `completed` |
|
||||||
|
| `priority` | `high`, `normal`, `low` |
|
||||||
|
|
||||||
|
### Feedback
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `type` | `bug`, `feature`, `general` |
|
||||||
|
| `status` | `pending`, `reviewed`, `resolved`, `closed` |
|
||||||
|
|
||||||
|
### Notification
|
||||||
|
| 字段 | 可选值 |
|
||||||
|
|------|--------|
|
||||||
|
| `scope` | `user`, `admin` |
|
||||||
|
|
||||||
|
## iOS 处理建议
|
||||||
|
|
||||||
|
### 状态优先级
|
||||||
|
|
||||||
|
1. **UI 关心的状态子集:**
|
||||||
|
|
||||||
|
| 模块 | 展示用状态 | 说明 |
|
||||||
|
|------|-----------|------|
|
||||||
|
| DocumentImport | `QUEUED` → `PROCESSING`(any intermediate) → `COMPLETED` / `FAILED` | 中间态统一显示为"处理中" |
|
||||||
|
| ReviewCard | `new` / `learning` / `review` / `relearning` | scheduleState 比 status 更有用 |
|
||||||
|
| FocusItem | `open` / `completed` | 简单二态 |
|
||||||
|
| LearningSession | `active` / `completed` | — |
|
||||||
|
|
||||||
|
2. **轮询策略:**
|
||||||
|
- DocumentImport 状态:初始 2s,后续 5s
|
||||||
|
- AI 分析作业:2s 间隔
|
||||||
|
- 最多轮询 5 分钟,超时提示用户
|
||||||
|
|
||||||
|
3. **错误本地化:**
|
||||||
|
- 后端返回中文错误消息
|
||||||
|
- 建议 iOS 端做本地化映射(中 → 英/其他语言)
|
||||||
468
技术设计/iOS对接可行性审计报告.md
Normal file
468
技术设计/iOS对接可行性审计报告.md
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
# iOS 端对接可行性审计报告
|
||||||
|
|
||||||
|
> 审计日期:2026-05-24 | 范围:api-server (NestJS 11) | 方式:只读排查,未修改任何代码
|
||||||
|
>
|
||||||
|
> 审计人:Claude Code | 服务端:蜂驰云 8核32G (120.53.227.155) + 轻量云 4核4G (10.2.0.7)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A. 总体结论:有条件可以 ✅
|
||||||
|
|
||||||
|
**在 P0 阻塞项解决之前,不能发布 iOS 对接任务。** P0 涉及 2 个安全/功能严重问题,预计修复工作量 1-2 天。P1 高风险项建议在 iOS 开发第一周内修复。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. 阻塞项清单
|
||||||
|
|
||||||
|
### 🔴 P0 阻塞 — 不解决不能开始 iOS 对接
|
||||||
|
|
||||||
|
| # | 问题 | 位置 | 风险 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| 1 | **Apple 登录是假的** — `APPLE_BUNDLE_ID` 未设,退回 mock 模式,任何长度≥4 的字符串都当合法 token | `src/modules/auth/apple-auth.service.ts:28-30`,生产 env 缺 `APPLE_BUNDLE_ID` | iOS 用户可以用任意字符串登录,伪造 Apple ID |
|
||||||
|
| 2 | **`/internal/*` 7 个端点零认证暴露公网** — 无 Guard、无 API Key、无 IP 白名单 | `src/common/guards/jwt-auth.guard.ts:31` 跳过 internal;`internal-rag.controller.ts` 有 `@Public()` 且无 `@UseGuards` | 攻击者可读取文档原文、写入 chunks/candidates、劫持导入作业 |
|
||||||
|
|
||||||
|
### 🟡 P1 高风险 — 可以开始,但必须尽快修(建议 iOS 开发第一周内)
|
||||||
|
|
||||||
|
| # | 问题 | 位置 | 风险 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| 3 | **用户 Token 不检查账号状态** — JwtAuthGuard 不解码查库,禁用/已删用户 Token 仍然有效 | `src/common/guards/jwt-auth.guard.ts:42-46` | 禁用用户仍可访问所有 API |
|
||||||
|
| 4 | **Refresh Token 不检查用户状态** — 刷新时不验证用户是否被禁用 | `src/modules/auth/auth.service.ts:125-128` | 禁用用户可以无限刷新 Token |
|
||||||
|
| 5 | **用户和管理员 JWT 共用同一密钥** — `ADMIN_JWT_ACCESS_SECRET` 未设,退回 `JWT_SECRET` | `src/config/jwt.config.ts:6`;生产 env 缺变量 | 降低 Token 隔离性,增加横向移动风险 |
|
||||||
|
| 6 | **请求 DTO 大量缺失** — iOS 核心流程(import、source、learning-session)用 `@Body() body: any` | `document-import.controller.ts:13`、`knowledge-source.controller.ts:18` 等 | iOS 可能发送错误字段,拿到 201 但后续失败 |
|
||||||
|
|
||||||
|
### 🟢 P2 优化项 — 不阻塞 iOS
|
||||||
|
|
||||||
|
| # | 问题 | 位置 |
|
||||||
|
|---|------|------|
|
||||||
|
| 7 | 无统一分页响应格式 — `PaginatedResponse<T>` 定义了但从未使用 | `src/common/types/index.ts:9-19` |
|
||||||
|
| 8 | Swagger 缺少 Response Schema — 所有 `@ApiResponse` 只有 description,无 typed schema | 全部 CAPI 控制器 |
|
||||||
|
| 9 | 4 个端点双重包装响应 — logout/feedback/reports/waitlist 返回 `{success, data}` 被 ResponseInterceptor 再包一层 | `auth.controller.ts:51`、`feedback.controller.ts:20`、`content-safety.controller.ts:27`、`waitlist.controller.ts:19` |
|
||||||
|
| 10 | Refresh Token 用 SHA-256 而非 bcrypt 哈希 | `src/modules/auth/token.service.ts:19,24` |
|
||||||
|
| 11 | CI/CD 中硬编码了生产数据库密码 | `.gitea/workflows/deploy.yml:8,40` |
|
||||||
|
| 12 | 4核4G 服务器上有旧 MySQL(35 表,dev 密码),但不影响生产 | `10.2.0.7` 上 `mysql-zhixi` 容器 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. 服务器与数据库部署
|
||||||
|
|
||||||
|
### 拓扑图
|
||||||
|
|
||||||
|
```
|
||||||
|
用户 (iOS App)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
api.longde.cloud (Nginx)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
蜂驰云 8核32G (120.53.227.155) ← 唯一生产服务器
|
||||||
|
├── zhixi-api.service NestJS API (端口 3000)
|
||||||
|
├── zhixi-worker.service BullMQ Worker
|
||||||
|
├── rag-worker.service Python RAG Worker (端口 8000)
|
||||||
|
├── Docker: mysql zhixi_prod (90 表) ← 唯一生产数据库
|
||||||
|
├── Docker: redis 缓存/队列/限流
|
||||||
|
├── Docker: qdrant 向量数据库
|
||||||
|
├── Docker: gitea-runner CI/CD Runner
|
||||||
|
└── Docker: nginx 反向代理
|
||||||
|
|
||||||
|
轻量云 4核4G (10.2.0.7) ← 辅助/开发残留
|
||||||
|
├── Docker: mysql-zhixi zhixi (35 表) ← 旧 dev 数据库,不服务生产
|
||||||
|
├── Docker: gitea Git 代码仓库
|
||||||
|
├── Docker: hermes-agent AI Agent
|
||||||
|
└── zhixi-worker.service 故障中 (auto-restart)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键结论
|
||||||
|
|
||||||
|
| 问题 | 结论 |
|
||||||
|
|------|------|
|
||||||
|
| 是否存在数据分裂风险? | **否。** 生产仅一套 MySQL `zhixi_prod`,运行在蜂驰云 |
|
||||||
|
| iOS 是否会连错数据库? | **否。** API 服务固定连接 `zhixi_prod`,通过 env 文件配置 |
|
||||||
|
| 4核4G 的 MySQL 服务生产流量吗? | **否。** 数据库名不同(`zhixi` vs `zhixi_prod`),仅 35 张旧表,密码是 dev 环境密码 |
|
||||||
|
| JWT 用户/管理员密钥隔离吗? | **否。** `ADMIN_JWT_ACCESS_SECRET` 未设,退回与用户相同的 `JWT_SECRET` |
|
||||||
|
|
||||||
|
### 生产环境配置概况
|
||||||
|
|
||||||
|
配置文件:`/opt/zhixi/env/.env.production`(28 行)
|
||||||
|
|
||||||
|
| 配置项 | 状态 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| DATABASE_URL | ✅ 已设 | `mysql://zhixi_user:***@127.0.0.1:3306/zhixi_prod` |
|
||||||
|
| REDIS_HOST/PORT/PASSWORD | ✅ 已设 | 127.0.0.1:6379 |
|
||||||
|
| QDRANT_URL | ✅ 已设 | http://127.0.0.1:6333 |
|
||||||
|
| JWT_SECRET | ✅ 已设 | 64 位 hex |
|
||||||
|
| ADMIN_JWT_ACCESS_SECRET | ❌ 未设 | 退回 JWT_SECRET |
|
||||||
|
| APPLE_BUNDLE_ID | ❌ 未设 | 导致 Apple 登录退回 mock |
|
||||||
|
| ENABLE_SWAGGER | ✅ true | 已恢复,Basic Auth 保护 |
|
||||||
|
| STORAGE_COS_* | ✅ 已设 | 腾讯云 COS (zhixi-1259685406, ap-beijing) |
|
||||||
|
| AI_PROVIDER / DEEPSEEK_API_KEY | ✅ 已设 | DeepSeek |
|
||||||
|
| SILICONFLOW_API_KEY | ✅ 已设 | SiliconFlow (备用) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. 各问题详细诊断
|
||||||
|
|
||||||
|
### P0-1: Apple 登录 Mock 模式
|
||||||
|
|
||||||
|
**文件:** `src/modules/auth/apple-auth.service.ts`
|
||||||
|
|
||||||
|
**问题代码(第 23-32 行):**
|
||||||
|
```typescript
|
||||||
|
async verifyIdentityToken(identityToken: string): Promise<...> {
|
||||||
|
if (!this.appleBundleId) {
|
||||||
|
return this.verifyMock(identityToken); // ← 生产走这分支
|
||||||
|
}
|
||||||
|
return this.verifyReal(identityToken);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mock 逻辑(第 34-47 行):**
|
||||||
|
```typescript
|
||||||
|
private verifyMock(identityToken: string): { appleUserId: string } {
|
||||||
|
if (!identityToken || identityToken.trim().length < 4) {
|
||||||
|
throw new UnauthorizedException('identityToken 无效');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
appleUserId: crypto.createHash('sha256')
|
||||||
|
.update(`apple-mock:${identityToken}`)
|
||||||
|
.digest('hex')
|
||||||
|
.slice(0, 64),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**真实验证逻辑(第 49-78 行)** — 已实现但未启用:
|
||||||
|
- 使用 `jose.jwtVerify` 验证 JWT 签名
|
||||||
|
- 通过 `createRemoteJWKSet` 获取 Apple 公钥 (`https://appleid.apple.com/auth/keys`)
|
||||||
|
- 验证 `issuer`(Apple)和 `audience`(Bundle ID)
|
||||||
|
|
||||||
|
**当前状态:** 配置(`src/config/apple.config.ts`)中 `bundleId` 默认为空字符串。生产 env 未设 `APPLE_BUNDLE_ID`。任何人发送任意 ≥4 字符的字符串即可登录。
|
||||||
|
|
||||||
|
**修复方式:**
|
||||||
|
1. 在 Apple Developer Console 获取应用的 Bundle ID
|
||||||
|
2. 在生产 env 添加 `APPLE_BUNDLE_ID=com.longde.zhixi`(或实际值)
|
||||||
|
3. 建议同时在代码层加保护:当 `appleBundleId` 为空时直接拒绝而非退回 mock
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P0-2: /internal/* 零认证暴露
|
||||||
|
|
||||||
|
**文件:** `src/common/guards/jwt-auth.guard.ts:31` + `src/modules/rag/internal-rag.controller.ts`
|
||||||
|
|
||||||
|
**JwtAuthGuard 跳过 internal 路由:**
|
||||||
|
```typescript
|
||||||
|
if (request.path.startsWith('/admin-api') || request.path.startsWith('/internal')) {
|
||||||
|
return true; // ← 完全放行,不做任何认证
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**控制器层:** `InternalRagController` 有 `@Public()` 装饰器,无 `@UseGuards()`。
|
||||||
|
|
||||||
|
**暴露的端点(全部无需认证,实测确认):**
|
||||||
|
|
||||||
|
| 方法 | 路径 | 功能 | 实测 HTTP 状态 |
|
||||||
|
|------|------|------|----------------|
|
||||||
|
| GET | `/internal/rag/jobs/next` | 获取下一个待处理导入作业(含文档原文) | 200 |
|
||||||
|
| GET | `/internal/rag/jobs/:id` | 获取指定作业详情(含原文+元数据) | 200 |
|
||||||
|
| POST | `/internal/rag/jobs/:id/claim` | 认领作业(任意 workerId) | 可访问 |
|
||||||
|
| POST | `/internal/rag/jobs/:id/heartbeat` | 发送心跳保持作业租约 | 可访问 |
|
||||||
|
| POST | `/internal/rag/jobs/:id/status` | 更新作业状态/进度/错误 | 可访问 |
|
||||||
|
| POST | `/internal/rag/chunks` | 批量写入 knowledge_chunk 表 | 可访问 |
|
||||||
|
| POST | `/internal/rag/candidates` | 批量写入导入候选 | 可访问 |
|
||||||
|
|
||||||
|
**攻击面:**
|
||||||
|
- 读取所有导入文档的原文(数据泄露)
|
||||||
|
- 往 `knowledge_chunk` 表注入任意数据(RAG 投毒)
|
||||||
|
- 认领/劫持作业,干扰合法 Worker(拒绝服务)
|
||||||
|
- 无速率限制,可无限写入
|
||||||
|
|
||||||
|
**修复方式:**
|
||||||
|
- 方案 A:添加 InternalAuthGuard,验证 `X-Internal-API-Key` 请求头(已有 `RAG_WORKER_SECRET` 在生产 env 中)
|
||||||
|
- 方案 B:在 Nginx 层限制 IP(仅允许 127.0.0.1),因为这些端点仅供本地 Worker 使用
|
||||||
|
- 建议:A+B 组合
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-3: JwtAuthGuard 不检查用户状态
|
||||||
|
|
||||||
|
**文件:** `src/common/guards/jwt-auth.guard.ts:42-46`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 当前逻辑:只验证 JWT 签名,不检查用户状态
|
||||||
|
const payload = await this.jwtService.verifyAsync(token, {
|
||||||
|
secret: this.configService.get<string>('jwt.secret'),
|
||||||
|
});
|
||||||
|
request.user = { id: String(payload.sub), email: payload.email, role: payload.role };
|
||||||
|
return true; // ← 禁用的用户、已删除的用户 Token 都通过
|
||||||
|
```
|
||||||
|
|
||||||
|
**对比 AdminAuthGuard(正确示范,admin-auth.guard.ts:45-59):**
|
||||||
|
```typescript
|
||||||
|
const adminUser = await this.prisma.adminUser.findUnique({
|
||||||
|
where: { id: payload.sub },
|
||||||
|
});
|
||||||
|
if (!adminUser || adminUser.deletedAt) {
|
||||||
|
throw new UnauthorizedException('管理员账号不存在');
|
||||||
|
}
|
||||||
|
if (adminUser.status !== 'ACTIVE') {
|
||||||
|
throw new UnauthorizedException('管理员账号已被禁用');
|
||||||
|
}
|
||||||
|
if (adminUser.lockedUntil && new Date(adminUser.lockedUntil) > new Date()) {
|
||||||
|
throw new UnauthorizedException('管理员账号已被锁定,请稍后再试');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修复方式:** JwtAuthGuard 添加数据库查询,验证 `user.deletedAt === null && user.status === 'active'`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-4: Refresh Token 不检查用户状态
|
||||||
|
|
||||||
|
**文件:** `src/modules/auth/auth.service.ts:125-128`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 刷新时不检查用户状态
|
||||||
|
const accessToken = await this.tokenService.generateAccessToken(stored.user);
|
||||||
|
```
|
||||||
|
|
||||||
|
刷新流程完整链路:验证 refreshToken → 撤销旧 token → 生成新 token → **未检查 stored.user 是否被禁用**
|
||||||
|
|
||||||
|
**修复方式:** 在生成新 access token 前添加 `if (stored.user.status !== 'active' || stored.user.deletedAt) throw new UnauthorizedException()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-5: 用户/管理员 JWT 共用密钥
|
||||||
|
|
||||||
|
**文件:** `src/config/jwt.config.ts:6`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const adminSecret = process.env.ADMIN_JWT_ACCESS_SECRET || process.env.JWT_SECRET;
|
||||||
|
```
|
||||||
|
|
||||||
|
生产 env 仅设 `JWT_SECRET`,未设 `ADMIN_JWT_ACCESS_SECRET`。用户和管理员 Token 用同一密钥签名。
|
||||||
|
|
||||||
|
**Token 隔离分析:**
|
||||||
|
|
||||||
|
| 场景 | 结果 |
|
||||||
|
|------|------|
|
||||||
|
| 管理员 Token 访问 `/api/*` | JwtAuthGuard 验证通过(同密钥),管理员可以访问用户 API |
|
||||||
|
| 用户 Token 访问 `/admin-api/*` | AdminAuthGuard 发现 `payload.type !== 'admin'` 拒绝 |
|
||||||
|
| 用户 Token 的 payload | `{ sub, email, role }` — 无 `type` 字段 |
|
||||||
|
| 管理员 Token 的 payload | `{ sub, type: 'admin', role, sessionId }` — 有 `type` 字段 |
|
||||||
|
|
||||||
|
**当前状态:** AdminAuthGuard 通过 `type` 字段区分,即使密钥相同也能阻止用户 Token 访问管理员接口。但反之不行——管理员 Token 可以访问用户接口。且如果密钥泄露,攻击者可伪造管理员 Token。
|
||||||
|
|
||||||
|
**修复方式:** 在生产 env 添加 `ADMIN_JWT_ACCESS_SECRET=<独立密钥>`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P1-6: iOS 核心流程缺少 DTO
|
||||||
|
|
||||||
|
**受影响的端点:**
|
||||||
|
|
||||||
|
| 端点 | 文件:行号 | 当前状态 |
|
||||||
|
|------|-----------|----------|
|
||||||
|
| `POST /api/imports` | `document-import.controller.ts:13` | `@Body() body: any` |
|
||||||
|
| `POST /api/knowledge-bases/:kbId/sources` | `knowledge-source.controller.ts:18` | `@Body() dto: any` |
|
||||||
|
| `POST /api/learning-sessions` | `learning-session.controller.ts` | `@Body() body: any` |
|
||||||
|
| `POST /api/active-recalls/:id/submit` | `active-recall.controller.ts` | `@Body() body: any` |
|
||||||
|
| `POST /api/focus-items` | `focus-items.controller.ts` | `@Body() body: any` |
|
||||||
|
|
||||||
|
**影响:** iOS 发送错误字段时,由于无 `class-validator` 校验,可能拿到 201 Created,直到 AI 处理阶段才失败,调试成本高。
|
||||||
|
|
||||||
|
**修复方式:** 为上述端点创建 DTO 类,添加 `@IsString()`, `@IsOptional()`, `@IsEnum()` 等装饰器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. CAPI 契约检查
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
**统一成功响应**(ResponseInterceptor 全局拦截):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": <原始返回值>,
|
||||||
|
"timestamp": "2026-05-24T..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**统一错误响应**(GlobalExceptionFilter):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"statusCode": 401,
|
||||||
|
"message": "请先登录"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**已知异常:** 4 个端点手动返回 `{success, data}` 结构,被拦截器二次包装导致 `data.data` 嵌套:
|
||||||
|
- `POST /api/auth/logout`
|
||||||
|
- `POST /api/feedback`
|
||||||
|
- `POST /api/reports`
|
||||||
|
- `POST /api/waitlist`
|
||||||
|
|
||||||
|
### 分页
|
||||||
|
|
||||||
|
`PaginatedResponse<T>` 接口已定义(`src/common/types/index.ts:9-19`),但**从未被任何控制器使用**。CAPI 分页端点各自返回 raw 格式,iOS 需按端点适配。
|
||||||
|
|
||||||
|
### Swagger 质量
|
||||||
|
|
||||||
|
- ✅ 所有 CAPI 控制器有 `@ApiTags`(中文标签)
|
||||||
|
- ✅ 所有端点有 `@ApiOperation`(中文描述)
|
||||||
|
- ❌ 无 `@ApiResponse({ type: SomeDto })` —— Swagger 无 response schema,iOS 客户端生成工具无法推断返回类型
|
||||||
|
- ✅ Bearer Auth 已全局配置
|
||||||
|
- ✅ 生产环境可通过 Basic Auth 访问 `/api-docs-json`
|
||||||
|
|
||||||
|
### 状态枚举
|
||||||
|
|
||||||
|
Prisma schema 使用 `String` 类型(无原生 enum),状态值在应用层约定。以下是 CAPI 相关模型的状态枚举:
|
||||||
|
|
||||||
|
| 模型 | 字段 | 可能的值 |
|
||||||
|
|------|------|----------|
|
||||||
|
| User | status | `active`, `disabled`, `deleted` |
|
||||||
|
| User | role | `USER`, `ADMIN` |
|
||||||
|
| KnowledgeBase | status | `active`, `archived`, `deleted` |
|
||||||
|
| KnowledgeItem | status | `active`, `archived` |
|
||||||
|
| KnowledgeItem | itemType | `note`, `flashcard`, `concept` |
|
||||||
|
| KnowledgeSource | parseStatus | `pending`, `parsing`, `completed`, `failed` |
|
||||||
|
| KnowledgeSource | indexStatus | `pending`, `indexing`, `completed`, `failed` |
|
||||||
|
| KnowledgeSource | learningStatus | `pending`, `learning`, `completed`, `skipped` |
|
||||||
|
| DocumentImport | status | `QUEUED`, `CLAIMED`, `PARSING`, `COMPLETED`, `FAILED`, `FAILED_FINAL` (另有中间状态 10+ 个) |
|
||||||
|
| ImportCandidate | status | `PENDING`, `ACCEPTED`, `REJECTED` |
|
||||||
|
| LearningSession | status | `active`, `completed`, `cancelled` |
|
||||||
|
| LearningSession | mode | `active_recall`, `feynman`, `review`, `reading` |
|
||||||
|
| AiAnalysisJob | status | `pending`, `processing`, `completed`, `failed` |
|
||||||
|
| AiAnalysisJob | jobType | `active_recall_analysis`, `feynman_evaluation` |
|
||||||
|
| ReviewCard | status | `active`, `completed`, `archived` |
|
||||||
|
| ReviewCard | scheduleState | `new`, `learning`, `review`, `relearning` |
|
||||||
|
| ReviewCard | difficulty | 数值 (0.0-1.0, SM-2 算法) |
|
||||||
|
| FocusItem | status | `open`, `completed` |
|
||||||
|
| FocusItem | priority | `high`, `normal`, `low` |
|
||||||
|
| Feedback | status | `pending`, `reviewed`, `resolved`, `closed` |
|
||||||
|
| Feedback | type | `bug`, `feature`, `general` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F. 文件上传与导入流程(iOS 适配分析)
|
||||||
|
|
||||||
|
### 完整流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
iOS App API Server COS (腾讯云) BullMQ Worker
|
||||||
|
======= ========== ============ =============
|
||||||
|
|
||||||
|
1. POST /api/files/upload-url
|
||||||
|
{ filename, mimeType, sizeBytes }
|
||||||
|
──────────────────────────► 校验文件类型(9种)/大小(≤20MB)
|
||||||
|
生成预签名 PUT URL
|
||||||
|
◄────────────────────────── { uploadUrl, objectKey, expiresIn }
|
||||||
|
|
||||||
|
2. PUT <uploadUrl>
|
||||||
|
[二进制文件数据]
|
||||||
|
───────────────────────────────────────────────────────► 文件存入 COS
|
||||||
|
|
||||||
|
3. POST /api/files/complete
|
||||||
|
{ objectKey, checksum? }
|
||||||
|
──────────────────────────► 验证文件已到达 COS
|
||||||
|
创建 UploadedFile 记录
|
||||||
|
◄────────────────────────── { id, filename, sizeBytes }
|
||||||
|
|
||||||
|
4. POST /api/knowledge-bases/:kbId/sources
|
||||||
|
{ fileId, title, originalFilename }
|
||||||
|
──────────────────────────► 创建 KnowledgeSource
|
||||||
|
自动创建 DocumentImport (QUEUED)
|
||||||
|
◄────────────────────────── { id, parseStatus: "pending" }
|
||||||
|
|
||||||
|
5. GET /api/imports/:id/status (轮询)
|
||||||
|
──────────────────────────► 查 Redis 实时状态 → 回退 DB
|
||||||
|
◄────────────────────────── { status: "QUEUED", progress: 0 }
|
||||||
|
|
||||||
|
... Worker 处理中 ...
|
||||||
|
|
||||||
|
6. GET /api/imports/:id/status (轮询)
|
||||||
|
◄────────────────────────── { status: "COMPLETED", progress: 100,
|
||||||
|
message: "成功提取 N 个知识点" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 允许的文件类型
|
||||||
|
|
||||||
|
| 类型 | MIME |
|
||||||
|
|------|------|
|
||||||
|
| PDF | `application/pdf` |
|
||||||
|
| TXT | `text/plain` |
|
||||||
|
| Markdown | `text/markdown`, `text/x-markdown` |
|
||||||
|
| CSV | `text/csv` |
|
||||||
|
| Word | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
|
||||||
|
| Excel | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
||||||
|
| PNG | `image/png` |
|
||||||
|
| JPEG | `image/jpeg` |
|
||||||
|
| WebP | `image/webp` |
|
||||||
|
|
||||||
|
文件大小上限:**20MB**,全局 `StrictValidationPipe` 限制请求体 **10MB**。
|
||||||
|
|
||||||
|
### iOS 适配评估
|
||||||
|
|
||||||
|
| 评估项 | 结论 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 预签名 URL 直传 | ✅ 可用 | 标准 HTTP PUT,iOS URLSession/Alamofire 均可 |
|
||||||
|
| 请求/响应格式 | ✅ 可用 | 标准 JSON,字段简单 |
|
||||||
|
| 认证方式 | ✅ 可用 | 标准 JWT Bearer Token |
|
||||||
|
| 分块上传/续传 | ❌ 不支持 | 仅支持完整文件一次 PUT,大文件+弱网可能失败 |
|
||||||
|
| Checksum 校验 | ⚠️ 可选 | `CompleteUploadDto.checksum` 可选,建议 iOS 传 SHA-256 |
|
||||||
|
| 错误信息中文 | ⚠️ 注意 | 文件名校验等错误信息为中文,iOS 需处理或本地化 |
|
||||||
|
| 导入状态 DTO | ❌ 缺失 | `POST /api/imports` 使用 `any`,无验证 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## G. CAPI 端点认证测试(生产环境实测)
|
||||||
|
|
||||||
|
| 端点 | 无 Token | 结论 |
|
||||||
|
|------|----------|------|
|
||||||
|
| `GET /health` | 200 | 公开 |
|
||||||
|
| `GET /api/users/me` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /api/knowledge-bases` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /api/activity/streak` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /api/reviews/due` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /api/notifications` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /api/workspace/dashboard` | 401 "请先登录" | 受保护 ✅ |
|
||||||
|
| `GET /internal/rag/jobs/next` | **200** | **未保护 🔴** |
|
||||||
|
| `POST /api/auth/dev-login` | 403 "生产环境禁用" | 已禁用 ✅ |
|
||||||
|
| `GET /api-docs-json` | 401 "Authentication required" | Basic Auth ✅ |
|
||||||
|
| `GET /api-docs-json` (with auth) | 200 | 正常 ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## H. 给 iOS 团队的接口文档清单
|
||||||
|
|
||||||
|
以下文档需要在 iOS 对接开始前编写交付:
|
||||||
|
|
||||||
|
| 文档 | 内容 | 优先级 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `capi-contract-for-ios.md` | 完整 CAPI 端点清单、请求/响应格式、认证方式、通用约定 | P0 |
|
||||||
|
| `ios-auth-flow.md` | Apple 登录流程、Token 刷新策略、Keychain 存储建议、登出 | P0 |
|
||||||
|
| `ios-file-upload-import-flow.md` | 预签名 URL → COS 直传 → Source 创建 → 导入轮询 | P1 |
|
||||||
|
| `ios-learning-review-flow.md` | 学习会话 → 主动回忆 → AI 分析 → SM-2 复习 → 薄弱项 | P1 |
|
||||||
|
| `capi-error-codes-and-status-enums.md` | HTTP 错误码、业务错误消息、所有模型状态枚举值 | P1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## I. 修复优先级路线图
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 0 (iOS 启动前)
|
||||||
|
├── P0-1: 配置 APPLE_BUNDLE_ID,启用真实验证
|
||||||
|
├── P0-2: 添加 InternalAuthGuard 或 Nginx IP 限制
|
||||||
|
└── P1-5: 配置 ADMIN_JWT_ACCESS_SECRET(独立密钥)
|
||||||
|
|
||||||
|
Week 1 (iOS 开发启动)
|
||||||
|
├── P1-3: JwtAuthGuard 添加用户状态检查
|
||||||
|
├── P1-4: Refresh Token 添加用户状态检查
|
||||||
|
├── P1-6: 为核心端点创建 DTO
|
||||||
|
└── 编写 iOS 接口文档(5 份)
|
||||||
|
|
||||||
|
Week 2+
|
||||||
|
├── P2-7: 统一分页响应格式
|
||||||
|
├── P2-8: Swagger 添加 Response Schema
|
||||||
|
├── P2-9: 修复双重响应包装
|
||||||
|
└── P2-10/11/12: 杂项优化
|
||||||
|
```
|
||||||
78
技术设计/ios-auth-flow.md
Normal file
78
技术设计/ios-auth-flow.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# iOS 认证流程
|
||||||
|
|
||||||
|
> 版本:0.1.0 | 更新:2026-05-24
|
||||||
|
|
||||||
|
## 流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
iOS App API Server Apple
|
||||||
|
|
||||||
|
1. 用户点击"使用 Apple 登录"
|
||||||
|
Sign in with Apple 弹窗
|
||||||
|
─────────────────────────────────────────────────────────► 验证用户身份
|
||||||
|
◄───────────────────────────────────────────────────────── 返回 identityToken
|
||||||
|
|
||||||
|
2. POST /api/auth/apple
|
||||||
|
{ identityToken, fullName?, email? }
|
||||||
|
──────────────────────────► 验证 identityToken
|
||||||
|
通过 Apple JWKS 验签
|
||||||
|
验证 issuer + audience
|
||||||
|
查找或创建用户
|
||||||
|
生成 JWT + RefreshToken
|
||||||
|
◄────────────────────────── { accessToken, refreshToken, user }
|
||||||
|
|
||||||
|
3. 存储 Token
|
||||||
|
Keychain: accessToken + refreshToken
|
||||||
|
|
||||||
|
4. 后续请求携带 accessToken
|
||||||
|
GET /api/users/me
|
||||||
|
Authorization: Bearer <accessToken>
|
||||||
|
──────────────────────────► JwtAuthGuard 验证
|
||||||
|
- 解码 JWT
|
||||||
|
- 检查 type=user
|
||||||
|
- 查询用户状态 (active/deleted)
|
||||||
|
◄────────────────────────── 用户数据
|
||||||
|
|
||||||
|
5. Token 过期时刷新 (401)
|
||||||
|
POST /api/auth/refresh
|
||||||
|
{ refreshToken }
|
||||||
|
──────────────────────────► 验证 refresh token hash
|
||||||
|
检查用户状态
|
||||||
|
撤销旧 token → 签发新对
|
||||||
|
◄────────────────────────── { accessToken, refreshToken, user }
|
||||||
|
|
||||||
|
6. 登出
|
||||||
|
POST /api/auth/logout
|
||||||
|
{ refreshToken }
|
||||||
|
──────────────────────────► 撤销 refresh token
|
||||||
|
◄────────────────────────── 200 OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## Token 存储建议
|
||||||
|
|
||||||
|
| Token | 存储位置 | 说明 |
|
||||||
|
|-------|----------|------|
|
||||||
|
| accessToken | Keychain / Secure Enclave | 短期(1h),频繁使用 |
|
||||||
|
| refreshToken | Keychain | 长期(7d),仅在刷新时使用 |
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
| HTTP | message | iOS 处理 |
|
||||||
|
|------|---------|----------|
|
||||||
|
| 401 | "请先登录" | 跳转登录页 |
|
||||||
|
| 401 | "登录已过期,请重新登录" | 尝试刷新 token,失败则跳转登录 |
|
||||||
|
| 401 | "账号已被禁用" | 显示禁用提示,退出 |
|
||||||
|
| 401 | "账号不存在或已注销" | 跳转登录页 |
|
||||||
|
| 401 | "Apple 登录未配置" | 显示维护提示 |
|
||||||
|
|
||||||
|
## 自动刷新策略
|
||||||
|
|
||||||
|
```
|
||||||
|
请求 → 401?
|
||||||
|
├── 是 → 尝试 POST /api/auth/refresh
|
||||||
|
│ ├── 成功 → 更新 Keychain,重试原请求
|
||||||
|
│ └── 失败 → 清除 Token,跳转登录
|
||||||
|
└── 否 → 正常处理
|
||||||
|
```
|
||||||
|
|
||||||
|
建议在 HTTP 拦截器/中间件层实现,避免每个 API 调用都要处理。
|
||||||
98
技术设计/ios-file-upload-import-flow.md
Normal file
98
技术设计/ios-file-upload-import-flow.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# iOS 文件上传与导入流程
|
||||||
|
|
||||||
|
> 版本:0.1.0 | 更新:2026-05-24
|
||||||
|
|
||||||
|
## 完整流程
|
||||||
|
|
||||||
|
```
|
||||||
|
iOS App API Server COS (腾讯云) Worker
|
||||||
|
======= ========== ============ ======
|
||||||
|
|
||||||
|
1. 获取上传凭证
|
||||||
|
POST /api/files/upload-url
|
||||||
|
{ filename, mimeType, sizeBytes }
|
||||||
|
──────────────────────────► 校验文件类型(9 种)
|
||||||
|
校验大小(≤20MB)
|
||||||
|
生成预签名 PUT URL
|
||||||
|
◄────────────────────────── { uploadUrl, objectKey,
|
||||||
|
bucket, region, expiresIn }
|
||||||
|
|
||||||
|
2. 直传文件到 COS
|
||||||
|
PUT <uploadUrl>
|
||||||
|
Content-Type: <mimeType>
|
||||||
|
[binary file data]
|
||||||
|
───────────────────────────────────────────────────────► 文件存入 COS
|
||||||
|
|
||||||
|
3. 确认上传完成
|
||||||
|
POST /api/files/complete
|
||||||
|
{ objectKey, checksum? }
|
||||||
|
──────────────────────────► COS headObject 验证
|
||||||
|
创建 UploadedFile 记录
|
||||||
|
◄────────────────────────── { id, filename, sizeBytes,
|
||||||
|
mimeType }
|
||||||
|
|
||||||
|
4. 创建知识源(自动触发导入)
|
||||||
|
POST /api/knowledge-bases/:kbId/sources
|
||||||
|
{ fileId, title, type, originalFilename, mimeType }
|
||||||
|
──────────────────────────► 创建 KnowledgeSource
|
||||||
|
自动创建 DocumentImport
|
||||||
|
(status=QUEUED)
|
||||||
|
◄────────────────────────── { id, title, parseStatus }
|
||||||
|
|
||||||
|
5. 轮询导入状态
|
||||||
|
GET /api/imports/:importId/status
|
||||||
|
──────────────────────────► 查 Redis 实时状态
|
||||||
|
→ 回退 DB
|
||||||
|
◄────────────────────────── { id, status, progress,
|
||||||
|
message }
|
||||||
|
|
||||||
|
6. Worker 处理
|
||||||
|
下载文件
|
||||||
|
AI 解析提取
|
||||||
|
创建知识点
|
||||||
|
更新状态→COMPLETED
|
||||||
|
|
||||||
|
7. 轮询导入状态
|
||||||
|
GET /api/imports/:importId/status
|
||||||
|
◄────────────────────────── { status: "COMPLETED",
|
||||||
|
progress: 100,
|
||||||
|
message: "成功提取 N 个知识点" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 允许的文件类型
|
||||||
|
|
||||||
|
| 格式 | MIME |
|
||||||
|
|------|------|
|
||||||
|
| PDF | `application/pdf` |
|
||||||
|
| TXT | `text/plain` |
|
||||||
|
| Markdown | `text/markdown`, `text/x-markdown` |
|
||||||
|
| CSV | `text/csv` |
|
||||||
|
| Word | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
|
||||||
|
| Excel | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
||||||
|
| PNG | `image/png` |
|
||||||
|
| JPEG | `image/jpeg` |
|
||||||
|
| WebP | `image/webp` |
|
||||||
|
|
||||||
|
**大小上限:** 20MB
|
||||||
|
|
||||||
|
## 导入状态枚举
|
||||||
|
|
||||||
|
| 状态 | 含义 | 可操作 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `QUEUED` | 排队中 | 轮询等待 |
|
||||||
|
| `CLAIMED` | 已被 Worker 认领 | 轮询等待 |
|
||||||
|
| `DOWNLOADING` | 下载中 | 轮询等待 |
|
||||||
|
| `PARSING` | 解析中 | 轮询等待 |
|
||||||
|
| `CHUNKING` | 分块中 | 轮询等待 |
|
||||||
|
| `EMBEDDING` | 向量化中 | 轮询等待 |
|
||||||
|
| `COMPLETED` | 完成 | 可查看结果 |
|
||||||
|
| `FAILED` | 失败(会重试 3 次) | 等待自动重试 |
|
||||||
|
| `FAILED_FINAL` | 最终失败 | 需重新导入 |
|
||||||
|
|
||||||
|
## iOS 实现要点
|
||||||
|
|
||||||
|
1. **直接 PUT 到 COS**:使用 URLSession 的 `uploadTask(with:fromFile:)` 或 Alamofire 的 `upload(data:to:)`
|
||||||
|
2. **预签名 URL 有过期时间**:`expiresIn` 秒内有效,拿到后立即使用
|
||||||
|
3. **支持进度回调**:大文件上传时建议显示进度条
|
||||||
|
4. **Checksum 可选但推荐**:上传完成后传 `checksum`(SHA-256)确保完整性
|
||||||
|
5. **导入轮询间隔建议**:初始 2s,逐步退避到 5s
|
||||||
133
技术设计/ios-learning-review-flow.md
Normal file
133
技术设计/ios-learning-review-flow.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# iOS 学习与复习流程
|
||||||
|
|
||||||
|
> 版本:0.1.0 | 更新:2026-05-24
|
||||||
|
|
||||||
|
## 整体流程
|
||||||
|
|
||||||
|
```
|
||||||
|
创建知识库 → 上传资料 → 导入知识点 → 开始学习 → 主动回忆 → AI 分析 → 复习巩固
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. 学习会话
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/learning-sessions
|
||||||
|
{ mode: "active_recall", knowledgeBaseId: "kb-1" }
|
||||||
|
────────────────────────► 创建会话 (status=active)
|
||||||
|
◄─────────────────────── { id, startedAt, mode }
|
||||||
|
|
||||||
|
...学习过程...
|
||||||
|
|
||||||
|
POST /api/learning-sessions/:id/end
|
||||||
|
────────────────────────► 结束会话 (status=completed)
|
||||||
|
记录 durationSeconds
|
||||||
|
◄─────────────────────── { id, durationSeconds }
|
||||||
|
```
|
||||||
|
|
||||||
|
**mode 枚举:** `active_recall` | `feynman` | `review` | `reading`
|
||||||
|
|
||||||
|
## 2. 主动回忆
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/active-recalls?page=1&limit=20
|
||||||
|
◄─────────────────────── 题目列表(含问题/选项)
|
||||||
|
|
||||||
|
POST /api/active-recalls/:id/submit
|
||||||
|
{ answer: "用户答案" }
|
||||||
|
────────────────────────► 记录回答 → 触发 AI 分析
|
||||||
|
(异步:BullMQ ai-analysis 队列)
|
||||||
|
◄─────────────────────── { success: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. AI 分析
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/ai-analysis
|
||||||
|
{ ... }
|
||||||
|
────────────────────────► 创建分析作业
|
||||||
|
|
||||||
|
GET /api/ai-analysis/jobs/:id
|
||||||
|
◄─────────────────────── { status: "pending|processing|completed|failed" }
|
||||||
|
|
||||||
|
GET /api/ai-analysis/:id
|
||||||
|
◄─────────────────────── 分析结果(强项/弱项/建议)
|
||||||
|
```
|
||||||
|
|
||||||
|
**分析完成后自动生成:**
|
||||||
|
- ReviewCard(复习卡片,SM-2 算法调度)
|
||||||
|
- FocusItem(薄弱项,待巩固)
|
||||||
|
|
||||||
|
## 4. 复习(SM-2 间隔重复)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/reviews/due
|
||||||
|
◄─────────────────────── 今日待复习卡片列表
|
||||||
|
[{
|
||||||
|
id, frontText, difficulty,
|
||||||
|
scheduleState, intervalDays,
|
||||||
|
repetitionCount, lapseCount
|
||||||
|
}]
|
||||||
|
|
||||||
|
POST /api/reviews/:id/submit
|
||||||
|
{ quality: 4 }
|
||||||
|
────────────────────────► 提交复习质量评分 (0-5)
|
||||||
|
SM-2 算法更新:
|
||||||
|
- easeFactor
|
||||||
|
- intervalDays
|
||||||
|
- nextReviewAt
|
||||||
|
- scheduleState
|
||||||
|
◄─────────────────────── { id, nextReviewAt, intervalDays }
|
||||||
|
```
|
||||||
|
|
||||||
|
**scheduleState:** `new` → `learning` → `review` → `relearning`
|
||||||
|
|
||||||
|
**quality 评分指南:**
|
||||||
|
|
||||||
|
| 评分 | 含义 |
|
||||||
|
|------|------|
|
||||||
|
| 0 | 完全忘记 |
|
||||||
|
| 1 | 错误,有印象 |
|
||||||
|
| 2 | 错误,但正确答案看起来很熟悉 |
|
||||||
|
| 3 | 正确,但费了很大劲 |
|
||||||
|
| 4 | 正确,有些犹豫 |
|
||||||
|
| 5 | 完美,毫不费力 |
|
||||||
|
|
||||||
|
## 5. 薄弱项跟踪
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/focus-items?status=open
|
||||||
|
◄─────────────────────── [{ id, title, priority, status }]
|
||||||
|
|
||||||
|
POST /api/focus-items/:id/complete
|
||||||
|
────────────────────────► 标记为已掌握
|
||||||
|
◄─────────────────────── { id, status: "completed" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**priority 枚举:** `high` | `normal` | `low`
|
||||||
|
|
||||||
|
## 6. 学习统计
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/activity/heatmap
|
||||||
|
◄─────────────────────── [{ date, count }] 365 天热力图数据
|
||||||
|
|
||||||
|
GET /api/activity/streak
|
||||||
|
◄─────────────────────── { currentStreak, longestStreak }
|
||||||
|
|
||||||
|
GET /api/activity/summary
|
||||||
|
◄─────────────────────── { totalSessions, totalDuration, ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整学习主链路
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 创建/选择知识库 POST /api/knowledge-bases
|
||||||
|
2. 上传资料 POST /api/files/upload-url → PUT COS → POST /api/files/complete
|
||||||
|
3. 创建来源 + 导入 POST /api/knowledge-bases/:id/sources
|
||||||
|
4. 轮询导入状态 GET /api/imports/:id/status
|
||||||
|
5. 开始学习会话 POST /api/learning-sessions
|
||||||
|
6. 主动回忆练习 GET/POST /api/active-recalls
|
||||||
|
7. AI 分析 POST /api/ai-analysis
|
||||||
|
8. 每日复习 GET /api/reviews/due → POST /api/reviews/:id/submit
|
||||||
|
9. 跟踪薄弱项 GET → POST /api/focus-items/:id/complete
|
||||||
|
```
|
||||||
Loading…
x
Reference in New Issue
Block a user