diff --git a/技术设计/api-server/服务器与数据库部署方案.MD b/技术设计/api-server/服务器与数据库部署方案.MD new file mode 100644 index 0000000..f0b267d --- /dev/null +++ b/技术设计/api-server/服务器与数据库部署方案.MD @@ -0,0 +1,796 @@ +# 知习数据库与服务器部署方案(最终版) + +> 定版日期:2026-05-18 | 状态:已拍板,按此执行 + +--- + +## 一、存储架构总览 + +知识库不需要 MongoDB。最终存储分成四类: + +```text +MySQL :权威业务数据(用户、知识库、学习记录、额度、审计) +Qdrant :向量索引(chunk embedding、语义检索) +COS :对象存储(原始文件、解析结果、备份快照) +Redis :队列 / 缓存 / 限流 / 锁 +``` + +不引入的组件: + +```text +MongoDB → 不需要(没有文档型存储场景) +Elasticsearch → 第一阶段不需要(Qdrant + rerank 够用) +ClickHouse → 第一阶段不需要(OLAP 后续再说) +``` + +--- + +## 二、MySQL:唯一权威业务数据库 + +### 存放内容 + +```text +# 用户与认证 +users +auth_accounts +refresh_tokens +memberships +membership_plans + +# 知识库核心 +files +knowledge_bases +knowledge_sources +document_imports +knowledge_chunks +import_candidates +knowledge_items + +# 学习引擎 +active_recall_answers +ai_analysis_results +focus_items +review_cards +learning_activities +chat_sessions +chat_messages + +# 运营与审计 +ai_usage_logs +quota_usage +admin_audit_logs +backup_jobs +feedback +notifications +``` + +### ID 类型(重要) + +当前 Prisma schema 主键是 `BigInt @default(autoincrement())`,JavaScript/JSON 对 64 位整数有精度问题。 + +**现在数据库空,必须立即改:** + +```prisma +id String @id @default(cuid()) +``` + +推荐 `cuid/cuid2`:适合业务 ID,前端、后端、日志、JWT、API 都好处理。以后数据多了再改会很痛苦。 + +### 生产配置 + +文件:`/etc/mysql/conf.d/zhixi.cnf` + +```ini +[mysqld] +# InnoDB +innodb_buffer_pool_size = 8G +innodb_buffer_pool_instances = 4 +innodb_log_file_size = 1G +innodb_flush_log_at_trx_commit = 1 +innodb_file_per_table = 1 + +# Connections +max_connections = 100 +wait_timeout = 300 +interactive_timeout = 300 + +# Charset +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# Slow query +slow_query_log = 1 +long_query_time = 1 +log_queries_not_using_indexes = 0 + +# Binlog(无主从复制时可关) +skip-log-bin +``` + +### 32G 内存分配 + +```text +MySQL buffer pool 8G +Qdrant 8G–12G(动态) +Redis 1G 内 +RAG Worker / 解析 4G–8G +系统 + Docker + 缓存 4G+ +预留安全空间 4G+ +``` + +后续调整规则: + +```text +MySQL 慢查询多 + 内存充足 → buffer pool 调到 10G–12G +Qdrant / Worker 内存吃紧 → 不再加 MySQL 内存 +Too many connections → max_connections 调到 150 + +--- + +## 三、Qdrant:向量数据库 + +### 存放内容 + +```text +Collection:zhixi_chunks +├─ chunk embedding 向量(1024d,Cosine) +├─ userId / knowledgeBaseId / sourceId / chunkId(payload 索引) +├─ pageNumber / sectionTitle(元数据) +└─ deleted(bool,软删除标记) +``` + +### 负责的能力 + +```text +语义检索(ANN Top-50 → rerank Top-5~8) +知识库问答召回 +相似知识点查找 +学习内容推荐 +``` + +### 不负责 + +```text +用户体系 +会员额度 +知识点管理 +学习记录 +订单支付 +``` + +Qdrant 是 MySQL 的补充,不是替代。 + +### 安全策略(已拍板) + +```text +第一阶段:不公网暴露 Qdrant +优先方案:不映射端口,只在 Docker internal network 用容器名访问 +调试方案:仅绑定 127.0.0.1:6333 +暂不启用 API Key,但预留 QDRANT_API_KEY 环境变量 +未来跨服务器访问时 → 必须启用 API Key +``` + +### Docker Compose 推荐写法(不映射端口) + +```yaml +services: + qdrant: + image: qdrant/qdrant:latest + restart: unless-stopped + volumes: + - /data/qdrant:/qdrant/storage + networks: + - zhixi_internal + # 不写 ports,公网访问不到 + + backend: + image: zhixi-backend:latest + restart: unless-stopped + environment: + QDRANT_URL: http://qdrant:6333 + networks: + - zhixi_internal + - zhixi_public + +networks: + zhixi_internal: + internal: true # 不对外暴露 + zhixi_public: +``` + +### 如需宿主机调试(仅绑定 localhost) + +```yaml +services: + qdrant: + ports: + - "127.0.0.1:6333:6333" + - "127.0.0.1:6334:6334" +``` + +### 不要这样写 + +```yaml +ports: + - "6333:6333" # 暴露公网!没有 API Key 时非常危险 +``` + +--- + +## 四、COS:对象存储 + +### 存放内容 + +```text +zhixi-prod/ +├─ users/{userId}/knowledge-bases/... ← 原始文件 + 解析结果 +├─ users/{userId}/profile/avatar/... ← 头像 +├─ users/{userId}/feedback/... ← 反馈截图 +├─ system/knowledge-bases/... ← 系统内置知识库 +├─ system/backups/qdrant/{date}/... ← Qdrant 快照 +├─ system/backups/mysql/{date}/... ← MySQL 备份 +├─ exports/users/{userId}/... ← 用户导出 +└─ public/app-assets/... ← 公共资源 +``` + +### 原则 + +```text +数据库是主关系,COS 是文件仓库 +权限、归属、状态永远以数据库为准 +处理文件时从 COS 临时拉取,处理完删除本地临时文件 +``` + +--- + +## 五、Redis:队列与缓存 + +### 负责 + +```text +BullMQ 任务队列(文档导入、AI 分析、通知发送) +API 限流 +分布式锁(防止重复导入、重复扣额度) +用户 session(备选,也可用 MySQL) +临时状态缓存 +``` + +### 不负责 + +```text +长期业务数据 +向量索引 +文件存储 +``` + +--- + +## 六、服务器分工(已拍板) + +### 总原则 + +现在数据库基本空,**不要再把新表建到旧服务器上**。唯一权威 MySQL 放 8核32G。 + +### 8核32G CVM:正式生产主服务器 + +**部署清单:** + +```text +/opt/zhixi/backend ← NestJS 主后端 +/opt/zhixi/rag-worker ← Python RAG Worker +/opt/zhixi/deploy ← docker-compose.yml + 脚本 + +/data/docker ← Docker data-root +/data/mysql ← MySQL 数据文件 +/data/redis ← Redis 数据文件 +/data/qdrant ← Qdrant 存储 +/data/tmp/imports ← 临时下载/解析工作区 +/data/logs ← 统一日志 +/data/worker-cache ← llama-index 缓存 +/data/backups/mysql ← MySQL 每日备份 +/data/backups/qdrant ← Qdrant 每日快照 +``` + +**运行服务:** + +```text +Nginx(可选) +NestJS API(Node) +MySQL 8.0 +Redis 7 +Qdrant(Docker) +RAG Worker(Python) +AI Gateway Worker(Node) +Backup Worker(Cron) +Cron Jobs(定时清理、stale job recovery) +``` + +**负责的业务:** + +```text +全部用户数据、认证、会员 +全部知识库数据(表 + 向量) +全部学习引擎(主动回忆、AI 诊断、复习) +全部 AI 调用与成本记录 +全部后台管理与审计 +``` + +### 4核4G 轻量云:工具服务器 / 辅助服务器 + +**定位:** 继续当工具服务器,不承载生产核心数据。 + +**已运行服务(保留):** + +```text +Web 产品页 / landing page +Gitea +``` + +**适合新增的服务:** + +```text +n8n ← 营销自动化、流程编排、Webhook +轻量 agent runner ← CI/CD 辅助、定时调研、通知 +Uptime Kuma ← 服务监控 +跳板机 ← SSH 入口 +测试 / staging 环境 ← 预发布验证 +Gitea Webhook 自动化 ← CI/CD 触发 +营销自动化脚本 ← 定时任务 +轻量后台工具 ← 不操作生产库的管理工具 +``` + +**不建议放 4核4G 的服务:** + +```text +正式 MySQL / Redis / Qdrant ← 生产核心数据 +RAG Worker / 文档解析 ← 重型 CPU/内存任务 +Dify 生产环境 ← 最低 4G 但跑不稳(后面细说) +本地大模型 / 多模态模型 ← GPU/内存不够 +重型爬虫 / 大量自动化任务 ← 吃满 CPU +``` + +--- + +## 七、4核4G 上各服务决策 + +### 1. n8n:可以放 + +n8n 适合放 4核4G,用来做: + +```text +营销自动化 +内容生产流程编排 +Gitea Webhook 触发 +定时任务 +数据同步 +通知提醒 +AI API 调用编排 +运营脚本 +``` + +部署方式: + +```text +Docker Compose +nginx/caddy 反代 → https://n8n.yourdomain.com +``` + +安全要求: + +```text +强密码 +HTTPS +禁止公开注册 +后台路径不裸奔 +定期备份 n8n 数据目录 +``` + +**核心规则:n8n 不直接操作生产数据库。** + +正确方式: + +```text +n8n / agent +→ 调用 8核32G 的受控内部 API +→ 8核32G 做权限、额度、日志、落库 +``` + +不要让 n8n 或 agent 变成"野生后台"。 + +### 2. Dify:第一阶段不部署 + +Dify 最低要求 CPU ≥ 2 Core、RAM ≥ 4 GiB(官方推荐 8 GiB 更稳)。4核4G 已有 Web 产品页 + Gitea + n8n + agent,再放 Dify 容易"能启动但卡、能用但不稳、一跑 workflow 就吃满"。 + +而且 Dify 和你自己的 AI Gateway / RAG Worker 有功能重叠,知习核心知识库才是优先事项。 + +**决策:** + +```text +第一阶段不部署 Dify。 +后续如需实验,放 8核32G sandbox profile(不连生产库)。 +``` + +```bash +# 以后如需实验: +docker compose --profile dify up -d # 用完关掉 +``` + +### 3. 轻量 agent:可以放 + +适合放 4核4G 的 agent 类型: + +```text +营销内容生成 agent +定时调研 agent +Gitea issue 分析 agent +自动发布提醒 agent +数据采集辅助 agent +n8n workflow agent +小型 webhook agent +``` + +规则和 n8n 一样:**agent 可以调用知习 API,但不可直接连生产 MySQL、操作 Qdrant、拿 COS 主密钥。** + +--- + +## 八、最终部署架构图 + +```text + iOS / Web + ↓ + ┌───────────────┴───────────────┐ + │ │ +┌───────┴──────────┐ ┌───────┴──────────┐ +│ 8核32G CVM │ │ 4核4G 轻量云 │ +│ (生产核心服务器) │ │ (工具/辅助服务器) │ +│ │ │ │ +│ ┌──────────────┐ │ │ ┌───────────────┐ │ +│ │ gitea-runner │ │ CI/CD │ │ gitea-runner │ │ +│ │ -prod │ │ │ │ -web │ │ +│ └──────────────┘ │ │ └───────────────┘ │ +│ ┌──────────────┐ │ │ ┌───────────────┐ │ +│ │ NestJS API │ │ │ │ Web 产品页 │ │ +│ └──────┬───────┘ │ │ │ (landing) │ │ +│ │ │ │ └───────────────┘ │ +│ ┌──────┴───────┐ │ │ ┌───────────────┐ │ +│ │ MySQL │ │ 权威业务库 │ │ Gitea │ │ +│ └──────────────┘ │ │ │ (主服务) │ │ +│ ┌──────────────┐ │ │ └───────────────┘ │ +│ │ Redis │ │ 队列/缓存 │ ┌───────────────┐ │ +│ └──────────────┘ │ │ │ n8n │ │ +│ ┌──────────────┐ │ │ └───────────────┘ │ +│ │ Qdrant │ │ 向量索引 │ ┌───────────────┐ │ +│ └──────────────┘ │ │ │ agent runner │ │ +│ ┌──────────────┐ │ │ └───────────────┘ │ +│ │ RAG Worker │ │ 解析/切片 │ ┌───────────────┐ │ +│ └──────────────┘ │ │ │ Uptime Kuma │ │ +│ ┌──────────────┐ │ │ └───────────────┘ │ +│ │ AI Gateway │ │ AI 调度 │ │ +│ └──────────────┘ │ │ (不承载正式 │ +│ ┌──────────────┐ │ │ 业务数据) │ +│ │ Backup+Cron │ │ 备份/清理 └───────────────────┘ +│ └──────────────┘ │ +│ │ +│ /data (70G) │ +└────────┬─────────┘ + │ + ┌───────┴───────┐ + │ 腾讯云 COS │ + │ │ + │ 原始文件 │ + │ parsed.md │ + │ OCR/Vision │ + │ 备份快照 │ + │ 用户头像 │ + │ 导出文件 │ + └───────────────┘ +``` + +--- + +## 九、CI/CD 部署流水线 + +### 1. 总原则 + +```text +Gitea 本体只放 4核4G,Runner 两台都装 +服务跑在哪台服务器,Runner 就装在哪台服务器 +每台 Runner 本地构建、本地部署,不跨服务器传输构建产物 +``` + +不要搞成"4核 Runner 构建后端 → 传到 8核部署",多一层 SSH + 传输 + 1Mbps 带宽瓶颈。 + +### 2. Runner 分工 + +**4核4G:gitea-runner-web** + +```text +标签:web, tools, staging, docker +负责: + Web 产品页部署 + Gitea Webhook 自动化 + n8n / 工具服务部署 + 轻量 agent 更新 +``` + +**8核32G:gitea-runner-prod** + +```text +标签:prod, backend, rag, docker +负责: + NestJS 后端部署 + Prisma migration + MySQL / Redis / Qdrant docker compose 更新 + RAG Worker 部署 + AI Gateway 部署 + 备份脚本部署 +``` + +### 3. Runner 安装 + +两台服务器都创建 `deploy` 用户(不要用 root 跑 runner): + +```bash +sudo useradd -m -s /bin/bash deploy +sudo usermod -aG docker deploy +``` + +8核上注册 runner 到 4核 Gitea: + +```bash +# 在 8核32G 上执行 +./gitea-actions-runner register \ + --name zhixi-prod-runner \ + --labels prod,backend,rag,docker \ + --instance https://gitea.yourdomain.com \ + --token <从 Gitea Admin 获取> +``` + +### 4. Workflow 示例 + +**后端部署(跑在 8核 runner):** + +```yaml +# .gitea/workflows/deploy-backend.yml +name: deploy-backend + +on: + push: + branches: [main] + paths: + - "backend/**" + - "rag-worker/**" + - "deploy/**" + +jobs: + deploy: + runs-on: [prod, backend, docker] + steps: + - uses: actions/checkout@v4 + - name: Build & Deploy + run: | + cd backend + pnpm install --frozen-lockfile + pnpm build + npx prisma migrate deploy + docker compose -f ../deploy/docker-compose.prod.yml up -d --build +``` + +**Web 部署(跑在 4核 runner):** + +```yaml +# .gitea/workflows/deploy-web.yml +name: deploy-web + +on: + push: + branches: [main] + paths: + - "web/**" + +jobs: + deploy: + runs-on: [web, docker] + steps: + - uses: actions/checkout@v4 + - name: Build & Deploy + run: | + cd web + pnpm install --frozen-lockfile + pnpm build + docker compose -f docker-compose.web.yml up -d --build +``` + +### 5. 密钥管理 + +生产密钥不写入仓库,放在 8核服务器本地或 Gitea Secrets: + +```text +/opt/zhixi/env/.env.production + +必需密钥: + DATABASE_URL + REDIS_URL + QDRANT_URL + COS_SECRET_ID / COS_SECRET_KEY + DEEPSEEK_API_KEY + SILICONFLOW_API_KEY + BAIDU_OCR_KEY + JWT_SECRET + RAG_WORKER_SECRET(内部 API 认证) +``` + +### 6. Gitea 挂了不影响生产 + +```text +Gitea 只影响代码仓库和部署流水线 +8核上的后端、MySQL、Redis、Qdrant 继续运行 +最多暂时不能自动部署新版本 +``` + +--- + +## 十、迁移执行计划 + +因为现在数据库基本空,迁移很简单,按此顺序推进: + +### 第一步:8核32G 基础环境 + +```text +1. 挂载 /data 数据盘 +2. 安装 Docker / Docker Compose +3. 配置 Docker data-root → /data/docker +4. 创建所有 /data 子目录 +5. 创建 deploy 用户(用于 Runner 和部署) +``` + +### 第二步:部署核心服务 + +```text +6. 部署 MySQL → /data/mysql,创建数据库 zhixi_prod +7. 部署 Redis → /data/redis +8. 部署 Qdrant → /data/qdrant,创建 collection zhixi_chunks +9. 配置数据库连接:DATABASE_URL / REDIS_URL / QDRANT_URL +``` + +### 第三步:数据库迁移 + +```text +9. 修改 Prisma schema:BigInt → String(cuid) +10. 初始化空库 migration +11. 4核4G mysqldump → 导入 8核32G(仅登录相关 2~3 张表) +12. 配置备份脚本(MySQL + Qdrant + 同步 COS) +``` + +### 第四步:CI/CD 搭建 + +```text +14. 8核32G 安装 gitea-runner-prod(标签 prod, backend, rag, docker) +15. 4核4G 安装 gitea-runner-web(标签 web, tools, docker) +16. 配置 Gitea Secrets(密钥不入仓库) +17. 编写 deploy-backend.yml / deploy-web.yml +18. 测试 push → 自动构建 → 自动部署流水线 +``` + +### 第五步:DNS 与后端切换 + +域名规划: + +```text +zhixi.app / api.zhixi.app → 8核32G(NestJS 后端 API) +www.zhixi.app / zhixi.app → 4核4G(Web 产品页) +n8n.zhixi.app → 4核4G(n8n) +gitea.zhixi.app → 4核4G(Gitea) +``` + +```text +19. 确认 8核32G 上 NestJS 正常运行(先用 IP:3000 测试) +20. DNS 主域名(api.zhixi.app / 主站)从 4核4G 切换到 8核32G +21. Caddy/Nginx 配置 SSL 证书(Let's Encrypt 自动申请) +22. 修改 DATABASE_URL 指向新 MySQL +23. 测试 iOS 登录功能(通过域名 + HTTPS) +24. 4核4G 旧后端保留 7~14 天作为回滚备用 +25. n8n / Gitea 的子域名保持指向 4核4G(不动) +``` + +> **当前状态:DNS 尚在 4核4G。切换完成后告知,更新本文档状态。** + +### 第六步:知识库开发 + +```text +23. 在 8核32G 上按知识库设计文档建表 +24. 实现文件上传 → COS +25. 部署 RAG Worker +26. 打通索引流程 → 学习流程 +``` + +--- + +## 十一、最终拍板清单 + +```text +# 存储架构 +1. 唯一权威 MySQL 放 8核32G,4核4G 不再建新表 +2. ID 类型立即从 BigInt 改为 String(cuid),趁库空 +3. Redis 放 8核32G(队列 / 缓存 / 限流 / 锁) +4. Qdrant 放 8核32G(向量索引) +5. COS 继续作为文件 + 备份存储 +6. 不引入 MongoDB / Elasticsearch / ClickHouse + +# 8核32G:生产核心服务器 +7. 全量承载 NestJS + MySQL + Redis + Qdrant + RAG Worker + AI Gateway +8. 负责全部用户数据、知识库、学习引擎、成本记录、后台审计 +9. 安装 gitea-runner-prod(负责后端/知识库/RAG 部署) + +# 4核4G:工具服务器 / 辅助服务器 +10. 保留 Gitea 主服务 + Web 产品页 +11. 安装 gitea-runner-web(负责 Web/工具/n8n 部署) +12. 可以加 n8n(营销自动化、Webhook、流程编排) +13. 可以加轻量 agent runner(不直接操作生产库) +14. 可以加 Uptime Kuma 监控 +15. Dify 第一阶段不部署(后续如需实验放 8核32G sandbox) +16. 不承载正式 MySQL / Qdrant / Redis / RAG Worker + +# CI/CD +17. Gitea 本体只放 4核4G,Runner 两台都装 +18. 服务跑在哪台服务器,Runner 就装在哪台服务器 +19. 生产密钥放 Gitea Secrets 或服务器本地 .env,不写入仓库 +20. Runner 使用 deploy 用户,不用 root + +# 安全边界 +21. n8n / agent 调用知习受控 API,不直连生产库 +22. 迁移完成后 4核4G 旧后端保留 7~14 天作为回滚备用 +``` + +后续不会再有一次大的数据库迁移。 + +--- + +## 十二、仍存缺陷与待决策项 + +以下是当前方案中尚未覆盖或需要你拍板的点,按风险等级排序。 + +### 🔴 高风险(上线前必须定) + +| # | 问题 | 现状 | 建议 | +|---|------|------|------| +| 1 | **3Mbps 公网带宽** | 8核32G 3Mbps | 日常增量文件(单文件 ≤20MB)影响可控。大文件下载 COS + 备份上传走后台队列异步处理,不阻塞 API。首次部署大量依赖包安装可能稍慢,耐心等待即可。 | +| 2 | **Qdrant 无内置认证** | 已定 | 第一阶段不公网暴露。优先不映射端口(Docker internal network),需调试时仅绑定 `127.0.0.1:6333`。暂不启用 API Key,预留 `QDRANT_API_KEY` 配置。跨服务器访问时必须启用。 | +| 3 | **HTTPS 证书** | 已定:免费证书(Let's Encrypt) | 域名解析当前指向 4核4G,待切换到 8核32G。切换后:域名 → 8核32G 公网 IP → Caddy/Nginx 反代 → 本地 NestJS:3000。Caddy 自动申请续期。 | +| 4 | **MySQL 配置调优** | 已定 | `innodb_buffer_pool_size=8G`,`max_connections=100`,完整配置见下方 MySQL 配置小节。后续观察慢查询/内存压力再调。 | + +### 🟡 中风险(迁移前应定) + +| # | 问题 | 现状 | 建议 | +|---|------|------|------| +| 5 | **Redis 持久化** | 未配置 | 推荐 AOF everysec + RDB 双开。Redis 主要做队列(BullMQ),任务丢失影响可控但要尽量保留。 | +| 6 | **Docker 容器网络规划** | 未定义 | 建议一个 `zhixi` 自定义 bridge 网络,MySQL/Redis/Qdrant 只监听 localhost 或容器内网,不绑定 0.0.0.0。 | +| 7 | **日志轮转与保留** | /data/logs 目录已规划但无策略 | 建议 logrotate:每天轮转,保留 30 天,压缩。Worker 日志量可能较大,需要单独评估。 | +| 8 | **健康检查端点** | 未定义 | NestJS 需 `/health` 端点(返回 DB/Redis/Qdrant 连通性)。Uptime Kuma 可用此端点监控。 | +| 9 | **两服务器间通信** | 内网还是公网?| 看腾讯云是否支持同地域内网互通(北京)。如果可以,Gitea → Runner 通信走内网更快。如果不能,Runner 通过公网拉取 Gitea 任务。 | +| 10 | **COS 带宽成本** | 备份上传/下载走公网 | COS 同地域内网访问免费。确认 8核32G 和 COS 都在"北京"地域,使用内网 Endpoint(`cos.ap-beijing.myqcloud.com` 的内网域名)。 | + +### 🟢 低风险(后续迭代时再定) + +| # | 问题 | 现状 | 建议 | +|---|------|------|------| +| 11 | **API 版本管理** | 未定义 | 建议 `/api/v1/...` 前缀,为后续 Breaking Change 留空间。 | +| 12 | **灰度/蓝绿部署** | 未涉及 | 第一阶段不需要。先单实例跑稳,后续再考虑 `docker compose` 滚动更新。 | +| 13 | **数据库读写分离** | 未涉及 | 第一阶段不需要。MySQL 单实例完全够。 | +| 14 | **异地备份** | COS 在北京 | COS 本身支持跨地域复制。如果预算允许,可以复制到另一个地域的 bucket。目前本地 + COS 同地域已较安全。 | +| 15 | **4核4G 最终去留** | 迁移后保留 7~14 天 | 后续如果 4核4G 作为长期工具服务器,建议保留。如果成本敏感且 8核32G 完全够用,可以到期前评估是否下线。 | +| 16 | **Docker Compose 文件结构** | 未定义 | 建议 `deploy/docker-compose.prod.yml` 统一管理 8核32G 上的所有服务,用一个 compose 文件管理容器间的启动顺序和网络。 | + +--- + +## 十三、文档关联索引 + +本文档应与其他设计文档配合使用: + +```text +知识库设计.MD → 数据模型、API 设计、RAG 流程、额度系统 +服务器与数据库部署方案.MD → 本文档:服务器分工、存储架构、CI/CD +(后续)Prisma Schema → 具体表结构实现 +(后续)docker-compose.yml → 容器编排配置 +``` + diff --git a/技术设计/api-server/知识库设计.MD b/技术设计/api-server/知识库设计.MD new file mode 100644 index 0000000..8c85638 --- /dev/null +++ b/技术设计/api-server/知识库设计.MD @@ -0,0 +1,1559 @@ + +可以,现在应该把它整理成一个**分板块执行的落地方案**。你后面就不要再一直在脑子里绕了,按这个顺序推进。 + +# 知习知识库落地执行方案(完整版) + +--- + +## 一、服务器部署板块 + +### 1. 当前服务器分工 + +```text +4核4G 轻量云: +主业务服务器 + +8核32G CVM: +知识库 / RAG / 文档处理 / AI 调度服务器 + +腾讯云 COS: +原始文件存储 +``` + +### 2. 8核32G 服务器用途 + +这台服务器负责: + +```text +Qdrant 向量库 +LlamaIndex / RAG Worker +文档解析 +PDF / DOCX / TXT / Markdown 处理 +OCR / 多模态任务调度 +chunking 切片 +embedding 调用 +知识库检索 +AI Gateway 部分调度 +DocumentImport 异步任务 +KnowledgeChunk 写入 +ImportCandidate 生成 +``` + +它不负责: + +```text +本地大模型推理 +本地多模态大模型 +GPU 任务 +原始文件长期存储 +``` + +--- + +## 二、服务器基础环境 + +### 1. 系统配置 + +```text +CPU:8核 +内存:32G +系统:Ubuntu Server 22.04 LTS +系统盘:30G +数据盘:70G +公网:1Mbps +地域:北京 +``` + +### 2. 数据盘挂载 + +挂载到: + +```text +/data +``` + +目录规划: + +```text +/data/docker ← Docker data-root +/data/qdrant ← Qdrant storage(向量数据) +/data/tmp/imports ← 临时下载、解析工作区 +/data/logs ← Worker 日志 +/data/worker-cache ← llama-index 缓存 +/data/backups ← Qdrant 快照 + MySQL dump +``` + +系统盘只放:Ubuntu 系统 + 基础配置 + 少量代码。不要让 Docker、Qdrant、临时解析文件堆到 30G 系统盘里。 + +--- + +## 三、服务器需要安装的东西 + +### 1. 基础环境 + +```text +Docker +Docker Compose +Git +Node.js +pnpm +Python 3.11+ +pip / poetry +nginx(可选,后续对外暴露 API 时配) +supervisor / systemd +logrotate +``` + +### 2. 知识库服务组件 + +```text +Qdrant(Docker) +RAG Worker(Python) +NestJS API(Node,部署在 4核4G 主服务器) +COS SDK +AI Gateway(Node,主服务器) +OCR Provider SDK +Embedding Provider SDK +``` + +### 3. Python Worker 主要依赖 + +```text +llama-index +qdrant-client +pymupdf / pdfplumber +python-docx +markdown +pandas / openpyxl +pydantic +requests / httpx +tencentcloud-sdk-python +Pillow(HEIC → JPG 转换) +``` + +### 4. Node 后端主要依赖 + +```text +NestJS +Prisma +MySQL client +Redis / BullMQ(任务队列) +COS SDK +AI Provider SDK +JWT / Auth +class-validator +zod / JSON Schema 校验 +``` + +> **语言分工:** NestJS(Node)负责 API 层 + AI Gateway + 任务入队。Python 负责 RAG Worker(文档解析、chunking、embedding、Qdrant 写入),因为 llama-index 生态在 Python 侧最成熟。两者通过 Redis/BullMQ(入队) + 内部 HTTP(heartbeat/result)通信。 + +--- + +## 四、安全组和网络 + +### 1. 安全组建议 + +```text +22:只允许你的 IP +80 / 443:如果知识库服务需要对外访问再开 +6333 / 6334:Qdrant 不开放公网 +3306:MySQL 不开放公网 +6379:Redis 不开放公网 +``` + +### 2. 服务器之间通信 + +```text +4核4G 主服务器 +→ 内网调用(HTTP) +8核32G 知识库服务器 +``` + +不要让知识库内部接口直接暴露公网。 + +### 3. COS 访问方式 + +COS 不作为本地硬盘长期挂载。正确方式: + +```text +Worker 通过 COS SDK 拉取文件 +→ 临时下载到 /data/tmp/imports/{jobId} +→ 解析处理 +→ 写入 Qdrant / MySQL +→ 删除临时文件 +``` + +COS 负责原始文件存储,服务器负责临时处理和索引。 + +--- + +## 五、COS 存储结构 + +核心原则: + +```text +数据库是主关系,COS 是文件仓库 +COS 路径按 userId / knowledgeBaseId / sourceId 分类 +权限、归属、状态永远以数据库为准 +不要直接用用户原文件名当 objectKey(用 fileId/sourceId 避免重名和特殊字符) +``` + +### 1. Bucket + +第一阶段一个 bucket 就够: + +```text +zhixi-prod +``` + +### 2. 用户知识库原始文件 + +```text +users/{userId}/knowledge-bases/{knowledgeBaseId}/sources/{sourceId}/original/{fileId}.{ext} +``` + +示例: + +```text +users/user_001/knowledge-bases/kb_001/sources/src_001/original/file_001.pdf +users/user_001/knowledge-bases/kb_001/sources/src_002/original/file_002.docx +``` + +### 3. 解析结果 + +```text +users/{userId}/knowledge-bases/{knowledgeBaseId}/sources/{sourceId}/parsed/parsed.md +users/{userId}/knowledge-bases/{knowledgeBaseId}/sources/{sourceId}/parsed/metadata.json +``` + +`parsed.md` 保留完整解析文本,MySQL 不存全文,通过 `parsedObjectKey` 引用。 + +### 4. OCR / 多模态结果 + +```text +users/{userId}/knowledge-bases/{knowledgeBaseId}/sources/{sourceId}/processed/ocr/page_001.json +users/{userId}/knowledge-bases/{knowledgeBaseId}/sources/{sourceId}/processed/vision/page_001.json +``` + +早期可只保存最终 `parsed.md`,调试 OCR/多模态质量时再保存中间结果。 + +### 5. 用户头像与反馈 + +```text +users/{userId}/profile/avatar/{fileId}.{ext} +users/{userId}/feedback/{feedbackId}/screenshots/{fileId}.{ext} +``` + +### 6. 系统内置知识库 + +```text +system/knowledge-bases/{systemKnowledgeBaseId}/sources/{sourceId}/original/{fileId}.{ext} +system/knowledge-bases/{systemKnowledgeBaseId}/sources/{sourceId}/parsed/parsed.md +system/knowledge-bases/{systemKnowledgeBaseId}/sources/{sourceId}/parsed/metadata.json +``` + +系统知识库只存一份,用户使用系统知识库时是"引用",不要给每个用户复制一份。 + +### 7. 备份(同步到 COS) + +```text +system/backups/qdrant/{yyyy-mm-dd}/zhixi_chunks.snapshot +system/backups/mysql/{yyyy-mm-dd}/zhixi.sql.gz +``` + +### 8. 用户导出 + +```text +exports/users/{userId}/{exportId}/learning_report.pdf +exports/users/{userId}/{exportId}/knowledge_base_export.zip +``` + +### 9. 临时文件(服务器本地为主) + +默认放服务器 `/data/tmp/imports`,处理完删除。如果必须放 COS: + +```text +temp/imports/{importId}/original.{ext} +temp/imports/{importId}/pages/page_001.png +temp/imports/{importId}/ocr/page_001.json +``` + +### 10. 完整 COS 目录树 + +```text +zhixi-prod/ + users/ + {userId}/ + knowledge-bases/ + {knowledgeBaseId}/ + sources/ + {sourceId}/ + original/ + {fileId}.{ext} + parsed/ + parsed.md + metadata.json + processed/ + ocr/ + page_001.json + vision/ + page_001.json + profile/ + avatar/ + {fileId}.{ext} + feedback/ + {feedbackId}/ + screenshots/ + {fileId}.{ext} + + system/ + knowledge-bases/ + {systemKnowledgeBaseId}/ + sources/ + {sourceId}/ + original/ + {fileId}.{ext} + parsed/ + parsed.md + metadata.json + backups/ + qdrant/ + {yyyy-mm-dd}/ + zhixi_chunks.snapshot + mysql/ + {yyyy-mm-dd}/ + zhixi.sql.gz + + exports/ + users/ + {userId}/ + {exportId}/ + learning_report.pdf + knowledge_base_export.zip + + temp/ + imports/ + {importId}/ + original.{ext} + + public/ + app-assets/ + icons/ + illustrations/ +``` + +### 11. 数据库里需保存的 COS 关联字段 + +`files` 表:`bucket / objectKey / originalFilename / mimeType / sizeBytes / sha256 / purpose / status` + +`knowledge_sources` 表:`originalObjectKey / parsedObjectKey / metadataObjectKey` + +### 12. COS 生命周期 + +```text +软删除后保留 7 天 +每天凌晨定时任务清理超期文件 +``` + +--- + +## 六、知识库支持的上传格式及处理策略 + +### 1. 第一阶段必须支持 + +```text +PDF DOCX TXT Markdown / MD +PNG JPG / JPEG WEBP HEIC +CSV XLSX +``` + +### 2. 详细处理策略 + +| 类型 | 处理方式 | 工具 | +|------|---------|------| +| TXT / Markdown | 本地解析 | Python 原生 | +| DOCX | 本地解析 | python-docx | +| 文本型 PDF | PyMuPDF 提取文本层 | pymupdf | +| 扫描 PDF(文本层为空) | 百度 OCR / Qwen3-VL 兜底 | 百度 OCR → 硅基流动 | +| 图片文字 | 百度 OCR | 百度 OCR | +| 表格截图 | Qwen3-VL 多模态 | 硅基流动 | +| 图文混排 | Qwen3-VL 多模态 | 硅基流动 | +| CSV / XLSX | 本地解析为 Markdown table | pandas / openpyxl | +| PPTX | 预留,仅提取文本 | python-pptx(后续) | +| HEIC | 先转 JPG 再处理 | Pillow | + +### 3. 暂时预留 + +```text +音频 视频 网页抓取 压缩包批量导入 +``` + +### 4. PDF 解析细化 + +```text +先尝试 PyMuPDF 提取文本层 +如果文本层为空或每页文本 < 50 字符 +→ 判断为扫描件 +→ 走百度 OCR(普通扫描文字) +→ 复杂排版 / 教材类走 Qwen3-VL +``` + +--- + +## 七、核心数据模型 + +### 1. File + +```text +files +- id +- userId +- bucket +- objectKey +- originalFilename +- mimeType +- sizeBytes +- sha256 ← 用于重复文件检测 +- purpose +- status +- createdAt +- updatedAt +- deletedAt +``` + +重复文件检测逻辑:同一用户、同一知识库内 sha256 重复 → 提示用户"该文件已存在",允许取消 / 引用已有文件 / 仍然新增。 + +### 2. KnowledgeBase + +```text +knowledge_bases +- id +- userId +- title +- description +- icon +- coverColor +- visibility ← 先预留,第一阶段默认 private +- status +- itemCount +- sourceCount +- storageUsedBytes +- lastImportedAt +- lastStudiedAt +- createdAt +- updatedAt +- deletedAt +``` + +### 3. KnowledgeSource + +```text +knowledge_sources +- id +- userId +- knowledgeBaseId +- fileId +- type +- title +- originalFilename +- mimeType +- sizeBytes +- textLength +- parseStatus +- indexStatus +- learningStatus +- parsedObjectKey +- version ← 预留,默认 1 +- parentSourceId ← 预留(版本链) +- replacedBySourceId ← 预留(被哪个新版本替代) +- errorCode +- errorMessage +- createdAt +- updatedAt +- deletedAt +``` + +### 4. DocumentImport + +```text +document_imports +- id +- userId +- knowledgeBaseId +- sourceId +- status +- step +- progress ← 0~100 +- workerId +- retryCount ← 新增 +- maxRetries ← 新增,默认 3 +- heartbeatAt +- errorCode +- errorMessage +- startedAt +- completedAt +- createdAt +- updatedAt +``` + +状态机: + +```text +QUEUED +CLAIMED +DOWNLOADING +PARSING +OCR_PROCESSING +VISION_PROCESSING +CLEANING +CHUNKING +EMBEDDING +INDEXING +GENERATING_CANDIDATES +WAITING_CONFIRM +COMPLETED +FAILED_RETRYABLE +FAILED_FINAL +CANCELED +``` + +**Heartbeat 机制:** Worker 每 30 秒上报一次。超过 5 分钟无 heartbeat → 状态回退 QUEUED + workerId 清空 + retryCount +1。 + +**Stale Job Recovery(定时任务,每分钟):** + +```sql +UPDATE document_imports +SET status = 'QUEUED', workerId = NULL +WHERE status IN ('CLAIMED', 'DOWNLOADING', 'PARSING', 'OCR_PROCESSING', + 'VISION_PROCESSING', 'CLEANING', 'CHUNKING', 'EMBEDDING', + 'INDEXING', 'GENERATING_CANDIDATES') + AND heartbeatAt < NOW() - INTERVAL 5 MINUTE; +``` + +### 5. KnowledgeChunk + +```text +knowledge_chunks +- id +- userId +- knowledgeBaseId +- sourceId +- content +- chunkIndex +- pageNumber +- sectionTitle +- tokenCount +- externalVectorId ← Qdrant point ID +- embeddingModel ← 'bge-m3' +- embeddingStatus ← PENDING / COMPLETED / FAILED +- metadataJson ← { overlapWith, chunkType, ... } +- createdAt +- updatedAt +- deletedAt +``` + +### 6. ImportCandidate + +```text +import_candidates +- id +- userId +- knowledgeBaseId +- sourceId +- importId +- title +- summary +- content +- tagsJson +- recallQuestionsJson +- sourceTextSnippet +- sourceChunkIds ← 新增:关联哪些 chunk +- confidence ← 0.0 ~ 1.0 +- difficulty ← 新增:easy / medium / hard +- orderIndex +- status ← PENDING / ACCEPTED / REJECTED / EDITED / IMPORTED +- createdAt +- updatedAt +``` + +生成规则(已拍板): + +```text +每 2000 中文字生成 1~2 个候选 +单个 source 上限 30 个 +最少生成 3 个(即使文档很短) +第一阶段不自动接受,全部 PENDING 等用户确认 +``` + +### 7. KnowledgeItem + +```text +knowledge_items +- id +- userId +- knowledgeBaseId +- sourceId +- importId +- title +- summary +- content +- tagsJson +- sourceType +- masteryLevel +- sourceDeleted ← 新增:原资料是否已删除 +- sourceTitleSnapshot ← 新增:原资料标题快照 +- sourceSnippetSnapshot ← 新增:原引用片段快照 +- createdAt +- updatedAt +- deletedAt +``` + +### 8. MembershipPlan + +```text +membership_plans +- id +- code ← FREE / PRO_TEST / PRO +- name +- priceMonthly ← 单位:分(避免浮点),配置化可随时调 +- priceYearly +- maxKnowledgeBases +- maxStorageBytes +- maxFileSizeBytes +- monthlyOcrPages +- monthlyVisionPages +- monthlyChatCount +- monthlyAiAnalysisCount +- monthlyRecallCount +- monthlyCardGenCount +- isActive +- createdAt +- updatedAt +``` + +价格不写死在代码里,后端只认 planId + quotaConfig。第一阶段先用 28 元/月作为 Pro 预设,跑 1 个月真实成本后再正式确定价格和额度。 + +### 9. BackupJob + +```text +backup_jobs +- id +- type ← QDRANT / MYSQL +- status ← RUNNING / COMPLETED / FAILED +- localPath +- cosObjectKey +- fileSizeBytes +- startedAt +- completedAt +- errorMessage +- createdAt +``` + +--- + +## 八、Chunking 切片策略(已拍板) + +### 1. 默认参数 + +```text +chunk_size = 512 tokens +overlap = 64 tokens(~12%) +策略 = 递归字符分割 + 中文分句保护 +``` + +### 2. 分文档类型规则 + +| 文档类型 | 切片方式 | +|---------|---------| +| Markdown | 优先按 `#` / `##` / `###` 标题分层切片 | +| PDF | 保留 pageNumber,在段落边界切 | +| DOCX | 按标题 / 段落层级切 | +| 表格 | **整表保留**,不强行切碎 | +| 代码块 | **整块保留** | +| 公式附近 | 公式 + 上下文保留在同一 chunk | +| 普通文本 | 512 tokens | +| 复杂解释型段落 | 允许扩展到 768 tokens | + +### 3. 注入每个 chunk 的元数据 + +```text +sourceId +pageNumber +sectionTitle +chunkIndex +chunkType(text / table / code / formula) +``` + +--- + +## 九、Qdrant 设计(已拍板) + +### 1. 部署参数 + +```text +部署模式:单节点 Docker +Collection:zhixi_chunks +vector_size:1024 +distance:Cosine +``` + +### 2. Collection 创建参数 + +```json +{ + "vectors": { + "size": 1024, + "distance": "Cosine" + }, + "shard_number": 1, + "replication_factor": 1, + "hnsw_config": { + "m": 16, + "ef_construct": 100 + }, + "optimizers_config": { + "default_segment_number": 2 + }, + "on_disk_payload": true +} +``` + +> **注意:** 单节点不要设 `replication_factor = 2`,没有意义且浪费资源。 + +### 3. Payload + +```json +{ + "userId": "user_xxx", + "knowledgeBaseId": "kb_xxx", + "sourceId": "src_xxx", + "chunkId": "chunk_xxx", + "pageNumber": 3, + "sectionTitle": "章节标题", + "deleted": false +} +``` + +### 4. Payload 索引(必须建) + +```text +userId → keyword index +knowledgeBaseId → keyword index +sourceId → keyword index +chunkId → keyword index +deleted → bool index +``` + +### 5. 检索过滤 + +每次检索必须带: + +```text +userId = 当前用户 +knowledgeBaseId = 当前知识库 +deleted = false +``` + +### 6. 备份策略(已拍板:本地 + 同步 COS) + +```text +每日凌晨 3 点生成 Qdrant snapshot → /data/backups/qdrant/ +生成后上传到 COS → system/backups/qdrant/{yyyy-mm-dd}/zhixi_chunks.snapshot +本地保留最近 7 天 +COS 保留最近 30 天 +``` + +恢复依赖链:COS 原始文件 + MySQL 元数据 + Qdrant 快照 → 三者共同保证可恢复。 + +### 7. Qdrant 集群迁移时机 + +```text +第一阶段:单节点 Qdrant,1 shard,replication_factor = 1 +``` + +迁移触发条件(任一满足即评估): + +```text +collection 超过 100 万 points +Qdrant 内存长期超过 70% +检索 p95 延迟超过 1.5 秒 +snapshot / restore 时间过长 +``` + +迁移路径: + +```text +阶段 1:单节点 Qdrant(现在) +阶段 2:当前服务器加数据盘 +阶段 3:Qdrant 独立迁移到新服务器 +阶段 4:Qdrant 集群 +``` + +--- + +## 十、Embedding & Rerank(已拍板) + +### 1. Embedding + +```text +模型:BAAI/bge-m3 +Provider:硅基流动 +维度:1024 +Batch size:50~100 chunks / 批次 +``` + +注意:不要一个大文档全塞进一次 embedding 调用,分批处理。单批失败只重试该批,不重跑全部。 + +备选降级(如果 bge-m3 成本或速度不理想): + +```text +→ 切到 bge-large-zh-v1.5(同样 1024d) +``` + +但第一版先统一用 bge-m3,不要同时支持多个 embedding 模型。 + +### 2. Rerank + +```text +模型:BAAI/bge-reranker-v2-m3 +Provider:硅基流动 +输入:query + Top-50 候选 chunks +输出:精排 Top-5~8 +``` + +--- + +## 十一、RAG 检索流程(已拍板) + +```text +用户提问 +→ 生成 query embedding(bge-m3) +→ Qdrant ANN 召回 Top-50(带 userId/kbId/deleted 过滤) +→ bge-reranker-v2-m3 精排 +→ 取 Top-5(普通问题)~ Top-8(复杂问题) +→ 拼接 context +→ DeepSeek 生成回答 + 引用溯源 +``` + +### Context 拼接格式 + +```text +[来源:{sourceTitle},章节:{sectionTitle},第 {pageNumber} 页] +{chunkContent} +``` + +### 知识库对话多轮策略 + +第一阶段: + +```text +保存 chat session + messages:✅ +取最近 3 轮对话拼入 prompt:✅ 少量做 +根据历史重写检索 query:❌ 后面再做 +通用 AI 问答(非知识库内容):❌ 第一阶段不支持 +``` + +检索 query 仍使用用户当前原问题,但 prompt 上下文附带最近 3 轮历史。 + +当检索不到相关内容时返回: + +```text +当前知识库中没有找到足够相关的资料。 +你可以上传更多资料,或换一种问法。 +``` + +知习第一阶段只做**基于当前知识库内容的 RAG 问答**,不做纯通用 AI 对话。避免产品定位偏离、增加无意义 token 成本和用户把它当 ChatGPT 用。 + +--- + +## 十二、AI Provider 策略 + +### 1. DeepSeek 官方 — 核心文本智能 + +```text +知识点提取 默认 V4 Flash(非思考) +摘要 / 标签 默认 V4 Flash +主动回忆题 默认 V4 Flash +复习卡生成 默认 V4 Flash +知识库问答 默认 V4 Flash +主动回忆诊断 V4 Flash thinking +待巩固项分析 V4 Flash thinking +学习报告 V4 Flash thinking +高价值深度分析 V4 Pro +``` + +### 2. 硅基流动 — 工具模型 + +```text +Qwen3-VL 多模态 Qwen/Qwen3-VL-32B-Instruct +复杂视觉兜底 Qwen/Qwen3-VL-32B-Thinking +embedding BAAI/bge-m3 +rerank BAAI/bge-reranker-v2-m3 +备用模型池 营销 / 客服 / 润色测试 +``` + +### 3. 百度 OCR + +```text +普通扫描文字 +图片文字 +截图文字 +→ 复杂页面不要用 OCR 硬识别,交给 Qwen3-VL +``` + +--- + +## 十三、AI Gateway + +所有 AI 调用必须走 AI Gateway。负责: + +```text +模型路由 +Prompt 版本管理 +JSON Schema 校验 +失败重试 + 超时控制 +token 统计 + 成本估算 +AIUsageLog +会员额度扣减 +模型降级 +``` + +业务模块只调用: + +```text +AIGateway.run("extract_knowledge_candidates") +AIGateway.run("analyze_active_recall") +AIGateway.run("knowledge_chat") +AIGateway.run("parse_complex_page") +AIGateway.run("embed_chunks") +AIGateway.run("rerank_chunks") +``` + +--- + +## 十四、Worker 部署与并发控制 + +### 1. 进程数与并发(已拍板:单 Worker 起步) + +```text +RAG Worker 进程数:1 +``` + +8 核 32G 服务器起步并发: + +```text +文档导入并发 1 +embedding batch 50~100 chunks +OCR 并发 1~2 +多模态并发 1 +候选知识点生成并发 1~2 +``` + +原因:文档解析、embedding、OCR、多模态、Qdrant upsert 都可能吃 CPU/内存/网络。先稳,再提并发。 + +### 2. 扩展条件 + +```text +CPU 长期低于 50% +内存长期低于 60% +任务队列积压明显 +``` + +满足以上 → 扩展到 2~3 Worker 进程。多 Worker 时靠数据库原子更新防止重复处理: + +```sql +UPDATE document_imports +SET status = 'CLAIMED', workerId = ?, heartbeatAt = NOW() +WHERE id = ? + AND status = 'QUEUED'; +``` + +### 3. 各环节重试策略 + +| 环节 | 重试次数 | 退避策略 | 失败后行为 | +|------|---------|---------|-----------| +| COS 下载 | 3 次 | 指数退避 1s/4s/16s | FAILED_RETRYABLE | +| OCR API | 2 次 | 固定 2s | 降级到多模态 | +| 多模态 API | 2 次 | 固定 2s | FAILED_RETRYABLE | +| Embedding batch | 2 次 | 固定 2s | 只重试该 batch | +| Qdrant upsert | 2 次 | 固定 2s | 回滚该 batch | +| Worker 崩溃 | — | heartbeat 超时 5min | 自动回队列 | +| DeepSeek 调用 | 2 次 | 固定 1s | FAILED_RETRYABLE | + +### 2. 最大重试 + +```text +maxRetries = 3 +超过 → FAILED_FINAL +→ 通知用户(iOS 解析失败页) +→ 后台告警 +``` + +--- + +## 十五、删除与清理策略(已拍板) + +### 1. 删除单个 KnowledgeSource + +```text +source.deletedAt = now() +chunks.deletedAt = now() +Qdrant points → deleted = true(不物理删除) +COS 原文件 → 进入待清理队列(7 天后清除) +KnowledgeItem → 默认保留 +``` + +用户已确认、编辑、学习过的知识点,不应因删除原文件而丢失。KnowledgeItem 记录来源状态: + +```text +knowledge_items.sourceDeleted = true +knowledge_items.sourceTitleSnapshot = 原资料标题 +knowledge_items.sourceSnippetSnapshot = 原始引用片段 +``` + +这样即使原资料被删,知识点页仍能展示"该知识点来自已删除资料"。 + +### 2. 用户删除 source 时给两个选项 + +```text +默认:仅删除原资料,保留已确认知识点 +高级:同时删除该资料生成的知识点 +``` + +### 3. 删除整个 KnowledgeBase(与人不同) + +```text +knowledgeBase.deletedAt = now() +→ 级联软删该 KB 下所有 sources / chunks / candidates / items / review cards / learning records +→ Qdrant 标记 deleted = true +→ COS 异步清理 +``` + +用户删除的是整个学习空间,所以级联删除。 + +### 4. 后台物理清理(每日凌晨) + +数据保留天数全部配置化(环境变量): + +```text +DATA_RETENTION_DAYS=30 ← 普通业务数据物理删除 +SOURCE_PURGE_DAYS=7 ← COS 原文件物理删除 +QDRANT_PURGE_DAYS=7 ← Qdrant deleted=true 点物理删除 +COS_PURGE_DAYS=7 ← COS 标记删除文件物理删除 +AI_USAGE_LOG_RETENTION_DAYS=180 ← AI 成本日志保留更久 +ADMIN_AUDIT_RETENTION_DAYS=365 ← 审计日志保留更久 +``` + +规则: + +```text +普通业务数据 soft delete 后 30 天物理删除 +COS 原文件 soft delete 后 7 天物理删除 +Qdrant deleted=true 后 7 天物理删除 +AI 成本日志保留 180 天 +后台操作审计日志保留 365 天 +用户学习记录 / 支付记录不要跟普通 source 一起快速物理删除 +``` + +--- + +## 十六、备份与灾备策略(已拍板) + +### 1. MySQL 备份 + +```text +优先级最高:MySQL > Qdrant > COS +``` + +理由:Qdrant 可重建,COS 有原文件,但 MySQL 丢了业务关系、用户数据、学习记录会非常麻烦。 + +```text +每日凌晨备份 → /data/backups/mysql/zhixi_{yyyy-mm-dd}.sql.gz +备份后上传 COS → system/backups/mysql/{yyyy-mm-dd}/zhixi.sql.gz +本地保留 7 天 +COS 保留 30 天 +``` + +### 2. Qdrant 备份 + +```text +每日凌晨 3 点生成 snapshot → /data/backups/qdrant/ +生成后上传 COS → system/backups/qdrant/{yyyy-mm-dd}/zhixi_chunks.snapshot +本地保留 7 天 +COS 保留 30 天 +``` + +### 3. 备份任务记录表 + +```text +backup_jobs +- id +- type ← QDRANT / MYSQL +- status ← RUNNING / COMPLETED / FAILED +- localPath +- cosObjectKey +- fileSizeBytes +- startedAt +- completedAt +- errorMessage +- createdAt +``` + +### 4. 恢复依赖链 + +```text +COS 原始文件 + MySQL 元数据 + Qdrant 快照 → 三者共同保证可恢复 +``` + +--- + +## 十七、文档版本管理 + +第一阶段不做完整版本管理,但预留字段: + +```text +KnowledgeSource.version = 1 +KnowledgeSource.parentSourceId = nullable +KnowledgeSource.replacedBySourceId = nullable +``` + +当前行为:用户重新上传同名文件 → 按新 source 独立处理。 + +后续升级:新版本 ready → 旧版本 Qdrant 标记 deleted=true → 旧版本 COS 保留 7 天。 + +--- + +## 十八、后台管理 + +后台要能看: + +```text +用户列表 +知识库列表 +文件列表 +DocumentImport 任务(按状态筛选) +失败任务(含错误原因) +OCR 调用记录(按用户、按时间) +多模态调用记录 +DeepSeek 调用记录 +AI 成本(按 Provider 汇总) +Qdrant 索引状态(collection 大小、points 数量) +高成本用户 Top N +会员额度使用情况 +反馈记录 +后台操作审计 +``` + +核心目标是**成本可视化和异常发现**。 + +--- + +## 十九、额度系统 + +### 1. 必须控制的维度 + +```text +知识库数量 +总存储空间 +单文件大小 +每月上传文件数 +每月普通解析页数 +每月 OCR 页数 +每月多模态页数 +每月知识库对话次数 +每月 AI 分析次数 +每月主动回忆次数 +每月复习卡生成次数 +``` + +### 2. 检查点(每次调用前) + +```text +上传前 → 存储 + 数量 +解析前 → 页数 +OCR 前 → OCR 页数 +多模态前 → 多模态页数 +知识点生成前 → AI 分析次数 +知识库对话前 → 对话次数 +AI 诊断前 → AI 分析次数 +复习卡生成前 → 复习卡次数 +``` + +### 3. 推荐初始额度(已拍板:配置化,随时可调) + +| 维度 | 免费用户 | Pro 用户 | +|------|---------|---------| +| 知识库数量 | 3 个 | 30 个 | +| 总存储 | 100 MB | 5 GB | +| 单文件大小 | 20 MB | 100 MB | +| 每月 OCR 页数 | 20 页 | 500 页 | +| 每月多模态页数 | 5 页 | 100 页 | +| 每月知识库对话 | 20 次 | 1000 次 | +| 每月 AI 诊断 | 20 次 | 500 次 | + +### 4. 定价策略 + +```text +价格不写死在代码里,后端只认 planId + quotaConfig +第一阶段按 28 元/月作为 Pro 预设 +跑 1 个月真实成本后正式确定价格和额度 +重点记录:DeepSeek token 成本 / 硅基流动视觉成本 / 百度 OCR 页数 / COS 存储 / Qdrant 增长 / 高成本用户行为 +``` + +--- + +## 二十、核心接口设计 + +### 1. 文件上传 + +```http +POST /api/files/upload-url +``` + +返回: + +```json +{ + "fileId": "file_xxx", + "uploadUrl": "...", + "objectKey": "...", + "headers": {}, + "duplicateOf": null +} +``` + +如果 sha256 匹配到已有文件,返回 `duplicateOf` 指向已有 fileId,iOS 提示用户。 + +### 2. 知识库 CRUD + +```http +POST /api/knowledge-bases +GET /api/knowledge-bases +GET /api/knowledge-bases/:id +PATCH /api/knowledge-bases/:id +DELETE /api/knowledge-bases/:id +``` + +### 3. 资料来源 + +```http +POST /api/knowledge-bases/:id/sources +GET /api/knowledge-bases/:id/sources +GET /api/knowledge-sources/:sourceId +DELETE /api/knowledge-sources/:sourceId +``` + +创建 source 后自动生成 KnowledgeSource + DocumentImport。 + +### 4. 导入任务 + +```http +GET /api/document-imports/:id +GET /api/knowledge-sources/:sourceId/imports/latest +POST /api/document-imports/:id/retry +POST /api/document-imports/:id/cancel +``` + +iOS 用这些接口展示导入进度(排队中 → 解析中 → 索引中 → 生成知识点中 → 等待确认 → 完成 / 失败可重试)。 + +### 5. Worker 内部接口 + +```http +GET /internal/rag/jobs/next +POST /internal/rag/jobs/:id/heartbeat +POST /internal/rag/jobs/:id/result +POST /internal/rag/jobs/:id/fail +``` + +### 6. 候选知识点 + +```http +GET /api/knowledge-sources/:sourceId/import-candidates +PATCH /api/import-candidates/:id +POST /api/import-candidates/:id/accept +POST /api/import-candidates/:id/reject +POST /api/import-candidates/batch-accept +``` + +用户确认后生成 KnowledgeItem。 + +### 7. 正式知识点 + +```http +GET /api/knowledge-bases/:id/items +GET /api/knowledge-items/:id +POST /api/knowledge-items ← 手动创建 +PATCH /api/knowledge-items/:id +DELETE /api/knowledge-items/:id +``` + +### 8. 知识库对话 + +```http +POST /api/knowledge-bases/:id/chat +GET /api/knowledge-bases/:id/chat-sessions +GET /api/knowledge-chat-sessions/:id/messages +``` + +返回: + +```json +{ + "answer": "...", + "citations": [ + { + "sourceId": "src_xxx", + "chunkId": "chunk_xxx", + "title": "资料标题", + "snippet": "引用片段", + "pageNumber": 3 + } + ], + "suggestedActions": [ + "CREATE_KNOWLEDGE_ITEM", + "GENERATE_ACTIVE_RECALL", + "ADD_TO_FOCUS_ITEM" + ] +} +``` + +### 9. 单文件学习 + +```http +POST /api/knowledge-sources/:sourceId/prepare-learning +GET /api/knowledge-sources/:sourceId/learning-view +POST /api/knowledge-items/:id/active-recall +POST /api/active-recall-answers/:id/analyze +GET /api/knowledge-items/:id/review-cards +``` + +流程: + +```text +source → ImportCandidate → KnowledgeItem +→ ActiveRecall → AIAnalysis → FocusItem → ReviewCard +``` + +--- + +## 二十一、知识库主流程 + +### 1. 索引流程 + +```text +iOS 上传文件到 COS +→ 后端创建 File(含 sha256 重复检测) +→ 后端创建 KnowledgeSource +→ 创建 DocumentImport(status = QUEUED) +→ Worker claim 任务(QUEUED → CLAIMED) +→ Worker 从 COS 拉文件到 /data/tmp/imports/{jobId} +→ 本地解析 / OCR / 多模态 +→ 写入 parsed.md 到 COS +→ 清洗文本 +→ chunking(512 tokens + 64 overlap) +→ embedding(bge-m3,batch 50~100) +→ Qdrant upsert +→ 保存 KnowledgeChunk 到 MySQL +→ source.indexStatus = INDEXED +→ import.status = COMPLETED +``` + +### 2. 学习流程 + +```text +用户打开文件 +→ DeepSeek 生成 ImportCandidate(上限 30 条) +→ 用户确认 / 编辑 / 拒绝 +→ 生成 KnowledgeItem +→ 用户主动回忆 +→ DeepSeek 诊断(thinking 模式) +→ 生成 FocusItem(待巩固项) +→ 生成 ReviewCard(复习卡) +→ 记录 LearningActivity +``` + +--- + +## 二十二、iOS 需要的页面 + +### 知识库相关 + +```text +知识库列表页 +知识库详情页 +创建知识库页 +上传资料页 +资料列表页 +资料详情页 +导入进度页(含步骤+进度条) +解析失败页(含错误原因+重试按钮) +候选知识点确认页(批量接受/拒绝/编辑) +知识点详情页 +知识库对话页 +``` + +### 学习相关 + +```text +单文件学习首页 +主动回忆输入页 +AI 分析结果页 +待巩固项页 +复习卡页 +学习记录页 +``` + +### 额度相关 + +```text +额度展示页 +会员升级页 +OCR / 多模态额度提示 +文件过大提示 +解析额度不足提示 +重复文件提示 +``` + +--- + +## 二十三、执行顺序 + +### 第一阶段:服务器基础 + +```text +1. 购买服务器 +2. 挂载 /data 数据盘 +3. 配置安全组 +4. 安装 Docker / Docker Compose +5. 配置 Docker data-root → /data/docker +6. 部署 Qdrant(含 payload 索引创建) +7. 配置 COS SDK +8. 配置 MySQL 每日备份脚本 + COS 同步 +9. 配置 Qdrant 每日快照脚本 + COS 同步 +``` + +### 第二阶段:基础数据模型 + +```text +1. File(含 sha256) +2. KnowledgeBase +3. KnowledgeSource(含 version/reserved 字段) +4. DocumentImport(含 retryCount/heartbeat 机制) +5. KnowledgeChunk +6. ImportCandidate(含 sourceChunkIds/difficulty) +7. KnowledgeItem +``` + +### 第三阶段:文件导入闭环 + +```text +1. iOS 申请上传 URL +2. iOS 直传 COS + sha256 计算 +3. 后端重复文件检测 +4. 创建 source + import 任务 +5. Worker claim + heartbeat +6. 本地解析(PDF/DOCX/TXT/MD) +7. OCR / 多模态(按需) +8. 写入 parsed.md 到 COS +9. 更新任务状态 +``` + +### 第四阶段:RAG 索引 + +```text +1. chunking(512 tokens + 64 overlap) +2. embedding(bge-m3, batch 50~100) +3. Qdrant upsert +4. 保存 KnowledgeChunk 到 MySQL +5. source.indexStatus = INDEXED +``` + +### 第五阶段:AI 学习化 + +```text +1. DeepSeek 生成 ImportCandidate +2. iOS 展示候选知识点(含置信度、标签、回忆题) +3. 用户确认 / 编辑 +4. 生成 KnowledgeItem +``` + +### 第六阶段:单文件学习 + +```text +1. 主动回忆 +2. AI 诊断(thinking 模式) +3. 待巩固项 → FocusItem +4. 复习卡 → ReviewCard +5. 学习记录 +``` + +### 第七阶段:知识库增强 + +```text +1. 知识库对话(单轮 RAG + 最近 3 轮上下文) +2. 引用来源(citations) +3. rerank 精排 +4. 推荐学习内容 +5. 后台成本监控 +6. 额度检查全链路接入 +``` + +--- + +## 二十四、最终落地原则 + +```text +COS 存原始文件 → 检索前临时拉取,不留本地 +Qdrant 存向量索引 → 单节点,1024d Cosine,deleted 标记而非物理删除 +MySQL 存业务状态 → Prisma ORM,软删除 + 后台清理 +DeepSeek 负责核心文本智能 → Flash 日常 + thinking 诊断 + Pro 高价值 +硅基流动负责工具模型 → embedding / rerank / 多模态 +百度 OCR 负责普通扫描文字 → 复杂页面交给 Qwen3-VL +切片 512 token + 64 overlap → 递归分割 + 中文分句保护 +候选知识点上限 30 条/source → 不自动接受,用户确认 +删除策略软删除 + 7 天冷却 → 后台定时物理清理 +所有成本进入额度系统 → 每次调用前查额度 +``` + +--- + +## 二十五、全部决策汇总 + +| # | 决策项 | 最终决策 | +|---|--------|---------| +| 1 | Chunk size | 512 tokens | +| 2 | Overlap | 64 tokens(~12%) | +| 3 | 切片策略 | 递归字符分割 + 中文分句保护 | +| 4 | Embedding 模型 | BAAI/bge-m3,硅基流动 | +| 5 | Vector 维度 | 1024 | +| 6 | Qdrant distance | Cosine | +| 7 | Qdrant 部署 | 单节点 Docker,1 shard,replication_factor=1 | +| 8 | Qdrant 集群时机 | 100 万 points 或资源瓶颈后评估 | +| 9 | Rerank 模型 | BAAI/bge-reranker-v2-m3 | +| 10 | RAG 召回 | Top-50 ANN → rerank → Top-5~8 | +| 11 | 知识库对话 | 仅限 KB 内检索,不支持通用 AI 问答 | +| 12 | 多轮对话 | 保存 session + 最近 3 轮上下文拼入 prompt,不做 query rewrite | +| 13 | 候选知识点数量 | 每 2000 字 1~2 条,上限 30,最少 3 | +| 14 | 自动接受 | 第一阶段不做,全部 PENDING 等确认 | +| 15 | OCR | 百度 OCR(简单文字),Qwen3-VL(复杂排版) | +| 16 | 多模态兜底 | 硅基流动 Qwen3-VL-32B-Thinking | +| 17 | 删除 source → KnowledgeItem | 默认保留,记录 sourceDeleted 快照 | +| 18 | 删除 KB → 全对象 | 级联删除 | +| 19 | Qdrant 快照 | 本地 + 同步 COS | +| 20 | Qdrant 本地快照保留 | 7 天 | +| 21 | Qdrant COS 快照保留 | 30 天 | +| 22 | MySQL 备份 | 每日凌晨 + 同步 COS | +| 23 | COS 文件清理 | soft delete 后 7 天 | +| 24 | MySQL 物理删除 | 默认 30 天,环境变量可配 | +| 25 | AI 成本日志保留 | 180 天 | +| 26 | 审计日志保留 | 365 天 | +| 27 | Pro 定价 | 28 元/月预设,配置化,跑 1 个月成本后调整 | +| 28 | Worker 进程数 | 单 Worker 起步 | +| 29 | Worker 扩展 | CPU/内存/队列压力上来后 2~3 个 | +| 30 | 文档版本管理 | 第一阶段不做,预留 version 字段 | +| 31 | 重复文件 | sha256 检测 + 提示用户,不做自动去重 | +| 32 | 语言分工 | Node/NestJS=API+AI Gateway;Python=RAG Worker | + +--- + +你接下来就按这个顺序推进:**先部署服务器环境,再建数据模型,再做上传和导入任务,再接 Qdrant,最后接学习闭环和知识库对话。**