第一步

This commit is contained in:
amb
2026-09-01 11:53:59 +08:00
commit 47bf6cc5ca
66 changed files with 6501 additions and 0 deletions
+342
View File
@@ -0,0 +1,342 @@
# AI Knowledge Link — 产品需求文档(MVP v1
> 状态标注说明:✅ 已完成 | 🔄 部分完成 | ⬜ 未开始(对应 Phase N) | 📌 持续约束(贯穿全程)
>
> **版本说明**:本文档为当前有效需求(MVP 版)。早前一版需求(PostgreSQL + Redis + Celery + MinIO 全功能栈)已被本 MVP 版**取代**,核心差异:SQLite 替代 PostgreSQL、本地文件系统替代 MinIO、同步解析替代 Celery、内存限流替代 Redis、FTS5 替代 jieba+tsvector。原版中的商业许可审查结论仍有效(PDF 解析商品化前需将 PyMuPDF 替换为 pypdfium2,见 `technical-review.md` R3)。
>
> 关联文档:[技术审查报告](technical-review.md) [CLAUDE.md](../CLAUDE.md)
## Phase 进度总览(对应 §56 开发顺序)
| Phase | 内容 | 状态 |
|---|---|---|
| 1 | 项目初始化 | ✅ 完成 |
| 2 | 数据库和 Alembic | ⬜ |
| 3 | 用户注册登录 | ⬜ |
| 4 | 知识库 CRUD | ⬜ |
| 5 | 本地 StorageService | ✅ 完成(提前实现于 Phase 1) |
| 6 | 文档上传 | ⬜ |
| 7 | 文档解析和 Markdown | ⬜ |
| 8 | 文档管理 | ⬜ |
| 9 | Secret URL | ⬜ |
| 10 | AI 公共页面 | ⬜ |
| 11 | Markdown/TXT/JSON 输出 | ⬜ |
| 12 | 搜索 | ⬜ |
| 13 | 安全 | ⬜ |
| 14 | 前端完善 | ⬜ |
| 15 | 测试 | ⬜ |
| 16 | Docker | 🔄 compose/Dockerfile 已写(Phase 1),待 Docker 环境验证 |
| 17 | Ubuntu 部署文档 | ⬜ |
---
## 一、产品核心目标 ⬜(产品定位,随各 Phase 逐步实现)
这个产品不是普通网盘。
核心功能是:
用户上传自己的 Word、PDF 等文档 → 系统自动解析文档 → 转换成 Markdown → 建立个人知识库 → 系统生成一个 AI 专属访问链接 → 用户复制这个链接 → 发送给第三方 AI → AI 访问知识库 → AI 查看知识库中的文档列表 → 根据用户的描述选择一个或多个相关文档 → 访问具体文档内容 → 利用这些资料回答用户问题
例如:用户上传 公司介绍.docx、产品说明.pdf、技术架构.pdf、项目需求.docx,系统生成 `https://example.com/k/xxxxxxxx`。用户把这个链接发送给 AI:"请访问这个知识库,阅读产品A相关的技术架构和产品说明,然后告诉我产品A有哪些技术风险。" AI 访问 /k/xxxxxxxx,看到文档列表,然后根据用户要求访问相关文档。
## 二、第一阶段必须保持简单 📌 持续约束(Phase 1 已遵循)
这是一个 MVP。第一版禁止过度工程化。
第一版只使用:
- 前端:Vue 3、TypeScript、Vite、Element Plus、Pinia、Axios
- 后端:Python 3.12+、FastAPI、SQLAlchemy、Pydantic、Alembic
- 数据库:SQLite
- 文件存储:服务器本地文件系统
- 文档解析:MarkItDown、PyMuPDF、python-docx
- 部署:Docker Compose,可以使用 Nginx
第一版禁止强制依赖:PostgreSQL、Redis、Celery、MinIO、Qdrant、Elasticsearch、Kafka。不要因为未来可能商品化而提前加入这些组件。
## 三、开发环境 ✅ 完成(Phase 1 验证通过)
本地开发环境:Windows。本地没有 Docker。因此 Windows 开发必须可以直接运行。
- 前端:`npm install``npm run dev`
- 后端:Python venv → `pip install``uvicorn`
- 本地开发不要求 Docker ✅
- Docker 主要用于 Ubuntu 云服务器
生产环境:Ubuntu、Docker、Docker Compose。
## 四、第一版系统架构 ⬜(架构已定,实现随 Phase 推进)
采用:
- Windows 开发:Vue 3 + FastAPI + SQLite + 本地文件
- 生产:Ubuntu + Dockerfrontend / backend / nginx
数据目录:
```
/data
├── app.db
└── users/{user_id}/knowledge_bases/{knowledge_base_id}/
├── original/
└── markdown/
```
SQLite 保存元数据。文件系统保存:Word、PDF、Markdown。
## 五、不要把文件塞进 SQLite 📌 持续约束(存储层设计已按此实现)
禁止把原始 PDF、Word 二进制内容存进 SQLite。SQLite 只保存:用户信息、知识库信息、文档元数据、文件路径、Markdown 路径、文档状态、Token hash、时间、分类、描述、关键词、搜索相关数据。原始文件与 Markdown 均存服务器文件系统。
## 六、数据库访问必须抽象 ⬜ Phase 2Phase 1 已建 db.py 引擎层)
不能在业务代码中到处直接调用 sqlite3。必须使用 SQLAlchemy,并通过 Repository / Service 分层(UserRepository、KnowledgeBaseRepository、DocumentRepository)。业务逻辑不能依赖 SQLite 具体实现。数据库配置通过 `DATABASE_URL`(如 `sqlite:///./data/app.db`),未来可以切换 PostgreSQL 而尽量不修改业务层。
## 七、未来扩展接口 1SQLite → PostgreSQL 📌 持续约束(设计规则已在 db.py 落地)
第一版使用 SQLite,但设计 DatabaseRepository、SQLAlchemy models、Alembic migrations。不要使用 SQLite 特有 SQL 作为核心业务逻辑;避免 PRAGMA(WAL 设置除外)、SQLite 专属函数、SQLite 专属数据类型。业务代码尽可能使用标准 SQLAlchemy。未来切换只需更换连接配置并处理迁移。
## 八、未来扩展接口 2:本地文件 → MinIO/S3 ✅ 完成(Phase 1 提前实现)
已实现 `StorageService` 协议(`storage/base.py`save/read/delete/exists/get_size)与 `LocalStorageService``storage/local_storage.py`)。业务代码不能直接 `open("/data/xxx")`,所有文件读写必须经过 StorageService。未来可增加 MinioStorageService / S3StorageService。
## 九、未来扩展接口 3:同步解析 → 异步任务 ⬜ Phase 7DocumentProcessor 协议)
第一版:上传文件 → FastAPI → 同步解析 → 保存 Markdown → 返回结果。必须抽象 `DocumentProcessor`(如 `process_document()`):第一版 `LocalDocumentProcessor`,未来可增加 `CeleryDocumentProcessor`。业务代码不要把文档解析逻辑写进 API endpoint。
## 十、未来扩展接口 4:关键词搜索 → RAG ⬜ Phase 12Retriever 协议)
第一版不使用向量数据库。实现 `Retriever` 统一接口(`search(query, knowledge_base_id)`),第一版 `KeywordRetriever`(SQLite FTS 或简单关键词搜索)。未来可加 VectorRetriever / HybridRetriever / QdrantRetriever、Embedding、Reranker。第一版不安装这些依赖。
## 十一、未来扩展接口 5:个人用户 → 团队/企业 📌 持续约束(数据库设计时遵守)
第一版:一个用户拥有多个知识库(User → KnowledgeBase → Documents)。但数据库设计必须避免以后无法扩展。未来:Organization → Members → KnowledgeBases → Documents。第一版不实现团队,但不要把 user_id 硬编码成未来无法扩展的权限结构。
## 十二、未来扩展接口 6:AI 网页访问 → API/MCP 📌 持续约束(Phase 10 落地)
第一版:AI 通过 Secret URL 访问(GET /k/{token}),这是产品核心功能。未来可增加 API Key、REST API、MCP Server、OpenAPI、AI Agent 接口。第一版不实现 MCP,但公共知识库服务层必须独立:HTML、Markdown、TXT、JSON 都调用同一个 `KnowledgeBasePublicService`Controller → Service → Repository → Database),不要每种格式各写一套查询逻辑。
## 十三、用户系统 ⬜ Phase 3
实现注册、登录、退出登录。
Userid、username、email、password_hash、status、storage_quota、storage_used、created_at、updated_at。密码必须安全哈希,优先 Argon2id;禁止明文密码、MD5、SHA1。
## 十四、存储限制 🔄 部分完成(config.py 已有配额配置;Plan 模型 Phase 2 落地)
默认免费用户 100MB,单文件 20MB。不要硬编码,放到配置或 Plan 模型。第一版 Free Plan100MB / 20MB-per-file),未来 Basic 1GB、Pro 5GB、Enterprise 自定义。第一版不实现支付。
## 十五、知识库 ⬜ Phase 4
KnowledgeBaseid、user_id、name、description、enabled、secret_token_hash、created_at、updated_at。一个用户可以创建多个知识库(公司知识库、项目A、项目B、个人AI资料)。
## 十六、AI 专属链接 ⬜ Phase 9
每个知识库生成一个 Secret Token`https://example.com/k/7fA92xKpQ8m...`)。必须使用密码学安全随机数(`secrets.token_urlsafe()` 或同等方案);禁止自增 ID、时间戳、用户名、UUID 短截断。数据库保存 token_hash 而不是明文 Token。用户复制的完整 URL 只在生成时提供给用户。
## 十七、AI 链接安全模型 ⬜ Phase 9/13
公共 AI 访问不需要登录(第三方 AI 无法使用用户后台登录状态),Secret URL 本身就是访问凭证,相当于 Bearer Credential——任何获得 URL 的人理论上都可以读取知识库,必须在产品中明确这一点。提供启用/禁用链接、重新生成链接;重新生成后旧链接立即失效。
## 十八、AI 知识库首页 ⬜ Phase 10
`GET /k/{token}` 必须返回服务端生成的 HTML,不能依赖 Vue / JavaScript(第三方 AI 可能不执行 JS),页面必须首屏直接包含核心信息。页面结构:title=知识库名称、`<meta name="robots" content="noindex,nofollow,noarchive">`、知识库名称、知识库描述、Documents 列表(每篇含标题、描述、类型、更新时间、URL)。
## 十九、多格式输出 ⬜ Phase 11
- `GET /k/{token}` → HTML
- `GET /k/{token}.md` → Markdown
- `GET /k/{token}.txt` → 纯文本
- `GET /k/{token}.json` → JSON
JSON 只能返回必要的信息,禁止:用户密码、token hash、内部数据库 ID、服务器路径、MinIO 地址、内部配置。
## 二十、单文档访问 ⬜ Phase 10/11
- `GET /k/{token}/doc/{document_token}` → HTML
- `GET /k/{token}/doc/{document_token}.md` → Markdown
- `GET /k/{token}/doc/{document_token}.txt` → TXT
文档页面:标题、描述、文件类型、关键词、更新时间、Markdown 正文。默认不直接提供原始文件下载。
## 二十一、文档 Token ⬜ Phase 9
不要使用 /doc/1 这种容易猜测的 URL。每个文档生成随机 document_token,数据库保存 hash。例如 `/k/knowledge-token/doc/document-token`,两个 Token 都必须随机。
## 二十二、文档上传 ⬜ Phase 6
支持 .docx、.pdf;暂不支持 .doc、.xls、.xlsx、.ppt、.pptx,但代码架构预留扩展。
上传流程:POST /api/documents/upload → 检查登录 → 检查知识库权限 → 检查文件大小 → 检查扩展名 → 检查 MIME → 计算 SHA256 → 检查存储额度 → 保存原始文件 → 解析 → 转换 Markdown → 提取标题 → 生成摘要 → 提取关键词 → 保存 Markdown → 更新 Document → 返回结果。
## 二十三、文件目录 📌 持续约束(key 生成已实现于 storage/object_keys.py
推荐布局:
```
data/
├── app.db
└── users/{user_id}/knowledge_bases/{knowledge_base_id}/
├── original/ random-name.docx / random-name.pdf
└── markdown/ random-name.md
```
不要使用用户原始文件名直接作为物理存储路径(如 `../../evil.pdf` 必须安全处理)。
## 二十四、文档解析 ⬜ Phase 7
优先 MarkItDownPDF 用 PyMuPDF 作为辅助/fallbackWord 用 python-docx 作为 fallback。Markdown 需尽可能保留:标题、段落、列表、表格、粗体、斜体、代码块、引用。解析失败时 Document.status = FAILED,不向用户显示 Traceback / 服务器路径 / Python 异常堆栈,只显示友好错误("文档解析失败,请检查文件是否损坏或格式是否受支持。"),服务器日志记录详细错误。
## 二十五、扫描 PDF ⬜ Phase 7
第一版不强制 OCR。如果 PDF 没有文本层,提示"该 PDF 可能是扫描件,当前版本暂不支持 OCR。"未来预留 OCRProcessorPaddleOCR 等),第一版不加入 OCR。
## 二十六、文档状态 ⬜ Phase 2(状态模型)/ Phase 7(流转)
PENDING、PROCESSING、READY、FAILED、DELETED。虽然第一版同步处理,但状态模型必须保留,未来异步任务可以直接使用。
## 二十七、知识库搜索 ⬜ Phase 12
`GET /k/{token}/search?q=关键词` 返回:文档、标题、匹配摘要、URL。第一版用 SQLite FTS 或简单关键词搜索,实现 Retriever 接口(第一版 KeywordRetriever)。不引入 Qdrant、Embedding。
## 二十八、AI 选择多个文档 ⬜ Phase 10(产品最重要功能之一)
知识库首页不能把所有文档正文全部输出。首页应提供:文档标题、文档描述、关键词、文档类型、文档 URL。例如"产品A技术架构(关键词:产品A、架构、服务器、数据库、Redis)→ /k/xxxx/doc/yyyy"。这样 AI 可以根据用户描述选择文档——用户说"请阅读产品A的架构和数据库设计",AI 应能发现并访问"产品A技术架构"和"产品A数据库设计"两个文档。
## 二十九、AI 兼容性 ⬜ Phase 10/11README 声明 Phase 17
不能假设第三方 AI 一定支持 JavaScript、Cookie、登录、API 调用、自动跟随所有链接、搜索接口。AI 页面必须:服务端渲染、HTML 标准、正文直接返回、链接使用标准 `<a href>`、不依赖 JS、不使用复杂 SPA、内容结构清晰。同时提供 HTML / Markdown / TXT / JSON,尽可能提高 DeepSeek、豆包、Kimi、ChatGPT 及其他支持网页访问的 AI 的兼容性。README 必须明确:第三方 AI 是否支持访问外部 URL、跟随链接和读取页面,由第三方 AI 自身能力决定,本系统不能保证所有 AI 都一定会访问后续文档。
## 三十、前端 🔄 骨架完成(Phase 1);完整 UI ⬜ Phase 14
Vue 3、TypeScript、Vite、Element Plus、Pinia。UI 要求:现代、简洁、SaaS 风格、中文、不要花哨、不要大量动画。主要页面:/login、/register、/dashboard、/knowledge-bases、/knowledge-bases/:id、/settings。
## 三十一、登录页面 🔄 占位页已有(Phase 1);功能 ⬜ Phase 3/14
登录:用户名/邮箱、密码、登录。注册:用户名、邮箱、密码、确认密码。基本校验。
## 三十二、Dashboard 🔄 占位页已有(Phase 1);功能 ⬜ Phase 14
显示:知识库数量、文档数量、已使用空间/总空间(如 32MB / 100MB)、最近知识库、最近上传、解析失败文件。
## 三十三、知识库管理 ⬜ Phase 4(接口)/ Phase 14UI
显示:知识库名称、描述、文档数量、空间、创建时间、AI 链接。操作:管理、复制 AI 链接、预览、启用/禁用、重新生成链接、删除。
## 三十四、文档管理 ⬜ Phase 6/8(接口)/ Phase 14UI
支持拖拽上传、多文件上传。显示:文件名、类型、大小、状态、更新时间。操作:预览、重新解析、删除。
## 三十五、文档编辑 ⬜ Phase 8
用户可以修改:标题、描述、关键词、分类。Markdown 正文默认由系统生成。第一版可提供 Markdown 预览;是否允许直接编辑 Markdown 不是最高优先级,时间有限可暂时只读。
## 三十六、权限 ⬜ Phase 13(编码时全程遵守)
必须防止 IDOR:用户A访问 /api/knowledge-bases/10,不能通过修改 10 → 11 读取用户B知识库。所有后台接口必须验证:当前登录用户 → 资源 owner → 允许操作。不能只验证资源 ID 存在。
## 三十七、公共 AI 链接权限 ⬜ Phase 9
公共 AI URL 不要求登录,但必须验证 Secret Token。enabled = false 返回 404。重新生成 Token 后旧 Token 立即失效。
## 三十八、安全 ⬜ Phase 13(编码时全程遵守)
考虑:SQL 注入、XSS、CSRF、路径穿越、恶意文件、超大文件、Zip Bomb、暴力破解、Token 猜测、IDOR、资源耗尽。上传文件:限制大小、限制扩展名、限制 MIME、随机物理文件名。Markdown 输出必须防存储型 XSS;用户输入的标题、描述、关键词不能直接插入 HTML;Markdown 渲染需要安全处理。
## 三十九、限流 ✅ RateLimiter 抽象完成(Phase 1);接入公共 URL ⬜ Phase 13
第一版不需要 Redis,可以使用内存限流(如单 IP 每分钟一定次数),保护公共 AI URL。注意内存限流只适合单实例 MVP,代码中抽象 RateLimiter,未来可实现 RedisRateLimiter。(已实现 `core/rate_limit.py`:内存 TokenBucket + 配置化阈值。)
## 四十、访问日志 ⬜ Phase 2(模型)/ Phase 10(记录)
第一版可简单记录:knowledge_base_id、document_id、timestamp、user_agent、请求类型。不要默认长期保存完整 IP。后台可显示访问次数、最近访问时间。不要声称可以准确判断访问者是不是 AI,使用"外部访问"而不是"AI 访问"。
## 四十一、数据库模型 ⬜ Phase 2
至少:User、Plan、KnowledgeBase、Document、DocumentCategory、AccessLog。
Documentid、knowledge_base_id、user_id、document_token_hash、original_filename、storage_path、markdown_path、file_size、mime_type、sha256、title、description、keywords、category_id、status、created_at、updated_at。
KnowledgeBaseid、user_id、name、description、secret_token_hash、enabled、created_at、updated_at。
## 四十二、数据库迁移 ⬜ Phase 2
使用 Alembic,即使 SQLite 也必须使用迁移。不要手工修改生产数据库。README 提供:初始化迁移、升级迁移、回滚迁移。
## 四十三、API 设计 🔄 分 Phase 实现
认证:POST /api/auth/register、POST /api/auth/login、POST /api/auth/logout、GET /api/me
知识库:GET/POST /api/knowledge-bases、GET/PUT/DELETE /api/knowledge-bases/{id}
知识库链接:POST /api/knowledge-bases/{id}/regenerate-token、/enable、/disable
文档:GET /api/knowledge-bases/{id}/documents、POST /api/knowledge-bases/{id}/documents、GET/PUT/DELETE /api/documents/{id}、POST /api/documents/{id}/reprocess
存储:GET /api/storage
公共:GET /k/{token}+.md/.txt/.json)、GET /k/{token}/search?q=、GET /k/{token}/doc/{document_token}+.md/.txt
## 四十四、统一 Service 层 🔄 分 Phase 实现(Phase 1 已落 StorageService
不要把业务逻辑全部写进 FastAPI 路由。至少:AuthService、UserService、KnowledgeBaseService、DocumentService、DocumentProcessor、StorageService、KnowledgeBasePublicService、Retriever、RateLimiter。
## 四十五、公共 AI 访问统一 Service ⬜ Phase 10
HTML / Markdown / TXT / JSON 各 Renderer 都必须:KnowledgeBasePublicService → 返回知识库数据 → 各格式 Renderer。所有格式必须使用同一套数据来源。
## 四十六、Docker 🔄 compose/Dockerfile 已写(Phase 1);验证 ⬜ Phase 16
虽然 Windows 开发不需要 Docker,但项目必须提供生产 Docker 配置。docker-compose.ymlfrontend、backend、nginx。SQLite 和文件:宿主机 ./data 挂载到 /app/data。重要:数据库和用户文件不能写进 Docker 镜像;删除容器以后数据仍然存在。
## 四十七、生产目录 ⬜ Phase 17
服务器 /opt/ai-knowledge-link/docker-compose.yml、.env、data/app.db + users/)、nginx/、project/(或合理目录)。data 必须独立于代码。
## 四十八、备份 ⬜ Phase 17
第一版使用 SQLite,必须提供备份脚本:app.db + users/。建议每日备份,保留最近 7 天、最近 4 周。备份不能影响正在运行的服务。README 说明如何备份、如何恢复。
## 四十九、Git ✅ 完成(Phase 1
已提供 .gitignore(禁止提交:.env、data/、*.db、上传文件、虚拟环境、node_modules)与 .env.example。
## 五十、Windows 开发 ✅ 完成(Phase 1,README 已含完整步骤;实测通过)
README 必须详细说明:安装 Python、创建 venv`python -m venv .venv`、Windows 激活 `.venv\Scripts\activate`)、安装依赖(`pip install -r requirements.txt`)、启动 FastAPI`uvicorn app.main:app --reload`);安装 Node、`npm install``npm run dev`
## 五十一、生产部署 ⬜ Phase 17
README 必须详细说明:Ubuntu 安装 Docker、克隆 Git 仓库、配置 .env、创建 data 目录、启动(`docker compose up -d --build`)、查看(`docker compose ps`)、日志(`docker compose logs -f`)、更新(`git pull && docker compose up -d --build`)。数据不能因为更新丢失。
## 五十二、测试 🔄 冒烟测试 3 条通过(Phase 1);完整覆盖 ⬜ Phase 15
必须编写测试,至少覆盖:注册、登录、错误密码、创建知识库、删除知识库、用户隔离、上传 DOCX、上传 PDF、文件大小限制、存储空间限制、文档解析、Markdown 生成、Token 生成、Token 禁用、Token 重新生成、旧 Token 失效、公共知识库 HTML、公共 Markdown、公共 TXT、公共 JSON、单文档访问、文档 Token、搜索、XSS 防护、路径穿越、IDOR。
## 五十三、项目目录 ✅ 完成(Phase 1)
```
project/
├── backend/app/{api,core,models,schemas,repositories,services,processors,storage,retrieval,public}、alembic/、tests/、requirements.txt、Dockerfile
├── frontend/src/{api,components,layouts,views,stores,router,types}、package.json、Dockerfile
├── nginx/nginx.conf
├── data/.gitkeep
├── docker-compose.yml
├── .env.example
├── .gitignore
└── README.md
```
## 五十四、代码原则 📌 持续约束(Phase 1 已遵循)
不要:一个 main.py 写完所有东西;所有数据库查询写在路由;业务代码直接 open 文件;硬编码路径;硬编码 100MB;硬编码 Secret Token;把密码明文保存;把用户 A/B 数据混在一起;使用 Vue 渲染 AI 公共页面。
## 五十五、产品第一阶段验收标准 ⬜ 最终验收(Phase 15 后)
1. 用户注册。2. 用户登录。3. 创建"公司知识库"。4. 上传 公司介绍.docx、产品说明.pdf、技术架构.pdf。5. 系统自动转换 Markdown。6. 后台显示 3 个文档。7. 用户复制 AI 专属链接。8. 退出登录。9. 未登录状态打开链接仍能访问知识库。10. 页面显示知识库名称、描述、文档列表、文档描述、文档 URL。11. 访问其中一个文档能看到 Markdown 正文。12. 访问 .md 得到 Markdown。13. 访问 .txt 得到纯文本。14. 访问 .json 得到机器可读数据。15. /search?q=产品A 能找到相关文档。16. 重新生成 AI 链接后旧链接立即失效。17. 禁用 AI 链接后公共访问失败。18. 用户A无法访问用户B后台数据。19. 用户A无法通过修改 ID 读取用户B资源。20. Docker 部署后删除容器并重新创建,用户数据不能丢失。
## 五十六、第一阶段开发顺序 ✅ Phase 1 完成,按序推进中
Phase 1 项目初始化 → Phase 2 数据库和 Alembic → Phase 3 用户注册登录 → Phase 4 知识库 CRUD → Phase 5 本地 StorageService → Phase 6 文档上传 → Phase 7 文档解析和 Markdown → Phase 8 文档管理 → Phase 9 Secret URL → Phase 10 AI 公共页面 → Phase 11 Markdown/TXT/JSON 输出 → Phase 12 搜索 → Phase 13 安全 → Phase 14 前端完善 → Phase 15 测试 → Phase 16 Docker → Phase 17 Ubuntu 部署文档。
每完成一个 Phase:1. 检查代码 2. 运行测试 3. 修复错误 4. 确认没有破坏之前功能 5. 再进入下一阶段。
## 五十七、当前任务 ✅ 完成(技术审查报告见 technical-review.md
第一步不要立即创建大量业务代码,先对整个需求进行技术审查,输出:最终架构、数据库 ER 结构、API 结构、项目目录、六个未来扩展接口如何设计、安全模型、文档处理流程、AI 访问流程、Windows 开发流程、Docker 生产流程、发现的风险、建议修改的地方。确认后从 Phase 1 开始,不要跳过架构审查。
+536
View File
@@ -0,0 +1,536 @@
# AI Knowledge Link — 技术审查报告(MVP v1
> 审查范围:需求文档全文(57 节)。
> 核心原则:**MVP 保持简单,6 个扩展接口预留但不实现,SQLite + 本地文件 + 同步解析**。
> 结论:架构成立,核心需求可实现。发现 1 个必须修改的设计、若干建议补充。
---
## 1. 最终架构
### 1.1 架构总览
```
开发环境(Windows 生产环境(Ubuntu Docker
───────────────── ─────────────────────
Vue3 dev server ─┐ nginx :80
├─► FastAPI :8000 ├── /api/** → backend:8000
uvicorn --reload ─┘ ├── /k/** → backend:8000
└── / → frontend:80
FastAPI Docker Compose:
├── /api/** 管理端(需登录) ├── frontend (Vue build → nginx serve)
├── /k/** 公共AI页(SSR, 零JS ├── backend (uvicorn)
└── SQLite + 本地文件系统 └── nginx
/data/ /data/ (宿主机 volume)
├── app.db ├── app.db
└── users/{uid}/kbs/{kb}/... └── users/{uid}/kbs/{kb}/...
├── original/ ├── original/
└── markdown/ └── markdown/
```
### 1.2 分层
```
api/ → 路由层:参数校验 + 鉴权 + 调 Service + 组装响应
services/ → 业务逻辑:配额、Token、编排
repositories/→ 数据访问:隔离 SQLAlchemy 细节
processors/ → 文档解析:MarkItDown → PyMuPDF/python-docx fallback
storage/ → 存储抽象:LocalStorageService(未来 MinIO/S3
retrieval/ → 检索抽象:KeywordRetriever(未来 Vector/Hybrid
public/ → AI 公共页面渲染:Jinja2 SSR + md/txt/json 生成器
```
### 1.3 关键技术决策
| 决策点 | 选择 | 理由 |
|---|---|---|
| 数据库 | **SQLite** + SQLAlchemy + Alembic | MVPDATABASE_URL 可切换 PostgreSQL(§7 扩展接口) |
| 文件存储 | **本地文件系统** + StorageService 抽象 | MVP;不引入 MinIO(§8 扩展接口) |
| 文档解析 | **同步** + DocumentProcessor 抽象 | MVP 单用户低并发;未来 Celery(§9 扩展接口) |
| 检索 | **SQLite FTS5** + Retriever 抽象 | 中文 FTS5 支持 better than LIKE;未来 Qdrant(§10 扩展接口) |
| 鉴权 | **服务端 Session**(签发 opaque token 存 SQLiteCookie 传递) | 同源 SPA;比 JWT 简单且可即时吊销;SQLite 即 session store |
| 密码哈希 | **Argon2id**argon2-cffi | 需求指定 |
| 限流 | **内存 TokenBucket**(单进程够用) + RateLimiter 抽象 | 无 Redis 依赖;未来 RedisRateLimiter(§39 |
| 公共页渲染 | **Jinja2 SSR**,零 JS 零外链 | AI 兼容性第一原则 |
| Token 存储 | **SHA-256 hash**DB+ **Fernet 加密原文**(可解密,SECRET_KEY 派生) | 满足"后台显示完整链接"需求;DB 泄漏 ≠ 链接泄漏(需 + SECRET_KEY |
| 后缀路由 | 显式注册 `/k/{token}.md` / `.txt` / `.json` **先于** `/k/{token}` | FastAPI path param 默认匹配 `[^/]+``{token}` 会吞掉 `.md`(§19 |
| Python 版本 | 3.12+ 编写;本机 3.13.9 运行 | FastAPI / SQLAlchemy 2 / Pydantic v2 均已支持 |
| PyMuPDF 许可 | **MVP 阶段可接受**(AGPL-3.0);商品化前必须替换为 pypdfium2 | 需求 §24 明确指定 PyMuPDFAGPL 对**未分发**的 SaaS 后端(仅服务端运行)在多数法域下合规,但商品化闭源分发时需替换。MVP 阶段标记 TODO 并在 requirements 中注释 |
---
## 2. 数据库 ER 设计
### 2.1 实体关系
```
plans 1 ──── N users 1 ──── N knowledge_bases 1 ──── N documents 1 ──── 1 document_categories
│ │
│ └ 1 ──── N access_logs
└ 1 ──── N (配额归集)
```
### 2.2 表定义
**plans**
| 列 | 类型 | 说明 |
|---|---|---|
| id | integer PK | |
| code | text unique | `free` / `basic` / `pro` |
| name | text | 套餐名 |
| storage_quota | integer | 字节;free = 104857600 (100MB) |
| max_file_size | integer | 字节;free = 20971520 (20MB) |
| is_active | boolean | |
**users**
| 列 | 类型 | 说明 |
|---|---|---|
| id | text PK (UUID4 hex) | |
| username | text unique not null | |
| email | text unique not null | |
| password_hash | text not null | Argon2id |
| status | text | `active` / `disabled` |
| plan_id | integer FK → plans | 默认 free |
| storage_used | integer default 0 | 字节计数器 |
| created_at / updated_at | text (ISO8601) | |
**knowledge_bases**
| 列 | 类型 | 说明 |
|---|---|---|
| id | text PK (UUID4 hex) | |
| user_id | text FK → users not null | 隔离边界 |
| name | text not null | |
| description | text | |
| enabled | boolean default true | 链接开关 |
| token_hash | text unique not null | SHA-256(secret_token) hex |
| token_encrypted | text | Fernet 加密原文(可解密供后台显示) |
| token_hint | text | token 末 8 位明文,供后台识别 |
| created_at / updated_at | text | |
> SQLite 不支持 citext / timestamptz / uuid 原生类型,统一用 text + 应用层校验。
> 迁移到 PostgreSQL 时,Alembic 迁移负责类型映射,业务层无感。
**document_categories**
| 列 | 类型 |
|---|---|
| id text PK, knowledge_base_id text FK, name text not null, sort_order integer, created_at text |
**documents**
| 列 | 类型 | 说明 |
|---|---|---|
| id | text PK (UUID4 hex) | |
| knowledge_base_id | text FK | |
| user_id | text FK | 冗余存,便于隔离校验 + 配额统计 |
| category_id | text FK nullable | |
| original_filename | text | 仅展示用,不用于物理路径 |
| storage_path | text | 相对于 data/ 的物理路径,不外泄 |
| markdown_path | text | 相对于 data/ 的 Markdown 文件路径 |
| file_size | integer | 字节 |
| mime_type | text | |
| file_ext | text | `.docx` / `.pdf` |
| sha256 | text | 去重与完整性 |
| doc_token_hash | text unique | 单文档公共 URL 凭证 |
| doc_token_encrypted | text | Fernet 加密 |
| doc_token_hint | text | |
| title | text | 用户可改,默认从 Markdown H1 提取 |
| description | text | 用户可改,默认自动生成摘要 |
| keywords | text | 逗号分隔,jieba 提取 + 用户可改 |
| content_summary | text | 抽取式摘要 ~200 字 |
| status | text | PENDING / PROCESSING / READY / FAILED / DELETED |
| error_code | text | 安全错误码(如 `SCANNED_PDF_NO_TEXT_LAYER` |
| created_at / updated_at | text | |
**access_logs**
| 列 | 类型 |
|---|---|
| id integer PK AUTOINCREMENT, knowledge_base_id text FK, document_id text nullable FK, path text, accessed_at text, user_agent text, request_type text |
> SQLite FTS5 虚拟表(documents_content)单独建,与 documents 通过 id 关联。
### 2.3 索引
- `knowledge_bases(token_hash)` unique
- `documents(doc_token_hash)` unique
- `documents(knowledge_base_id, status)`
- `documents(user_id, status)`
- `users(username)` / `users(email)` unique
---
## 3. API 设计
### 3.1 管理端(/api,需登录)
```
POST /api/auth/register {username, email, password} → 201
POST /api/auth/login {username|email, password} → Set-Cookie + 200
POST /api/auth/logout 清除 Cookie
GET /api/me 用户信息 + 套餐
PATCH /api/me 改密码
GET /api/knowledge-bases 分页列表
POST /api/knowledge-bases {name, description} → 201(返回完整 AI URL,仅此一次)
GET /api/knowledge-bases/{id} 详情
PUT /api/knowledge-bases/{id} 改名/改描述
DELETE /api/knowledge-bases/{id} 软删 + 异步清理文件
POST /api/knowledge-bases/{id}/regenerate-token 旧链立即失效
POST /api/knowledge-bases/{id}/enable
POST /api/knowledge-bases/{id}/disable
GET /api/knowledge-bases/{id}/documents 文档列表
POST /api/knowledge-bases/{id}/documents/upload multipart 上传
GET /api/documents/{id} 详情 + markdown 预览
PUT /api/documents/{id} 改 title/description/keywords/category
DELETE /api/documents/{id} 软删 + 清理文件
POST /api/documents/{id}/reprocess 重新解析
GET /api/knowledge-bases/{id}/categories 分类 CRUD
GET /api/storage {storage_used, storage_quota}
```
统一错误体:`{"code": "STORAGE_QUOTA_EXCEEDED", "message": "存储空间不足", "detail": null}`
### 3.2 公共 AI 端(/k,无登录,限流)
```
GET /k/{token} HTML 入口页(Jinja2 SSR
GET /k/{token}.md Markdown 入口
GET /k/{token}.txt 纯文本入口
GET /k/{token}.json JSON 目录(白名单字段)
GET /k/{token}/search?q=&page= HTML(默认)/ .json
GET /k/{token}/doc/{doc_token} 文档 HTML 页
GET /k/{token}/doc/{doc_token}.md 原始 Markdown
GET /k/{token}/doc/{doc_token}.txt 纯文本
```
公共端硬性规则:
1. token 不存在 / enabled=false / 软删 → **一律 404**
2. `<meta name="robots" content="noindex,nofollow,noarchive">`
3. `<meta name="referrer" content="no-referrer">`
4. JSON 输出白名单:`name, description, documents[{title, file_type, description, summary, keywords, updated_at, url}]`
5. 限流:内存 TokenBucket,单 IP 30/min,单 token 60/min
---
## 4. 项目目录
```
amb_rag/
├── backend/
│ ├── app/
│ │ ├── main.py # app 工厂、路由挂载、异常处理、lifespan
│ │ ├── api/ # 管理端路由: auth.py, knowledge_bases.py, documents.py, me.py, deps.py
│ │ ├── public/ # 公共AI路由: routes.py, render.py, serializers.py
│ │ ├── core/ # config.py, security.py, errors.py, session.py, rate_limit.py, logging.py
│ │ ├── models/ # SQLAlchemy 2.0 (Mapped/mapped_column)
│ │ ├── schemas/ # Pydantic v2 request/response
│ │ ├── services/ # auth_service.py, kb_service.py, doc_service.py, kb_public_service.py, storage_service.py
│ │ ├── repositories/ # user_repo.py, kb_repo.py, doc_repo.py, access_log_repo.py
│ │ ├── processors/ # base.py(DocumentProcessor), local_processor.py, parsers/markitdown_parser.py, pdf_parser.py, docx_parser.py
│ │ ├── retrieval/ # base.py(Retriever), keyword_retriever.py
│ │ ├── storage/ # base.py(StorageService), local_storage.py
│ │ └── templates/ # kb_index.html.j2, doc_page.html.j2
│ ├── alembic/ # env.py + versions/
│ ├── tests/ # conftest.py + 各模块测试
│ ├── requirements.txt
│ ├── pyproject.toml # ruff + mypy + pytest 配置
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── api/ # axios 实例 + 各资源 client
│ │ ├── views/ # Login, Register, Dashboard, KnowledgeBases, KbDetail, Settings
│ │ ├── components/
│ │ ├── layouts/
│ │ ├── stores/ # Pinia stores
│ │ ├── router/
│ │ └── types/
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ └── Dockerfile
├── nginx/
│ └── nginx.conf
├── data/ # .gitkeep;运行时生成
│ └── .gitkeep
├── docs/
│ └── technical-review.md # 本文件
├── docker-compose.yml
├── .env.example
├── .gitignore
├── CLAUDE.md
└── README.md
```
---
## 5. 六个未来扩展接口设计
### 5.1 SQLite → PostgreSQL
| 层 | 影响 |
|---|---|
| `core/config.py` | `DATABASE_URL``sqlite:///./data/app.db` 切为 `postgresql+asyncpg://...`;引擎换 async |
| `core/db.py` | 同步引擎 → 异步引擎 + async_sessionmakerget_session 改 async generator |
| `repositories/` | 现有同步调用改 `await`SQLAlchemy 2.x 统一风格使改动最小 |
| Alembic | 新增迁移处理类型映射(text→varchar/citext/uuid, integer→bigint |
| 业务层 | **零改动**Repository 已隔离) |
**第一版必须遵守的规则**
- 不用 PRAGMA 做业务逻辑(只用 WAL 模式设置)
- 不用 SQLite 专属函数(`group_concat` → SQLAlchemy `func` 通用)
- UUID 存 text 而非 blob(跨 DB 兼容)
- 时间存 ISO8601 text 而非 SQLite datetime 函数
- 布尔存 integer 0/1SQLAlchemy Boolean 在 SQLite 自动映射)
### 5.2 本地文件 → MinIO/S3
```python
# storage/base.py
class StorageService(Protocol):
async def save(self, key: str, data: bytes | IO) -> str: ...
async def read(self, key: str) -> bytes: ...
async def delete(self, key: str) -> None: ...
async def exists(self, key: str) -> bool: ...
async def get_size(self, key: str) -> int: ...
```
- `LocalStorageService`key 即相对路径,拼 `data/` 前缀;save → 写文件,read → 读文件。
- `MinioStorageService`key 即对象 keysave → minio.put_objectread → minio.get_object。
- 业务层通过 `core/config.py` 中的 `STORAGE_BACKEND` 选择实现,注入 Service。
- **第一版**:所有 `open()` / `Path()` 操作只在 `local_storage.py` 中出现。
### 5.3 同步解析 → 异步任务
```python
# processors/base.py
class DocumentProcessor(Protocol):
async def process(self, document_id: str) -> None: ...
```
- `LocalDocumentProcessor`:同步调用 parsers,在同一进程中完成。
- `CeleryDocumentProcessor`:提交 Celery task,轮询状态。
- **第一版**:上传 endpoint 中调 `await processor.process(doc_id)`(同步包在 `run_in_executor` 里避免阻塞事件循环)。
- 文档状态机(PENDING→PROCESSING→READY/FAILED)在两个实现中完全相同。
### 5.4 关键词搜索 → RAG
```python
# retrieval/base.py
class Retriever(Protocol):
async def search(self, query: str, knowledge_base_id: str, limit: int = 10) -> list[SearchResult]: ...
```
- `KeywordRetriever`SQLite FTS5 `MATCH` 查询 + `snippet()` 高亮。
- `VectorRetriever`Embedding + Qdrant search。
- `HybridRetriever`:关键词 + 向量加权融合。
- **第一版**FTS5 在 Alembic 迁移中建虚拟表 `documents_fts`;写入时同步更新 FTS。
### 5.5 个人 → 团队/企业
- **第一版**`knowledge_bases.user_id` 是 owner;所有查询 `WHERE user_id = :me`
- **扩展**:新增 `organizations` 表 + `org_members`role);`knowledge_bases.owner_type` + `owner_id`(多态外键);Repository 查询加 `owner_scope` 参数。
- **关键**:第一版不把 `user_id` 硬编码成无法泛化的东西(如拼接进 URL 路径)。
### 5.6 网页访问 → API/MCP
- `public/` 路由调用 `KnowledgeBasePublicService` 获取数据 → 由 RendererHTML/MD/TXT/JSON)格式化输出。
- **扩展**:新增 `/api/v1/public/...` 路由,鉴权用 API Key**复用同一个 Service**。
- MCP Server 是独立进程,调 Repository 层。
- **关键**:公共数据获取逻辑只在 Service 中写一次,Renderer 各格式只做序列化。
---
## 6. 安全模型
### 6.1 Secret Token
```
生成:token = secrets.token_urlsafe(16) # 22 字符, 128bit 熵
存储:knowledge_bases.token_hash = SHA-256(token).hexdigest()
knowledge_bases.token_encrypted = Fernet(SECRET_KEY 派生).encrypt(token)
knowledge_bases.token_hint = token[-8:]
校验:请求 token → 正则校验 → SHA-256 → 单条索引查询 → enabled/软删检查 → 404 或放行
轮换:regenerate → 新 token,旧 hash 失效,旧缓存失效
```
安全属性:
- 128bit 熵 + 限流 → 暴力枚举不可行
- DB 泄漏 ≠ 链接泄漏(需 + SECRET_KEY
- 统一 404 防存在性探测
- `<meta name="referrer" content="no-referrer">` 防文档页跳出时 Referer 泄漏 token
### 6.2 IDOR 防护
所有管理端 API 在 Service 层强制校验 `resource.user_id == current_user.id`;不匹配 → 404(不暴露存在性)。
### 6.3 文件上传安全
- 扩展名白名单(`.docx`, `.pdf`
- MIME 嗅探(filetype 库,前 8KB
- 随机物理文件名(`secrets.token_urlsafe(8)_safe_original_name`
- 路径穿越清洗(取 basename,剔除 `..` / `\` / 控制字符)
- 文件大小前置校验(Nginx `client_max_body_size` + 应用层双重保险)
### 6.4 XSS 防护
- AI 公共页:Markdown → HTML 走 Jinja2 `{{ content | safe }}`,但 Markdown 本身是服务端生成(非用户直接输入 HTML),风险可控;用户手改 Markdown 时需 sanitizebleach 或 markdown 配置 `sanitize=True`)。
- 管理端:Vue SPAAPI 返回 JSONElement Plus 组件默认 escape。
### 6.5 Session 安全
- Cookie: `HttpOnly`, `SameSite=Lax`, 生产环境 `Secure`
- Session token: `secrets.token_urlsafe(32)`, 存 SQLite `sessions` 表(或内存 dict — MVP 足够)
- 过期: 7 天不活动自动清除
---
## 7. 文档处理流程
```
[HTTP 上传](同步返回)
鉴权 → KB 归属 → MIME 嗅探 → 扩展名白名单 → 大小校验
→ 配额原子扣减(UPDATE users SET storage_used = storage_used + :size WHERE id=:uid AND storage_used + :size <= storage_quota
失败 → STORAGE_QUOTA_EXCEEDED / FILE_TOO_LARGE
→ 流式 SHA256(边读边算,内存峰值 = 一个 chunk)
→ StorageService.save() 保存原始文件(随机文件名)
→ Document(PENDING) 入库
→ DocumentProcessor.process(doc_id) 同步解析:
┌ .pdf → markitdown → 失败/空 → PyMuPDF fallback → 仍空 → SCANNED_PDF_NO_TEXT_LAYER
├ .docx → markitdown → 失败 → python-docx fallback
└ 其他 → Phase 2+ 扩展
→ Markdown 清洗(压空行、规整标题层级、截超长行)
→ 提取 H1 → title(未填时)
→ 抽取前 ~200 字纯文本 → content_summary
→ jieba.analyse.extract_tags top-10 → keywords
→ StorageService.save() 保存 Markdown 文件
→ 更新 FTS 索引
→ Document(READY)
任何异常 → Document(FAILED, error_code),用户只看友好文案
→ 返回 201 + Document 信息
```
> **同步解析的注意事项**:大 PDF 解析可能耗时数秒,会阻塞当前请求。MVP 阶段可接受(单用户低并发),但必须:
> 1. 设置请求级超时(uvicorn `--timeout-keep-alive` 不够,需应用层 middleware 或 nginx `proxy_read_timeout`
> 2. 前端上传组件显示 loading + 预估时间
> 3. 未来切 Celery 时,上传立即返回 202,前端轮询状态
---
## 8. AI 访问流程
```
用户 → 在 AI 对话中粘贴 https://example.com/k/7fA92xKpQ8m... + 描述需求
AI 抓取器 → GET /k/7fA92xKpQ8m...(无 Cookie/JS
服务端:限流 → SHA256(token) → 查 KB → 未命中 404
→ 渲染入口页:
· KB 名称/描述
· 文档列表(每条:标题、类型、描述、一句话摘要、关键词、更新时间、完整文档 URL)
· 分页(≤50 条/页)
· 页脚双语机器可读说明
AI → 根据用户描述匹配文档 → GET /k/{token}/doc/{doc_token}.md(优先 .md
或 .html;需精确定位 → /k/{token}/search?q=
AI → 汇总回答用户
```
**兜底**:若 AI 不跟随链接,用户可直接粘贴文档级 URL。README 必须说明。
---
## 9. Windows 开发流程
```bash
# 后端
cd backend
python -m venv .venv
# Git Bash:
source .venv/Scripts/activate
# 或 CMD:
.venv\Scripts\activate
pip install -r requirements.txt
# 初始化数据库
alembic upgrade head
# 启动
uvicorn app.main:app --reload --port 8000
# 访问
# http://localhost:8000/api/health → 健康检查
# http://localhost:8000/api/docs → Swagger(开发期)
# 前端
cd frontend
npm install
npm run dev
# http://localhost:5173
# Vite proxy: /api → localhost:8000, /k → localhost:8000
```
**关键**`data/` 目录在首次运行时由 backend 自动创建(`os.makedirs`);`.env` 仅生产需要(开发期 config 用默认值)。
---
## 10. Docker 生产流程
```yaml
# docker-compose.ymlUbuntu 服务器)
services:
frontend: # multi-stage: node build → nginx:alpine serve dist
backend: # python:3.12-slim → uvicorn
nginx: # 反代 /api + /k → backend:8000/ → frontend:80
volumes:
- ./data:/app/data # SQLite + 用户文件,宿主机持久化
```
```bash
# 部署
git clone <repo> /opt/ai-knowledge-link
cd /opt/ai-knowledge-link
cp .env.example .env # 修改全部密钥
mkdir -p data
docker compose up -d --build
# 更新
git pull
docker compose up -d --build # 数据在 ./data 不受影响
# 备份
sqlite3 data/app.db ".backup data/backup/app_$(date +%Y%m%d).db"
tar czf data/backup/files_$(date +%Y%m%d).tar.gz data/users/
```
---
## 11. 风险清单
| # | 风险 | 等级 | 缓解 |
|---|---|---|---|
| R1 | **同步解析阻塞请求**:大 PDF 解析耗时数秒到十几秒 | 中 | MVP 可接受;前端显示 loading;设请求超时 60s;未来切 Celery |
| R2 | **SQLite 并发写锁**:多用户同时上传时 WAL 模式允许并发读但写仍串行 | 中 | `PRAGMA journal_mode=WAL`;写操作短事务;未来切 PostgreSQL |
| R3 | **PyMuPDF AGPL-3.0**:商品化闭源分发时许可风险 | 中 | MVP 阶段可接受(仅服务端运行,不分发);requirements 中注释标记;商品化前换 pypdfium2 |
| R4 | **SQLite FTS5 中文分词**FTS5 的 `unicode61` tokenizer 对中文按字分词(非词),召回率低于 jieba | 中 | 第一版仍用 FTS5(比 LIKE 好很多);关键词字段用 jieba 分词写入辅助;Phase 9 验收时若不满意可切换到 jieba 预分词 + LIKE 方案 |
| R5 | **FastAPI 后缀路由被 path param 吞掉** | 低 | 显式后缀路由注册在无后缀路由之前;token 用正则 `^[A-Za-z0-9_-]{22}$` |
| R6 | **内存限流单进程局限** | 低 | MVP 单 uvicorn worker 够用;未来多 worker/容器需 Redis 限流 |
| R7 | **Markdown XSS**:用户手改 Markdown 注入 `<script>` | 中 | Jinja2 渲染 AI 页时 sanitizebleach 或 Markdown 库 safe 模式);管理端 Vue 默认 escape |
| R8 | **配额并发超额**:SQLite 写锁串行化已天然防竞态,但应用层"先查后写"仍有窗口 | 低 | 原子 SQL `UPDATE ... WHERE storage_used + :n <= quota` |
---
## 12. 建议修改 / 补充
1. **【建议】Token 加密存储**Fernet)以满足"后台显示完整链接"。需求 §16 说"只在生成时提供",但 §33 需要"复制 AI 链接"按钮 → 后台需能取回原文。两处表述矛盾,Fernet 方案同时满足。
2. **【建议】入口页每篇文档附一句话摘要**:显著提高 AI 不点进去也能判断相关性的概率;代价是页面变大,用 50 条/页分页对冲。
3. **【建议】公共页加 `<meta name="referrer" content="no-referrer">`**:需求未提,防文档页跳出时 Referer 泄漏 token。
4. **【建议】文档 Token 也用 Fernet 加密存储**:与 KB Token 同方案,便于构建文档 URL。
5. **【建议】Session 用内存 dict + TTL 而非 SQLite 表**:MVP 更简单,重启丢失可接受;未来切 Redis 时无缝。
6. **【明确化】需求 §24 指定 PyMuPDF**:MVP 阶段接受,但必须在代码中标记 `# TODO: 商品化前替换为 pypdfium2AGPL 风险)`
7. **【明确化】配额扣减必须用原子 SQL**,不能"先查 storage_used 再写"(即使 SQLite 串行写也尽量用原子语句,养成习惯以备 PostgreSQL 切换)。
---
## 13. 开发阶段确认
按需求 §56,共 17 个 Phase。技术审查通过后从 Phase 1(项目初始化)开始,每 Phase 完成必须验证后再进入下一 Phase。