From 3e653dc1af52137be682e62660d272ea0b29de77 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sat, 9 May 2026 20:31:23 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=95=B4=E7=90=86=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=88=B0=20docs/=20=E6=96=87=E4=BB=B6=E5=A4=B9=20+=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20credentials.md=20=E5=87=AD=E6=8D=AE=E6=B1=87?= =?UTF-8?q?=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/BACKEND-PLAN.md | 1185 +++++++++++++++++++++++++++++++++++++++ docs/DATABASE-DESIGN.md | 814 +++++++++++++++++++++++++++ docs/REDIS-DESIGN.md | 262 +++++++++ docs/SECURITY.md | 169 ++++++ docs/credentials.md | 107 ++++ 5 files changed, 2537 insertions(+) create mode 100644 docs/BACKEND-PLAN.md create mode 100644 docs/DATABASE-DESIGN.md create mode 100644 docs/REDIS-DESIGN.md create mode 100644 docs/SECURITY.md create mode 100644 docs/credentials.md diff --git a/docs/BACKEND-PLAN.md b/docs/BACKEND-PLAN.md new file mode 100644 index 0000000..f14ba85 --- /dev/null +++ b/docs/BACKEND-PLAN.md @@ -0,0 +1,1185 @@ +--- +source: startup-plan/个人开发者创业 v0.1 + 完全版 + AI回答.md 架构深化方案 +updated: 2026-05-09 +--- + +# 知习 api-server 后端架构规划 + +> 「知习」是一款 AI-first 的系统化学习 App。后端需要同时服务 iOS App 和 Web 官网。 +> 核心功能包括知识库、主动回忆、AI 学习分析、间隔复习、待巩固项、学习活跃记录、消息通知和反馈。 + +--- + +## 1. 架构总览:模块化单体 + Redis + Worker + +**不要微服务。** 当前最适合的是: + +> **模块化单体架构 Monolithic Modular Architecture** +> +> 一个后端项目,内部按业务模块拆清楚。 + +```text +iOS App +Web 官网 + ↓ +统一 API 后端(NestJS 模块化单体) + ↓ +数据库 / Redis / 文件存储 / AI 服务 + ↓ +Worker 异步任务 +``` + +--- + +## 2. 最终技术栈 + +```text +后端框架:NestJS + TypeScript +ORM:Prisma +数据库:PostgreSQL(推荐),MySQL 亦可 +缓存/队列:Redis +队列系统:BullMQ +接口文档:Swagger / OpenAPI +文件存储:本地开发先本地存,后期接对象存储 +AI:Provider 抽象 + 一个真实模型 + MockProvider +部署:Docker Compose +反向代理:Nginx +域名:api.longde.cloud +``` + +Docker Compose 项目服务规划: + +```text +api-server — NestJS 后端 +postgres — 主数据库 +redis — 缓存 + 队列 +worker — 异步任务处理 +nginx — 反向代理 + HTTPS +``` + +--- + +## 3. 为什么选这个技术栈 + +### NestJS + +项目会越来越模块化。NestJS 的模块结构适合长期维护: + +```text +用户 / 知识库 / 学习记录 / AI 分析 / 复习计划 / 待巩固项 / 通知 / 反馈 / 订阅 +``` + +Express 也能做,但后期容易自己手写一堆规范,最后变乱。 + +### Prisma + +- 类型安全的 ORM,自动生成 TypeScript 类型 +- 迁移管理清晰 +- 与 NestJS 集成良好 +- AI Agent 按 schema 生成代码能力好 + +### PostgreSQL(优于 MySQL) + +- 更适合 JSON 数据(AI 分析结果) +- 支持全文检索 +- 后续可扩展 pgvector 做向量检索 +- 如果更熟悉 MySQL,也可以先用 MySQL + +### Redis + BullMQ + +Redis 不只是缓存,在知习系统里做四件事: + +```text +缓存 → 短期数据加速 +队列 → AI 分析 / 资料导入 / 通知任务 +限流 → AI 调用频控 / 用户请求限流 +临时状态 → 任务处理中状态 / 防重复提交锁 +``` + +--- + +## 4. 前后端职责分离 + +| 端 | 职责 | +|---|------| +| iOS 客户端 | 页面展示、用户交互、本地状态、登录入口、学习流程体验 | +| 后端 API | 用户身份、AI API 代理、学习记录、AI 分析、学习画像、复习计划、待巩固项、通知、反馈 | +| Worker | AI 分析任务异步处理、资料导入异步处理、通知分发 | +| AI 模型 | 分析用户输入、生成学习反馈、识别薄弱点、生成复习建议、辅助学习对话 | +| 官网 | 产品介绍、SEO、隐私政策、用户协议、支持页面、等待名单 | + +--- + +## 5. 数据库和 Redis 的分工 + +``` +PostgreSQL / MySQL:长期真实数据 +Redis:短期状态、缓存、队列、限流 +文件存储:PDF、图片、附件 +AI 服务:分析、总结、生成复习题 +``` + +一句话: + +> **数据库存事实,Redis 管状态。** + +### Redis 适合做 + +- AI 分析任务队列 +- 资料导入任务队列 +- 通知任务队列 +- 用户请求限流 +- AI 调用次数计数 +- 任务处理中状态 +- 防重复提交锁 +- 短期缓存 + +### Redis 不适合做主数据 + +这些必须进数据库: + +- 用户资料 +- 知识库 +- 学习记录 +- AI 分析结果 +- 待巩固项 +- 复习计划 +- 学习活跃记录 +- 通知记录 + +--- + +## 6. 后端目录结构 + +```text +api-server/ +├── src/ +│ ├── main.ts +│ ├── app.module.ts +│ │ +│ ├── config/ +│ │ ├── app.config.ts +│ │ ├── database.config.ts +│ │ ├── redis.config.ts +│ │ ├── jwt.config.ts +│ │ ├── ai.config.ts +│ │ └── storage.config.ts +│ │ +│ ├── common/ +│ │ ├── constants/ +│ │ ├── decorators/ +│ │ ├── guards/ +│ │ ├── interceptors/ +│ │ ├── filters/ +│ │ ├── pipes/ +│ │ ├── dto/ +│ │ ├── types/ +│ │ └── utils/ +│ │ +│ ├── infrastructure/ +│ │ ├── database/ +│ │ ├── redis/ +│ │ ├── queue/ +│ │ ├── ai/ +│ │ │ ├── prompts/ +│ │ │ └── providers/ +│ │ ├── storage/ +│ │ └── logger/ +│ │ +│ ├── modules/ +│ │ ├── auth/ +│ │ ├── users/ +│ │ ├── knowledge-base/ +│ │ ├── knowledge-items/ +│ │ ├── document-import/ +│ │ ├── learning-session/ +│ │ ├── active-recall/ +│ │ ├── ai-analysis/ +│ │ ├── review/ +│ │ ├── focus-items/ +│ │ ├── learning-activity/ +│ │ ├── notifications/ +│ │ ├── feedback/ +│ │ └── system/ +│ │ +│ └── workers/ +│ ├── ai-analysis.worker.ts +│ ├── document-import.worker.ts +│ └── notification.worker.ts +│ +├── prisma/ +│ ├── schema.prisma +│ └── migrations/ +│ +├── docker-compose.yml +├── Dockerfile +├── .env.example +└── package.json +``` + +--- + +## 7. 各模块职责 + +### 7.1 Auth 模块 + +负责 Apple 登录、JWT、刷新 Token、退出登录、账号注销。 + +App Store 上架要求 Sign in with Apple,这是必须做的。 + +### 7.2 Users 模块 + +负责用户资料、头像、昵称、学习身份、学习方向、学习偏好。 + +### 7.3 Knowledge Base 模块 + +负责知识库创建、列表、详情、编辑、删除、标签。 + +### 7.4 Knowledge Items 模块 + +负责具体知识点、章节、内容段落、关键概念、标签、关联关系。 + +### 7.5 Document Import 模块 + +负责上传文件、粘贴文本、链接导入、导入状态、解析进度、导入成功/失败。 + +这里非常适合用 Redis Queue 做异步处理。 + +### 7.6 Learning Session 模块 + +负责一次学习过程:开始学习、结束学习、学习时长、学习内容、学习模式、完成状态。 + +这是学习活跃图的数据来源之一。 + +### 7.7 Active Recall 模块 + +负责主动回忆:回忆问题、用户回答、语音回答、回答提交、回答历史。 + +### 7.8 AI Analysis 模块 + +负责提交分析任务、分析状态、AI 分析结果、掌握度、薄弱点、改进建议、下一步建议。 + +**必须走 Redis Queue 异步处理。** + +### 7.9 Focus Items 模块 + +「待巩固项」——类似 GitHub Issue,但在知习里叫待巩固项。 + +负责:AI 自动生成待巩固项、用户手动添加、状态(待处理/已加入复习/已完成)、关联知识点、关联学习记录。 + +### 7.10 Review 模块 + +负责复习卡片、到期复习、间隔复习计划、复习结果、掌握程度选择、下次复习时间。 + +### 7.11 Learning Activity 模块 + +负责学习活跃图:每日学习时长、主动回忆次数、复习次数、AI 分析次数、完成学习闭环次数、学习活跃度等级。 + +活跃图数据最终写入数据库,Redis 可做临时累计。 + +### 7.12 Notifications 模块 + +负责复习提醒、AI 分析完成提醒、学习建议、系统通知、已读/未读。 + +### 7.13 Feedback 模块 + +负责 Web 和 App 反馈:用户反馈、问题类型、邮箱、设备、处理状态。 + +### 7.14 System 模块 + +健康检查、应用状态、基础运维接口。 + +--- + +## 8. 模块优先级 + +### v0.1 必须实现 + +```text +auth +users +knowledge-base +knowledge-items +learning-session +active-recall +ai-analysis +focus-items +review +learning-activity +feedback +system +``` + +### v0.1 暂缓 + +```text +document-import — 先手动录入内容 +notifications — 先不做推送 +billing — 暂无付费 +subscription — 暂无订阅 +admin-dashboard — 暂无管理后台 +team — 单人使用 +``` + +--- + +## 9. API 路由规划 + +```text +GET /api/health + +POST /api/auth/apple +POST /api/auth/refresh +POST /api/auth/logout + +GET /api/users/me +PATCH /api/users/me +PATCH /api/users/me/preferences + +GET /api/knowledge-bases +POST /api/knowledge-bases +GET /api/knowledge-bases/:id +PATCH /api/knowledge-bases/:id +DELETE /api/knowledge-bases/:id +GET /api/knowledge-bases/:id/items + +GET /api/knowledge-items/:id +POST /api/knowledge-items + +POST /api/imports +GET /api/imports/:id/status + +POST /api/learning-sessions +POST /api/learning-sessions/:id/end + +GET /api/active-recalls +POST /api/active-recalls/:id/submit + +POST /api/ai-analysis +GET /api/ai-analysis/:id +GET /api/ai-analysis/jobs/:jobId/status + +GET /api/focus-items +POST /api/focus-items +PATCH /api/focus-items/:id +POST /api/focus-items/:id/complete + +GET /api/reviews/due +POST /api/reviews/:id/submit + +GET /api/activity/heatmap +GET /api/activity/summary + +GET /api/notifications +POST /api/notifications/:id/read + +POST /api/feedback +``` + +--- + +## 10. 关键请求流程 + +核心异步流程详见 [23. 异步模块额外文件 - AI 分析模块完整流程](#23-异步模块额外文件queue--processor)。 + +--- + +## 11. 核心 AI 接口 + +### POST /api/ai-analysis(提交异步任务) + +输入: +```json +{ + "sessionId": "session_001", + "lessonId": "lesson_001", + "userInput": "用户写下的答案或笔记", + "context": { + "lessonTitle": "材料阅读方法", + "objectives": ["理解材料结构", "提取关键要点"], + "keyPoints": ["问题", "原因", "影响", "对策"] + } +} +``` + +返回: +```json +{ + "jobId": "job_abc123", + "status": "pending" +} +``` + +### GET /api/ai-analysis/jobs/:jobId/status(查询状态) + +```json +{ + "jobId": "job_abc123", + "status": "completed", + "result": { + "masteryScore": 3, + "understandingLevel": "基本理解", + "summary": "用户能理解主要内容,但要点不完整。", + "strengths": ["表达清楚"], + "weakPoints": ["遗漏关键要点", "逻辑层次不足"], + "suggestions": ["补充第二个要点", "先概括再展开"], + "reviewNeeded": true, + "nextAction": "明天重新回答本节主动回忆问题。" + } +} +``` + +--- + +## 12. AI Provider 层设计 + +```text +AIService + ├── MiniMaxProvider + ├── DeepSeekProvider + ├── OpenAIProvider + └── MockProvider +``` + +### MockProvider + +v0.1 强烈建议做 MockProvider: +- 没有 API Key 时也能开发 +- AI 服务出问题时能测试流程 +- 降低开发成本 +- 方便录 Demo + +### 候选模型 + +MiniMax / DeepSeek / 通义千问 / OpenAI / Claude / Gemini + +### 模型选择原则 + +1. 中文能力好 +2. 成本可控 +3. API 稳定 +4. 支持结构化输出 +5. 速度可以接受 +6. 容易接入 +7. 可替换 + +--- + +## 13. 核心实体/数据模型 + +```text +User +├── id +├── appleUserId +├── displayName +├── email +├── preferredLanguage +├── learningDirection +├── createdAt +├── lastLoginAt +└── status + +KnowledgeBase +├── id +├── userId +├── title +├── description +├── language +├── tags +├── createdAt +└── updatedAt + +KnowledgeItem +├── id +├── knowledgeBaseId +├── parentId +├── title +├── content +├── type (chapter/section/concept) +├── objectives +├── keyPoints +├── recallQuestions +├── order +└── estimatedMinutes + +DocumentImport +├── id +├── userId +├── knowledgeBaseId +├── sourceType (file/text/link) +├── status (pending/processing/completed/failed) +├── result +├── createdAt +└── completedAt + +LearningSession +├── id +├── userId +├── knowledgeItemId +├── startedAt +├── endedAt +├── durationSeconds +├── mode (reading/recall/review) +└── completedAt + +ActiveRecall +├── id +├── sessionId +├── question +├── userAnswer +├── answerType (text/voice) +├── submittedAt + +AIAnalysis +├── id +├── jobId +├── userId +├── sessionId +├── inputText +├── outputJson +├── masteryScore +├── weakPoints +├── suggestions +├── modelName +├── costEstimate +├── status (pending/processing/completed/failed) +├── createdAt +└── completedAt + +FocusItem +├── id +├── userId +├── knowledgeItemId +├── aiAnalysisId +├── title +├── description +├── status (pending/in_review/completed) +├── createdAt +└── completedAt + +ReviewTask +├── id +├── userId +├── knowledgeItemId +├── focusItemId +├── reviewType +├── scheduledAt +├── completedAt +├── masteryChoice +├── nextReviewAt +└── status + +LearningActivity +├── id +├── userId +├── date +├── durationSeconds +├── recallCount +├── reviewCount +├── analysisCount +├── closedLoopCount +├── activityLevel +└── updatedAt + +Notification +├── id +├── userId +├── type +├── title +├── body +├── data (JSON) +├── isRead +└── createdAt + +Feedback +├── id +├── userId +├── type +├── content +├── email +├── device +├── status +└── createdAt +``` + +--- + +## 14. 掌握度评分(0-5分) + +```text +0 = 没有作答 / 无法判断 +1 = 基本没理解 +2 = 理解较弱 +3 = 基本理解 +4 = 理解较好 +5 = 掌握很好 +``` + +这不是考试分数,是产品内部用于安排复习和学习建议的参考值。 + +--- + +## 15. 账号体系 + +### 第一版登录方式 + +```text +Sign in with Apple +``` + +暂不做:微信登录、手机号登录、邮箱密码登录、Google 登录。 + +### 账号边界 + +第一版账号只解决:登录、识别用户、保存学习记录、保存 AI 分析结果、保存基础偏好。 + +暂不做:好友系统、用户主页、头像上传、多登录方式绑定、复杂权限系统。 + +--- + +## 16. 部署方案 + +### 当前服务器 + +- IP: 81.70.187.179 +- 配置: 4 核 4G,40G SSD +- 当前运行: Gitea(端口 3000)+ Nginx +- SSH: ubuntu 用户 + WangDL.pem 密钥 + +### 推荐部署方式 + +```text +Nginx — 反向代理,已有 +api-server (NestJS) — 端口 3001 +postgres / mysql — 端口 5432 / 3306 +redis — 端口 6379 +worker (BullMQ) — 同代码仓库,独立进程 +gitea — 端口 3000,继续保留 +``` + +建议新增 Nginx 配置 `api.longde.cloud` → 反向代理到 NestJS 端口。 + +### 资源评估 + +Gitea 当前资源占用很低(内存 ~500MB),剩余 ~3GB 内存、33GB 磁盘足够跑 api-server + PostgreSQL + Redis。 + +--- + +## 17. 域名与 Nginx 规划 + +```text +longde.cloud 官网首页 +api.longde.cloud 后端 API(需新增 Nginx 配置) +git.longde.cloud Gitea 代码仓库(已有) +``` + +### 待新增 Nginx 配置(api.longde.cloud) + +```nginx +server { + server_name api.longde.cloud; + location / { + proxy_pass http://127.0.0.1:3001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + listen 443 ssl; + ssl_certificate /etc/letsencrypt/live/api.longde.cloud/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.longde.cloud/privkey.pem; +} +``` + +--- + +## 18. 技术里程碑 + +### 里程碑 1:最小后端 + +- NestJS 项目初始化 +- Prisma schema 定义 +- PostgreSQL/MySQL 连接 +- 用户表、知识库表 +- AI MockProvider +- Swagger 文档 + +### 里程碑 2:真实 AI 接入 + +- Redis + BullMQ 接入 +- AI Provider 抽象 +- 接入一个真实模型 +- AI 分析异步流程 +- Worker 处理 AI 任务 + +### 里程碑 3:TestFlight 前准备 + +- Sign in with Apple +- 后端用户身份记录 +- 基础错误处理 +- Docker Compose 一键部署 +- Nginx 反向代理配置 + +--- + +## 19. 商业化与支付(未来) + +### 支付流程 + +```text +用户在 iOS App 内付款 → Apple IAP 完成交易 → 后端记录用户权益 → 用户获得权益 +``` + +### 核心 + +不要让 Apple IAP 成为整个商业系统。真正核心的是: + +```text +用户系统 + 订单系统 + 权益系统 +``` + +### 当前暂缓 + +Apple IAP / 月订阅 / 年订阅 / 免费试用 / 订阅权益系统 / AI 成本精算 + +--- + +## 20. 当前技术不做清单 + +- 微服务 / Kubernetes / 多云部署 +- 完整后台管理系统 / CMS +- 完整 RAG / 向量数据库(第一版) +- 多模型自动路由 +- AI Agent 工具调用系统 +- 支付系统 +- 复杂数据分析平台 +- Android / Web 学习端 + +--- + +## 21. 四层代码分层原则 + +整体分四层,每一层有明确的职责边界: + +```text +Controller 层:接收请求,处理参数,返回响应 +Service 层:业务逻辑 +Repository 层:数据库读写 +Infrastructure 层:Redis、Queue、AI、Storage 等基础设施 +``` + +简单理解: + +```text +Controller 不写业务 +Service 不直接写复杂 SQL +Repository 不写业务判断 +Infrastructure 不知道具体业务,只提供能力 +``` + +一句话纪律: + +```text +Controller = 接口 Service = 业务 Repository = 数据库 +Infrastructure = 外部能力 Queue/Processor = 异步任务 DTO = 请求/响应结构 +``` + +第一版不要追求所有业务一次写完,但**架构模板必须先统一**。 + +--- + +## 22. 模块内部统一模板 + +每个业务模块都按这个结构来。以 `knowledge-base` 为例: + +```text +modules/knowledge-base/ +├── knowledge-base.module.ts +├── knowledge-base.controller.ts +├── knowledge-base.service.ts +├── knowledge-base.repository.ts +├── dto/ +│ ├── create-knowledge-base.dto.ts +│ ├── update-knowledge-base.dto.ts +│ ├── query-knowledge-base.dto.ts +│ └── knowledge-base-response.dto.ts +├── types/ +│ └── knowledge-base.types.ts +└── constants/ + └── knowledge-base.constants.ts +``` + +### 22.1 xxx.module.ts + +```text +作用: +- 注册 controller +- 注册 service +- 注册 repository +- 引入依赖模块 +- 导出给其他模块使用 +``` + +```ts +@Module({ + controllers: [KnowledgeBaseController], + providers: [KnowledgeBaseService, KnowledgeBaseRepository], + exports: [KnowledgeBaseService], +}) +export class KnowledgeBaseModule {} +``` + +### 22.2 xxx.controller.ts + +**只做这些事:** +1. 定义路由 +2. 接收参数 +3. 调用 service +4. 返回结果 + +**不要在 Controller 里写业务判断。** + +```ts +@Controller('knowledge-bases') +export class KnowledgeBaseController { + constructor(private readonly service: KnowledgeBaseService) {} + + @Post() + create(@Body() dto: CreateKnowledgeBaseDto, @CurrentUser() user: UserPayload) { + return this.service.create(user.id, dto); + } + + @Get() + findAll(@CurrentUser() user: UserPayload, @Query() query: QueryKnowledgeBaseDto) { + return this.service.findAll(user.id, query); + } + + @Get(':id') + findOne(@Param('id') id: string, @CurrentUser() user: UserPayload) { + return this.service.findOne(user.id, id); + } +} +``` + +### 22.3 xxx.service.ts + +**这里写:** +1. 权限判断 +2. 业务规则 +3. 调用 repository +4. 调用其他模块 service +5. 调用 queue / AI / Redis +6. 组织返回结果 + +```ts +@Injectable() +export class KnowledgeBaseService { + constructor( + private readonly repository: KnowledgeBaseRepository, + ) {} + + async create(userId: string, dto: CreateKnowledgeBaseDto) { + const count = await this.repository.countByUserId(userId); + if (count >= MAX_KNOWLEDGE_BASE_COUNT) { + throw new BadRequestException('知识库数量已达到上限'); + } + return this.repository.create(userId, dto); + } + + async findOne(userId: string, id: string) { + const knowledgeBase = await this.repository.findById(id); + if (!knowledgeBase || knowledgeBase.userId !== userId) { + throw new NotFoundException('知识库不存在'); + } + return knowledgeBase; + } +} +``` + +### 22.4 xxx.repository.ts + +**只写数据库操作,不写复杂业务。** + +```ts +@Injectable() +export class KnowledgeBaseRepository { + constructor(private readonly prisma: PrismaService) {} + + create(userId: string, dto: CreateKnowledgeBaseDto) { + return this.prisma.knowledgeBase.create({ + data: { userId, name: dto.name, description: dto.description }, + }); + } + + findById(id: string) { + return this.prisma.knowledgeBase.findUnique({ where: { id } }); + } + + countByUserId(userId: string) { + return this.prisma.knowledgeBase.count({ where: { userId } }); + } +} +``` + +### 22.5 dto/ + +DTO 就是接口输入输出的数据结构。 + +用途: +1. 校验请求参数 +2. 生成 Swagger 文档 +3. 让接口结构清楚 + +```ts +export class CreateKnowledgeBaseDto { + @ApiProperty({ example: '认知心理学' }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ example: '系统学习认知心理学基础概念', required: false }) + @IsOptional() + @IsString() + description?: string; +} +``` + +### 22.6 types/ + +放模块内部类型。如果是数据库模型,不放这里,放 Prisma。 + +```ts +export type KnowledgeBaseStatus = 'active' | 'archived'; + +export interface KnowledgeBaseStats { + itemCount: number; + completedCount: number; + reviewDueCount: number; +} +``` + +### 22.7 constants/ + +放模块常量。 + +```ts +export const MAX_KNOWLEDGE_BASE_COUNT = 20; +export const DEFAULT_KNOWLEDGE_BASE_COVER = 'default-blue'; +``` + +--- + +## 23. 异步模块额外文件(Queue + Processor) + +涉及异步任务的模块,需要在标准模板上加 `queue`、`processor`、`jobs`。 + +以 `ai-analysis` 为例: + +```text +modules/ai-analysis/ +├── ai-analysis.module.ts +├── ai-analysis.controller.ts +├── ai-analysis.service.ts +├── ai-analysis.repository.ts +├── ai-analysis.queue.ts ← 封装 BullMQ 队列 +├── ai-analysis.processor.ts ← 消费任务,调用大模型 +├── dto/ +│ ├── create-ai-analysis.dto.ts +│ ├── query-ai-analysis.dto.ts +│ └── ai-analysis-response.dto.ts +├── jobs/ +│ └── ai-analysis.job.ts ← 队列任务数据结构 +├── types/ +│ └── ai-analysis.types.ts +└── constants/ + └── ai-analysis.constants.ts +``` + +### 文件职责 + +| 文件 | 职责 | +|------|------| +| `controller` | 接收创建分析任务、查询分析结果的接口 | +| `service` | 创建任务、查询状态、组织业务流程 | +| `repository` | 读写 AI 分析任务和分析结果 | +| `queue` | 封装 BullMQ 队列添加任务 | +| `processor` | 真正消费任务,调用大模型 | +| `jobs/xxx.job.ts` | 定义队列任务的数据结构 | + +### AI 分析模块完整流程 + +```text +App 提交回答 + ↓ +Controller 接收请求 + ↓ +Service 创建分析任务 + ↓ +Repository 写入数据库 job 记录 + ↓ +Queue 加入 Redis 队列 + ↓ +Processor 后台消费任务 + ↓ +AI Service 调用大模型 + ↓ +Repository 写入分析结果 + ↓ +生成待巩固项 + ↓ +生成复习计划 + ↓ +通知用户 +``` + +--- + +## 24. AI 层设计:不允许业务模块直接调大模型 SDK + +### 原则 + +后面肯定会换模型、换供应商,所以 AI 不要写死在业务模块里。 + +**不推荐:** +```ts +// 不要直接在业务 service 里写 +await openai.chat.completions.create(...) +``` + +**应该:** +```text +业务模块 → AiService → 具体 Provider +``` + +```ts +@Injectable() +export class AiService { + constructor(private readonly provider: AiProvider) {} + + analyzeActiveRecall(input: ActiveRecallAnalysisInput) { + return this.provider.generateActiveRecallAnalysis(input); + } +} +``` + +这样从 OpenAI 换 DeepSeek、Gemini、Claude,都不用改业务代码。 + +--- + +## 25. Infrastructure 层详细拆解 + +`infrastructure` 是所有模块共用的底层能力。 + +```text +infrastructure/ +├── database/ +│ ├── prisma.module.ts +│ └── prisma.service.ts +│ +├── redis/ +│ ├── redis.module.ts +│ ├── redis.service.ts +│ └── redis.constants.ts +│ +├── queue/ +│ ├── queue.module.ts +│ ├── queue.constants.ts +│ └── queue.service.ts +│ +├── ai/ +│ ├── ai.module.ts +│ ├── ai.service.ts +│ ├── ai-provider.interface.ts +│ ├── providers/ +│ │ ├── openai.provider.ts +│ │ ├── deepseek.provider.ts +│ │ └── mock-ai.provider.ts +│ └── prompts/ +│ ├── active-recall-analysis.prompt.ts +│ └── focus-item-generation.prompt.ts +│ +├── storage/ +│ ├── storage.module.ts +│ ├── storage.service.ts +│ └── local-storage.provider.ts +│ +└── logger/ + ├── logger.module.ts + └── app-logger.service.ts +``` + +--- + +## 26. Common 层详细拆解 + +```text +common/ +├── decorators/ +│ └── current-user.decorator.ts +├── guards/ +│ ├── jwt-auth.guard.ts +│ └── optional-auth.guard.ts +├── interceptors/ +│ └── response.interceptor.ts +├── filters/ +│ └── http-exception.filter.ts +├── pipes/ +│ └── validation.pipe.ts +├── dto/ +│ ├── pagination.dto.ts +│ └── api-response.dto.ts +├── types/ +│ ├── user-payload.type.ts +│ └── pagination.type.ts +├── constants/ +│ └── app.constants.ts +└── utils/ + ├── date.util.ts + ├── hash.util.ts + └── id.util.ts +``` + +--- + +## 27. 模块复杂度分级 + +### 简单模块 + +结构 `module + controller + service + repository + dto` + +适用于:`feedback`、`system`、`notifications`(初版) + +### 中等模块 + +结构 `module + controller + service + repository + dto + types + constants` + +适用于:`users`、`knowledge-base`、`knowledge-items`、`learning-session`、`review`、`focus-items` + +### 复杂模块 + +结构 `module + controller + service + repository + queue + processor + jobs + dto + types + constants` + +适用于:`ai-analysis`、`document-import`、`notifications`(后期) + +--- + +## 28. 初始化顺序 + +第一版不要所有业务都写完。先搭基础设施骨架: + +```text + 1. infrastructure/database + 2. infrastructure/redis + 3. infrastructure/queue + 4. infrastructure/ai + 5. common + 6. system/health + 7. auth skeleton + 8. users skeleton + 9. knowledge-base skeleton +10. ai-analysis skeleton +``` + +这样就够开始开发。 + + diff --git a/docs/DATABASE-DESIGN.md b/docs/DATABASE-DESIGN.md new file mode 100644 index 0000000..e1832f3 --- /dev/null +++ b/docs/DATABASE-DESIGN.md @@ -0,0 +1,814 @@ +--- +source: AI回答.md +updated: 2026-05-09 +--- + +# 知习 MySQL 数据库表结构设计 + +> 共 27 张表,v0.1 先建 24 张核心表。 + +--- + +## 通用字段规范 + +每张核心表统一使用: + +```sql +id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, +created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, +updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +deleted_at DATETIME NULL +``` + +- `id`:内部主键 +- `created_at`:创建时间 +- `updated_at`:更新时间 +- `deleted_at`:软删除 + +状态字段统一用 `VARCHAR(32)`,不用 MySQL ENUM。 + +--- + +## 一、用户与认证表(5 张) + +### 1. users 用户表 + +```sql +users +- id BIGINT UNSIGNED PK +- email VARCHAR(255) NULL +- nickname VARCHAR(100) NULL +- avatar_url VARCHAR(500) NULL +- status VARCHAR(32) NOT NULL DEFAULT 'active' +- onboarding_completed TINYINT(1) NOT NULL DEFAULT 0 +- last_login_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +- deleted_at DATETIME NULL +``` + +索引: +```sql +INDEX idx_users_email (email) +INDEX idx_users_status (status) +``` + +--- + +### 2. auth_accounts 第三方登录账号表 + +```sql +auth_accounts +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- provider VARCHAR(32) NOT NULL -- apple +- provider_user_id VARCHAR(255) NOT NULL -- Apple userIdentifier / sub +- email VARCHAR(255) NULL +- raw_profile_json JSON NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_provider_user (provider, provider_user_id) +INDEX idx_auth_accounts_user_id (user_id) +``` + +--- + +### 3. refresh_tokens 刷新 Token 表 + +```sql +refresh_tokens +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- token_hash VARCHAR(255) NOT NULL +- device_id VARCHAR(255) NULL +- device_name VARCHAR(255) NULL +- expires_at DATETIME NOT NULL +- revoked_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_refresh_tokens_user_id (user_id) +INDEX idx_refresh_tokens_token_hash (token_hash) +``` + +--- + +### 4. user_profiles 用户资料扩展表 + +```sql +user_profiles +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- learning_identity VARCHAR(100) NULL -- 系统学习者 / 备考用户 / 知识工作者 +- learning_direction VARCHAR(255) NULL -- 认知科学 / AIGC / 产品设计 +- bio TEXT NULL +- current_goal VARCHAR(255) NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_user_profiles_user_id (user_id) +``` + +--- + +### 5. user_preferences 用户学习偏好表 + +```sql +user_preferences +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- preferred_methods JSON NULL + -- ["active_recall", "spaced_repetition", "feynman", "retrieval_practice"] +- default_focus_minutes INT NOT NULL DEFAULT 25 +- ai_suggestion_level VARCHAR(32) NOT NULL DEFAULT 'normal' + -- low / normal / high +- language VARCHAR(32) NOT NULL DEFAULT 'zh-CN' +- appearance VARCHAR(32) NOT NULL DEFAULT 'system' +- notification_enabled TINYINT(1) NOT NULL DEFAULT 1 +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_user_preferences_user_id (user_id) +``` + +--- + +## 二、知识库相关表(5 张) + +### 6. knowledge_bases 知识库表 + +```sql +knowledge_bases +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- title VARCHAR(255) NOT NULL +- description TEXT NULL +- cover_key VARCHAR(100) NULL +- status VARCHAR(32) NOT NULL DEFAULT 'active' + -- active / archived / deleted +- item_count INT NOT NULL DEFAULT 0 +- last_studied_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +- deleted_at DATETIME NULL +``` + +索引: +```sql +INDEX idx_knowledge_bases_user_id (user_id) +INDEX idx_knowledge_bases_status (status) +``` + +--- + +### 7. knowledge_items 知识点/内容表 + +```sql +knowledge_items +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_base_id BIGINT UNSIGNED NOT NULL +- parent_id BIGINT UNSIGNED NULL +- item_type VARCHAR(32) NOT NULL + -- chapter / lesson / concept / note / imported_doc +- title VARCHAR(255) NOT NULL +- content LONGTEXT NULL +- summary TEXT NULL +- source_type VARCHAR(32) NULL + -- manual / file / url / ai_generated +- source_ref VARCHAR(500) NULL +- order_index INT NOT NULL DEFAULT 0 +- status VARCHAR(32) NOT NULL DEFAULT 'active' +- created_at DATETIME +- updated_at DATETIME +- deleted_at DATETIME NULL +``` + +索引: +```sql +INDEX idx_knowledge_items_user_id (user_id) +INDEX idx_knowledge_items_kb_id (knowledge_base_id) +INDEX idx_knowledge_items_parent_id (parent_id) +INDEX idx_knowledge_items_type (item_type) +``` + +--- + +### 8. knowledge_item_relations 知识点关联表 + +```sql +knowledge_item_relations +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- source_item_id BIGINT UNSIGNED NOT NULL +- target_item_id BIGINT UNSIGNED NOT NULL +- relation_type VARCHAR(32) NOT NULL + -- related / prerequisite / similar / conflict / extension +- confidence DECIMAL(5,2) NULL +- reason TEXT NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_relations_source (source_item_id) +INDEX idx_relations_target (target_item_id) +``` + +--- + +### 9. tags 标签表 + +```sql +tags +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- name VARCHAR(100) NOT NULL +- color VARCHAR(32) NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_user_tag_name (user_id, name) +``` + +--- + +### 10. knowledge_item_tags 知识点标签关联表 + +```sql +knowledge_item_tags +- id BIGINT UNSIGNED PK +- knowledge_item_id BIGINT UNSIGNED NOT NULL +- tag_id BIGINT UNSIGNED NOT NULL +- created_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_item_tag (knowledge_item_id, tag_id) +``` + +--- + +## 三、资料导入相关表(2 张) + +### 11. uploaded_files 上传文件表 + +```sql +uploaded_files +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- filename VARCHAR(255) NOT NULL +- mime_type VARCHAR(100) NULL +- storage_path VARCHAR(500) NOT NULL +- size_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0 +- checksum VARCHAR(255) NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_uploaded_files_user_id (user_id) +``` + +--- + +### 12. document_imports 资料导入任务表 + +```sql +document_imports +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_base_id BIGINT UNSIGNED NULL +- file_id BIGINT UNSIGNED NULL +- source_type VARCHAR(32) NOT NULL + -- file / text / url +- source_name VARCHAR(255) NULL +- source_url VARCHAR(500) NULL +- raw_text LONGTEXT NULL +- status VARCHAR(32) NOT NULL DEFAULT 'pending' + -- pending / processing / success / failed +- progress INT NOT NULL DEFAULT 0 +- error_message TEXT NULL +- result_json JSON NULL +- started_at DATETIME NULL +- completed_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_document_imports_user_id (user_id) +INDEX idx_document_imports_status (status) +``` + +--- + +## 四、学习过程相关表(2 张) + +### 13. learning_sessions 学习会话表 + +```sql +learning_sessions +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_base_id BIGINT UNSIGNED NULL +- knowledge_item_id BIGINT UNSIGNED NULL +- mode VARCHAR(32) NOT NULL + -- reading / active_recall / review / feynman / free_learning +- status VARCHAR(32) NOT NULL DEFAULT 'active' + -- active / completed / cancelled +- started_at DATETIME NOT NULL +- ended_at DATETIME NULL +- duration_seconds INT NOT NULL DEFAULT 0 +- focus_minutes INT NULL +- metadata JSON NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_learning_sessions_user_id (user_id) +INDEX idx_learning_sessions_item_id (knowledge_item_id) +INDEX idx_learning_sessions_started_at (started_at) +``` + +--- + +### 14. learning_records 学习记录表 + +类似 GitHub commit log,语义是学习记录。 + +```sql +learning_records +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- session_id BIGINT UNSIGNED NULL +- record_type VARCHAR(32) NOT NULL + -- read / active_recall / review / ai_analysis / focus_item_completed +- title VARCHAR(255) NOT NULL +- description TEXT NULL +- duration_seconds INT NOT NULL DEFAULT 0 +- occurred_at DATETIME NOT NULL +- metadata JSON NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_learning_records_user_id (user_id) +INDEX idx_learning_records_occurred_at (occurred_at) +``` + +--- + +## 五、主动回忆相关表(2 张) + +### 15. active_recall_questions 主动回忆问题表 + +```sql +active_recall_questions +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_item_id BIGINT UNSIGNED NULL +- question_text TEXT NOT NULL +- difficulty VARCHAR(32) NULL + -- easy / normal / hard +- created_by VARCHAR(32) NOT NULL DEFAULT 'ai' + -- ai / user / system +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_recall_questions_user_id (user_id) +INDEX idx_recall_questions_item_id (knowledge_item_id) +``` + +--- + +### 16. active_recall_answers 主动回忆回答表 + +```sql +active_recall_answers +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- question_id BIGINT UNSIGNED NULL +- session_id BIGINT UNSIGNED NULL +- answer_type VARCHAR(32) NOT NULL DEFAULT 'text' + -- text / voice +- answer_text LONGTEXT NULL +- audio_file_id BIGINT UNSIGNED NULL +- submitted_at DATETIME NOT NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_recall_answers_user_id (user_id) +INDEX idx_recall_answers_question_id (question_id) +INDEX idx_recall_answers_session_id (session_id) +``` + +--- + +## 六、AI 分析相关表(2 张) + +### 17. ai_analysis_jobs AI 分析任务表 + +```sql +ai_analysis_jobs +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- session_id BIGINT UNSIGNED NULL +- answer_id BIGINT UNSIGNED NULL +- job_type VARCHAR(32) NOT NULL + -- active_recall_analysis / weak_point_detection / review_generation +- status VARCHAR(32) NOT NULL DEFAULT 'pending' + -- pending / processing / success / failed +- progress INT NOT NULL DEFAULT 0 +- error_message TEXT NULL +- queued_at DATETIME NULL +- started_at DATETIME NULL +- completed_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_ai_jobs_user_id (user_id) +INDEX idx_ai_jobs_status (status) +INDEX idx_ai_jobs_session_id (session_id) +``` + +--- + +### 18. ai_analysis_results AI 分析结果表 + +```sql +ai_analysis_results +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- job_id BIGINT UNSIGNED NOT NULL +- session_id BIGINT UNSIGNED NULL +- answer_id BIGINT UNSIGNED NULL +- summary TEXT NULL +- mastery_score INT NULL -- 0-100 +- strengths JSON NULL +- weaknesses JSON NULL +- suggestions JSON NULL +- next_actions JSON NULL +- raw_result JSON NULL +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_ai_results_user_id (user_id) +INDEX idx_ai_results_job_id (job_id) +INDEX idx_ai_results_session_id (session_id) +``` + +--- + +## 七、待巩固项表(1 张) + +### 19. focus_items 待巩固项表 + +类似 GitHub issue,学习语义叫「待巩固项」。 + +```sql +focus_items +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_base_id BIGINT UNSIGNED NULL +- knowledge_item_id BIGINT UNSIGNED NULL +- analysis_result_id BIGINT UNSIGNED NULL +- title VARCHAR(255) NOT NULL +- reason TEXT NULL +- suggestion TEXT NULL +- priority VARCHAR(32) NOT NULL DEFAULT 'normal' + -- low / normal / high +- status VARCHAR(32) NOT NULL DEFAULT 'open' + -- open / in_review / completed / ignored +- mastery_score INT NULL +- due_at DATETIME NULL +- completed_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +- deleted_at DATETIME NULL +``` + +索引: +```sql +INDEX idx_focus_items_user_id (user_id) +INDEX idx_focus_items_status (status) +INDEX idx_focus_items_due_at (due_at) +``` + +--- + +## 八、复习相关表(3 张) + +### 20. review_cards 复习卡片表 + +```sql +review_cards +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- knowledge_item_id BIGINT UNSIGNED NULL +- focus_item_id BIGINT UNSIGNED NULL +- front_text TEXT NOT NULL +- back_text TEXT NULL +- difficulty VARCHAR(32) NULL +- status VARCHAR(32) NOT NULL DEFAULT 'active' + -- active / suspended / completed +- next_review_at DATETIME NULL +- interval_days INT NOT NULL DEFAULT 1 +- ease_factor DECIMAL(4,2) NOT NULL DEFAULT 2.50 +- repetition_count INT NOT NULL DEFAULT 0 +- lapse_count INT NOT NULL DEFAULT 0 +- created_at DATETIME +- updated_at DATETIME +- deleted_at DATETIME NULL +``` + +索引: +```sql +INDEX idx_review_cards_user_id (user_id) +INDEX idx_review_cards_next_review_at (next_review_at) +INDEX idx_review_cards_focus_item_id (focus_item_id) +``` + +--- + +### 21. review_logs 复习记录表 + +```sql +review_logs +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- review_card_id BIGINT UNSIGNED NOT NULL +- session_id BIGINT UNSIGNED NULL +- rating VARCHAR(32) NOT NULL + -- again / hard / good / easy +- response_text TEXT NULL +- reviewed_at DATETIME NOT NULL +- next_review_at DATETIME NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_review_logs_user_id (user_id) +INDEX idx_review_logs_card_id (review_card_id) +INDEX idx_review_logs_reviewed_at (reviewed_at) +``` + +--- + +### 22. review_plans 复习计划表 + +```sql +review_plans +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- title VARCHAR(255) NOT NULL +- status VARCHAR(32) NOT NULL DEFAULT 'active' + -- active / completed / cancelled +- scheduled_at DATETIME NULL +- completed_at DATETIME NULL +- card_count INT NOT NULL DEFAULT 0 +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_review_plans_user_id (user_id) +INDEX idx_review_plans_scheduled_at (scheduled_at) +``` + +--- + +## 九、学习活跃记录表(1 张) + +### 23. daily_learning_activities 每日学习活跃表 + +用于个人中心的蓝色学习活跃图。 + +```sql +daily_learning_activities +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- activity_date DATE NOT NULL +- duration_seconds INT NOT NULL DEFAULT 0 +- sessions_count INT NOT NULL DEFAULT 0 +- active_recall_count INT NOT NULL DEFAULT 0 +- review_count INT NOT NULL DEFAULT 0 +- ai_analysis_count INT NOT NULL DEFAULT 0 +- completed_loop_count INT NOT NULL DEFAULT 0 +- activity_level INT NOT NULL DEFAULT 0 -- 0-4,颜色深浅 +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +UNIQUE KEY uk_user_activity_date (user_id, activity_date) +INDEX idx_daily_activity_user_id (user_id) +``` + +--- + +## 十、通知与反馈表(2 张) + +### 24. notifications 消息通知表 + +```sql +notifications +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- type VARCHAR(32) NOT NULL + -- review_due / ai_analysis_done / learning_suggestion / system +- title VARCHAR(255) NOT NULL +- content TEXT NULL +- data JSON NULL +- read_at DATETIME NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_notifications_user_id (user_id) +INDEX idx_notifications_read_at (read_at) +INDEX idx_notifications_type (type) +``` + +--- + +### 25. feedbacks 用户反馈表 + +```sql +feedbacks +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NULL +- email VARCHAR(255) NULL +- category VARCHAR(64) NOT NULL + -- feature / bug / experience / privacy / other +- content TEXT NOT NULL +- device_info JSON NULL +- status VARCHAR(32) NOT NULL DEFAULT 'open' + -- open / processing / resolved / ignored +- created_at DATETIME +- updated_at DATETIME +``` + +索引: +```sql +INDEX idx_feedbacks_user_id (user_id) +INDEX idx_feedbacks_status (status) +``` + +--- + +## 十一、合规与系统表(2 张) + +### 26. user_consents 用户协议同意记录表 + +```sql +user_consents +- id BIGINT UNSIGNED PK +- user_id BIGINT UNSIGNED NOT NULL +- consent_type VARCHAR(32) NOT NULL + -- privacy_policy / terms_of_service +- version VARCHAR(50) NOT NULL +- accepted_at DATETIME NOT NULL +- ip_address VARCHAR(100) NULL +- user_agent VARCHAR(500) NULL +- created_at DATETIME +``` + +索引: +```sql +INDEX idx_user_consents_user_id (user_id) +INDEX idx_user_consents_type (consent_type) +``` + +--- + +### 27. app_changelogs 更新记录表(可选) + +```sql +app_changelogs +- id BIGINT UNSIGNED PK +- version VARCHAR(50) NOT NULL +- title VARCHAR(255) NOT NULL +- content TEXT NOT NULL +- platform VARCHAR(32) NOT NULL DEFAULT 'ios' +- published_at DATETIME NULL +- created_at DATETIME +- updated_at DATETIME +``` + +--- + +## v0.1 建表优先级 + +### 第一批(24 张,必须) + +```text +users +auth_accounts +refresh_tokens +user_profiles +user_preferences + +knowledge_bases +knowledge_items +tags +knowledge_item_tags +document_imports +uploaded_files + +learning_sessions +learning_records +active_recall_questions +active_recall_answers + +ai_analysis_jobs +ai_analysis_results +focus_items + +review_cards +review_logs + +daily_learning_activities + +notifications +feedbacks +user_consents +``` + +### 第二批(3 张,可稍后) + +```text +knowledge_item_relations +review_plans +app_changelogs +``` + +--- + +## 模块与表对应关系 + +```text +auth → users, auth_accounts, refresh_tokens +users → user_profiles, user_preferences, user_consents +knowledge-base → knowledge_bases +knowledge-items → knowledge_items, knowledge_item_relations, tags, knowledge_item_tags +document-import → uploaded_files, document_imports +learning-session → learning_sessions, learning_records +active-recall → active_recall_questions, active_recall_answers +ai-analysis → ai_analysis_jobs, ai_analysis_results +focus-items → focus_items +review → review_cards, review_logs, review_plans +learning-activity → daily_learning_activities +notifications → notifications +feedback → feedbacks +system → app_changelogs +``` + +--- + +## Prisma 生成规范 + +```text +所有表使用 BIGINT UNSIGNED AUTO_INCREMENT 主键 +状态字段使用 VARCHAR,不使用 ENUM +JSON 字段用于存储 AI 分析结构化结果、用户偏好、元数据 +核心表添加 created_at、updated_at、deleted_at +为 user_id、status、created_at、外键字段添加合理索引 +``` diff --git a/docs/REDIS-DESIGN.md b/docs/REDIS-DESIGN.md new file mode 100644 index 0000000..149d8cb --- /dev/null +++ b/docs/REDIS-DESIGN.md @@ -0,0 +1,262 @@ +--- +source: AI回答.md +updated: 2026-05-09 +--- + +# 知习 Redis 设计 + +> Redis 在知习里不是"另一个 MySQL",它是系统的**加速器和调度器**。MySQL 存结果,Redis 管过程。 + +--- + +## 1. Redis 定位 + +Redis 不作为主数据库,只负责: + +1. 缓存 +2. 限流 +3. 队列(BullMQ) +4. 临时任务状态 +5. 分布式锁 +6. 防重复提交 +7. AI 调用次数统计 +8. 短期 Token / 黑名单 +9. 通知任务调度 +10. 学习会话草稿 + +--- + +## 2. Key 命名规范 + +统一格式: + +```text +业务域:对象类型:对象ID:字段 +``` + +示例: + +```text +cache:user:123:profile +rate:user:123:ai:daily:2026-05-09 +lock:ai-analysis:session:987 +job:ai-analysis:abc123:status +``` + +规则: + +1. 全部小写 +2. 用冒号 `:` 分隔 +3. 从大范围到小范围 +4. userId、jobId、sessionId 明确写在 key 里 +5. 带日期的 key 用 `YYYY-MM-DD` +6. 所有临时 key 必须设置 TTL + +--- + +## 3. Key 总表 + +### 缓存类 + +| Key | 用途 | TTL | +|-----|------|-----| +| `cache:user:{userId}:profile` | 用户资料 | 5-10 分钟 | +| `cache:user:{userId}:preferences` | 用户偏好设置 | 10 分钟 | +| `cache:user:{userId}:knowledge-bases` | 用户知识库列表 | 3-5 分钟 | +| `cache:knowledge-base:{kbId}:summary` | 知识库摘要 | 5 分钟 | +| `cache:review:user:{userId}:due-count` | 到期复习数量 | 1-3 分钟 | + +### 限流类 + +| Key | 用途 | TTL | +|-----|------|-----| +| `rate:user:{userId}:ai:daily:{date}` | 用户每日 AI 调用次数 | 到当天结束或 24h | +| `rate:user:{userId}:feedback:hourly` | 用户每小时反馈次数 | 1 小时 | +| `rate:ip:{ip}:request:{minute}` | IP 每分钟请求频率 | 60-120 秒 | +| `rate:ip:{ip}:login:{date}` | IP 每日登录尝试 | 10-30 分钟 | + +### 分布式锁类 + +| Key | 用途 | TTL | +|-----|------|-----| +| `lock:ai-analysis:session:{sessionId}` | 防止重复提交 AI 分析 | 60-300 秒 | +| `lock:ai-analysis:answer:{answerId}` | 防止同回答重复分析 | 60-300 秒 | +| `lock:document-import:{importId}` | 防止重复处理导入 | 5-30 分钟 | +| `lock:review-plan:user:{userId}:item:{itemId}` | 防止重复生成复习计划 | 60-300 秒 | +| `lock:feedback:ip:{ip}` | 防止 IP 刷反馈 | 60-300 秒 | + +### 任务状态类 + +| Key | Value 示例 | TTL | +|-----|-----------|-----| +| `job:ai-analysis:{jobId}:status` | `pending / processing / completed / failed` | 24h | +| `job:ai-analysis:{jobId}:progress` | `0-100` | 24h | +| `job:ai-analysis:{jobId}:error` | 错误信息字符串 | 24h | +| `job:document-import:{importId}:status` | `pending / parsing / chunking / generating / completed / failed` | 24h | +| `job:document-import:{importId}:progress` | `0-100` | 24h | +| `job:document-import:{importId}:message` | `"正在提取关键知识点"` | 24h | +| `job:document-import:{importId}:error` | 错误信息字符串 | 24h | + +### 会话临时状态类 + +| Key | 用途 | TTL | +|-----|------|-----| +| `session:learning:{sessionId}:heartbeat` | 学习会话心跳 | 30 分钟 | +| `session:learning:{sessionId}:current-step` | 当前学习步骤 | 2 小时 | +| `session:active-recall:{sessionId}:draft` | 回答草稿暂存 | 1-24 小时 | + +### Token / 黑名单 + +| Key | 用途 | TTL | +|-----|------|-----| +| `auth:refresh-token:blacklist:{tokenId}` | 注销后刷新 Token 失效 | 到 token 过期 | +| `auth:access-token:blacklist:{jwtId}` | 注销后 JWT 失效 | 到 token 过期 | + +### Set 类(可选) + +| Key | 用途 | +|-----|------| +| `set:user:{userId}:reviewed-items:{date}` | 当天已复习项去重 | + +--- + +## 4. Redis 数据类型选择 + +| 类型 | 用途 | 示例 | +|------|------|------| +| **String** | 最常用:缓存 JSON、计数器、状态、锁 | `rate:user:123:ai:daily:2026-05-09 = 8` | +| **Hash** | 可选,任务多字段频繁更新的场景 | `job:ai-analysis:1001 → status=processing, progress=40` | +| **List/Stream** | 队列,BullMQ 自动管理,不需要手动操作 | - | +| **Set** | 去重,如当天已复习项集合 | `set:user:123:reviewed-items:2026-05-09` | +| **Sorted Set** | 后期按时间排序的复习调度,v0.1 先不做 | - | + +--- + +## 5. 核心流程中 Redis 与 MySQL 的配合 + +### 5.1 AI 分析流程 + +```text + 1. MySQL 创建 ai_analysis_jobs + 2. Redis 加入 ai-analysis 队列(BullMQ) + 3. Redis 存 job:xxx:status = processing + 4. Worker 调用 AI + 5. MySQL 写 ai_analysis_results + 6. MySQL 写 focus_items + 7. MySQL 写 review_cards + 8. Redis 存 job:xxx:status = completed + 9. MySQL 写 notifications +``` + +### 5.2 资料导入流程 + +```text + 1. MySQL 创建 document_imports + 2. Redis 加入 document-import 队列(BullMQ) + 3. Redis 存导入进度 + 4. Worker 解析文件 + 5. MySQL 写 knowledge_items + 6. MySQL 更新 document_imports 为 success + 7. Redis 存状态 completed +``` + +### 5.3 学习活跃图流程 + +```text + 1. 用户完成学习动作 + 2. MySQL 写 learning_records + 3. MySQL 更新 daily_learning_activities + 4. Redis 可短期缓存今日活跃统计 + 5. App 查询活跃图优先查 MySQL,必要时加缓存 +``` + +--- + +## 6. BullMQ 队列 + +BullMQ 自动管理 Redis key,不需要手动建。建议预留 3 个队列: + +| 队列名 | 任务数据 | 处理逻辑 | +|--------|---------|---------| +| `ai-analysis` | `{ jobId, userId, sessionId, answerId, jobType }` | 读取回答 → 调 AI → 写结果 → 生成待巩固项 → 生成复习卡片 → 发通知 | +| `document-import` | `{ importId, userId, knowledgeBaseId, sourceType }` | 解析文件 → 提取文本 → 分段 → 生成知识点 → 写 knowledge_items → 更新状态 | +| `notification` | `{ userId, type, title, data }` | 写 notifications 表,后续可扩展 APNs 推送 | + +--- + +## 7. 哪些绝对不能只放 Redis + +以下全部必须写 MySQL,Redis 只能做缓存,不是唯一来源: + +```text +用户资料 → users, user_profiles +知识库内容 → knowledge_bases +知识点内容 → knowledge_items +学习记录 → learning_records +主动回忆回答 → active_recall_answers +AI 分析结果 → ai_analysis_results +待巩固项 → focus_items +复习卡片 → review_cards +复习记录 → review_logs +学习活跃记录 → daily_learning_activities +通知记录 → notifications +用户设置 → user_preferences +协议同意记录 → user_consents +``` + +--- + +## 8. v0.1 Redis 最小落地范围 + +### 必须做 + +```text +1. Redis 连接 +2. /health 检查 Redis +3. RedisModule + RedisService(get/set/del/exists/expire/ttl/incr/setNx/lock/unlock) +4. AI 每日调用限流(rate:user:{userId}:ai:daily:{date}) +5. AI 分析队列(BullMQ ai-analysis) +6. AI 分析任务状态(job:ai-analysis:{jobId}:status/progress/error) +7. 防重复提交锁(lock:ai-analysis:session:{sessionId}) +8. document-import 队列预留(BullMQ document-import) +9. notification 队列预留(BullMQ notification) +``` + +### 暂时不做 + +```text +复杂缓存策略 +Sorted Set 复习调度 +复杂分布式任务调度 +全量通知推送(APNs) +复杂排行榜 +``` + +--- + +## 9. RedisService 方法清单 + +```text +get(key) — 读取 +set(key, value) — 写入 +del(key) — 删除 +exists(key) — 判断存在 +expire(key, ttl) — 设置过期 +ttl(key) — 查看剩余时间 +incr(key) — 自增(限流计数) +setNx(key, value) — 不存在才写入(锁) +lock(key, ttl) — 获取分布式锁,返回 token +unlock(key, token) — 释放锁,校验 token,防止误删 +``` + +锁的实现注意: + +- 锁必须设置 TTL +- 解锁时必须校验 value/token,不能误删别人的锁 +- 锁的 value 用随机 token,解锁时比对 + +--- + +## 10. 一句话总结 + +> **Redis 在知习里不是"另一个 MySQL",它是系统的加速器和调度器。MySQL 存结果,Redis 管过程。** diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..bc2e1ca --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,169 @@ +# 知习 api-server 安全基线 + +> v0.1 安全设计文档。本后端存储用户资料、知识库、上传文件、主动回忆回答、AI 分析结果和学习记录,第一版必须建立基础安全边界。 + +--- + +## 1. 全局安全中间件 + +| 措施 | 实现 | 文件 | +|------|------|------| +| helmet | `app.use(helmet())` 设置安全 HTTP 头 | `src/main.ts` | +| CORS | 仅允许配置域名。生产环境仅允许 `longde.cloud` | `src/main.ts` | +| body size limit | JSON 请求体最大 10MB | `src/main.ts` | +| 异常过滤 | 生产环境不返回 stack trace | `src/common/filters/global-exception.filter.ts` | + +--- + +## 2. 认证与 Token + +### JWT + +- `accessToken`: JWT,1 小时过期 +- `refreshToken`: 128 位随机 hex,入库只存 SHA-256 hash +- logout 时 `revokedAt = now()` 撤销所有 refresh token +- `/users/me` 及其所有子路由强制 `@UseGuards(JwtAuthGuard)` + +``` +POST /auth/apple → 返回 accessToken + refreshToken +POST /auth/refresh → 消耗旧 refreshToken,发放新 token pair(rotation) +POST /auth/logout → 撤销该用户所有 refresh token +``` + +### 存储安全 + +``` +refresh_tokens.tokenHash = SHA-256(实际 token) +数据库中永远不存明文 refreshToken +``` + +--- + +## 3. 权限与越权防护 + +### 资源归属校验 + +所有用户资源操作必须校验 `userId` 归属: + +```ts +// src/common/utils/security.util.ts +export async function findByIdAndUserId(delegate, id, userId, resourceName) +export function ensureOwnership(record, userId, resourceName) +``` + +### 需校验的资源 + +| 资源 | 校验字段 | +|------|---------| +| KnowledgeBase | `userId` | +| KnowledgeItem | `userId` | +| LearningSession | `userId` | +| ActiveRecallAnswer | `userId` | +| AiAnalysisJob | `userId` | +| AiAnalysisResult | `userId` | +| FocusItem | `userId` | +| ReviewCard | `userId` | +| ReviewLog | `userId` | +| DocumentImport | `userId` | + +--- + +## 4. 参数校验 + +- 全局 `StrictValidationPipe`: + - `whitelist: true` — 自动剥离未声明字段 + - `forbidNonWhitelisted: true` — 未知字段返回 400 + - 字符串字段最大长度 5000 字符 +- 分页 DTO: page≥1, limit 1-100 + +--- + +## 5. 限流(Redis) + +| 场景 | Key | 限制 | +|------|-----|------| +| 登录 | `rate:ip:{ip}:login:{date}` | 20次/IP/天 | +| 反馈 | `rate:ip:{ip}:feedback:hourly` | 5次/IP/时 | +| AI 分析 | `rate:user:{userId}:ai:daily:{date}` | 50次/用户/天 | +| 文件上传 | `rate:user:{userId}:upload:hourly` | 10次/用户/时 | + +实现: `src/common/utils/rate-limit.service.ts` + +--- + +## 6. 文件上传安全 + +| 措施 | 说明 | +|------|------| +| 类型白名单 | PDF, Word, Excel, 纯文本, Markdown, CSV, PNG, JPEG, WebP | +| 大小限制 | 最大 20MB | +| 随机文件名 | `sanitizeFilename()` 生成随机 key,不信任用户原始文件名 | +| 默认私有 | 所有文件默认私有访问 | +| 路径隔离 | `users/{userId}/...` | + +--- + +## 7. Redis 安全使用 + +- 不存核心业务结果(用户资料/知识点/AI分析结果等必须在 MySQL) +- 队列任务只存 `jobId`/`userId` 等引用 ID +- 所有临时 key 必须设置 TTL +- 防重复提交锁必须有 TTL,解锁校验 token +- 不在 Redis 中存 token 明文 + +--- + +## 8. COS 安全使用 + +- Bucket 默认私有读写 +- 后端不向前端暴露 SecretId/SecretKey +- 下载私有文件通过签名 URL +- 上传路径按 `users/{userId}/{randomKey}` 组织 +- 预留临时上传 URL(STS)机制 + +--- + +## 9. Swagger 安全 + +- 开发环境默认开启 +- 生产环境默认关闭 +- 生产环境如需开启,必须配置 Basic Auth(`SWAGGER_USER`/`SWAGGER_PASSWORD`) +- 生产环境手动设置 `ENABLE_SWAGGER=true` + +--- + +## 10. 数据库安全 + +- 不使用 root 连接业务 +- 业务账号 `zhixi_user` 仅需 SELECT/INSERT/UPDATE/DELETE +- 迁移账号和业务账号分离(`prisma db push` 与运行时连接帐号可不同) +- 数据库自动备份建议: `mysqldump zhixi | gzip > backup-$(date +%Y%m%d).sql.gz` + +### 日志中禁止打印 + +``` +DATABASE_URL(含密码) +JWT_SECRET +AI_API_KEY +COS SecretKey +用户完整 refreshToken +用户上传文件的完整内容 +Authorization header +``` + +--- + +## 11. 安全检查清单 + +- [x] helmet 已启用 +- [x] CORS 仅允许白名单域名 +- [x] JWT + refresh token rotation + hash 存储 +- [x] logout 撤销 refresh token +- [x] 所有用户数据接口需要认证 +- [x] 资源所有权校验工具已就绪 +- [x] StrictValidationPipe 全局启用(whitelist + forbidNonWhitelisted) +- [x] Redis 限流已实现 +- [x] 文件类型/大小白名单 +- [x] 全局异常过滤器生产环境不暴露 stack trace +- [x] Swagger 生产环境默认关闭 +- [x] 敏感信息不在日志中打印原则已确立 diff --git a/docs/credentials.md b/docs/credentials.md new file mode 100644 index 0000000..d6d32aa --- /dev/null +++ b/docs/credentials.md @@ -0,0 +1,107 @@ +# 知习 (zhixi) 凭据与配置 + +> ⚠️ 本文件包含敏感信息,**不要提交到公开仓库**。 + +--- + +## 服务器 + +| 项目 | 值 | +|------|-----| +| IP | 81.70.187.179 | +| SSH 用户 | ubuntu | +| SSH 密钥 | `服务器密钥/WangDL.pem` | + +--- + +## MySQL + +| 项目 | 值 | +|------|-----| +| Host | 127.0.0.1:3306(服务器内部)/ 通过 SSH 隧道 localhost:3306 | +| 数据库名 | zhixi | +| root 密码 | `Zhixi@2026!Root` | +| 业务账号 | `zhixi_user` | +| 业务密码 | `Zhixi@2026!App` | +| 连接字符串 | `mysql://zhixi_user:Zhixi@2026!App@mysql-zhixi:3306/zhixi` | + +--- + +## Redis + +| 项目 | 值 | +|------|-----| +| Host | 127.0.0.1:6379(服务器内部)/ 通过 SSH 隧道 localhost:6379 | +| 密码 | `Rds@nTsgKrcqAkbuf6PwJIFMZQzF` | +| 最大内存 | 256MB | +| 淘汰策略 | allkeys-lru | + +--- + +## JWT + +| 项目 | 值 | +|------|-----| +| Secret | `98b1e7e377a40021ad7c46c55e467d2a218a89db7afc7c912780152ad64bdc45` | +| accessToken 过期 | 1h | +| refreshToken 过期 | 7d | + +--- + +## Swagger API 文档 + +| 项目 | 值 | +|------|-----| +| URL | http://81.70.187.179:3001/api-docs | +| 用户名 | `admin` | +| 密码 | `Swgr@fmDentAYVXQUpG6oZDpJ` | +| 认证方式 | Basic Auth | + +--- + +## Gitea + +| 项目 | 值 | +|------|-----| +| URL | http://81.70.187.179:3000 | +| SSH | ssh://git@81.70.187.179:2222 | +| Runner Token | `9ypD3K9SWXR17CUNbWsvBxMIfz0MBqLiBLcRkGRZ` | + +--- + +## 端口映射 + +| 端口 | 服务 | +|------|------| +| 22 | SSH | +| 3000 | Gitea | +| 3001 | zhixi-api | +| 3306 | MySQL(仅 127.0.0.1,通过 SSH 隧道访问) | +| 6379 | Redis(仅 127.0.0.1,通过 SSH 隧道访问) | + +--- + +## SSH 隧道(本地开发用) + +```bash +ssh -f -N -L 3306:127.0.0.1:3306 -L 6379:127.0.0.1:6379 \ + -i api-server/服务器密钥/WangDL.pem ubuntu@81.70.187.179 +``` + +--- + +## 本地环境变量(.env) + +```env +PORT=3000 +NODE_ENV=development +DATABASE_URL="mysql://zhixi_user:Zhixi@2026!App@localhost:3306/zhixi" +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=Rds@nTsgKrcqAkbuf6PwJIFMZQzF +JWT_SECRET=98b1e7e377a40021ad7c46c55e467d2a218a89db7afc7c912780152ad64bdc45 +AI_PROVIDER=mock +ENABLE_SWAGGER=true +SWAGGER_USER=admin +SWAGGER_PASSWORD=Swgr@fmDentAYVXQUpG6oZDpJ +```