- 新增 JWT 认证模块,支持登录/注册/用户管理 - 新增文件上传接口,支持 .txt/.md/.html/.pdf/.docx 等格式解析入库 - 新增检索结果 AI 总结功能 - 新增文本去重缓存机制 - 新增全局认证夹具简化测试 - 新增配置项与环境变量支持 - 完善文档与测试覆盖
6.6 KiB
多格式文件上传入库 Spec
Why
QMDSearch 现有入库接口 POST /api/v1/documents 仅接受 JSON 文本字段,用户必须先将文件内容贴成 text 才能入库;对 .md/.html/.pdf/.docx 等常见知识载体缺少直接通道。引入文件上传端点可减少入库摩擦、保留原始文件元数据、让管理后台一站式完成「上传→解析→入库→轮询任务」全流程。
What Changes
- 新增
app/core/file_parser.py:按扩展名分发解析器(.txt/.md/.html/.htm/.pdf/.docx),统一异常包装。 - 新增
POST /api/v1/documents/upload端点:multipart/form-data 接收文件 + 可选 title/source/metadata,校验扩展名/大小→提取文本→落盘→复用现有 IngestTaskManager 提交异步入库流水线,返回 202 + task_id + saved_path。 - 配置项:
upload_dir/upload_max_size_mb/upload_allowed_extensions注入到Settings。 - 应用启动 lifespan 中
mkdir -p upload_dir(失败仅 warning 不阻塞启动)。 - 依赖:
pyproject.toml增加python-multipart/pypdf/python-docx。 - 环境变量模板
.env.example增加--- 文件上传 ---段落。 - 管理后台
admin.html在入库区块新增「文件上传」子表单:file input + 可选 title/source + 提交按钮,构造 FormData 调用 upload 端点,复用ingestPollTimer轮询 UI;提交期间禁用按钮。 CLAUDE.mdAPI 清单加入POST /api/v1/documents/upload,并在「文档入库与三级总结」节简述文件上传通道。- 单元测试:
tests/test_file_parser.py(13 例覆盖各格式与异常路径)、tests/test_document_upload_api.py(10 例覆盖合法上传/不支持扩展名/超大/空文本/损坏 PDF/落盘失败降级)。
Impact
- Affected specs: 无(首次新增能力,不修改既有 spec)
- Affected code:
- 新增:
app/core/file_parser.py、tests/test_file_parser.py、tests/test_document_upload_api.py - 修改:
app/api/v1/document.py、app/config.py、app/main.py、app/static/admin.html、pyproject.toml、.env.example、CLAUDE.md
- 新增:
ADDED Requirements
Requirement: 多格式文件文本提取
系统 SHALL 提供 parse_file(filename, content) 函数,按扩展名分发到对应解析器:.txt/.md 走 UTF-8 解码(errors=replace 兜底);.html/.htm 走标准库 html.parser 剥离 script/style 后提取可见文本;.pdf 走 pypdf 逐页 extract_text 拼接;.docx 走 python-docx 段落文本拼接(不含表格/页眉页脚)。未识别扩展名 SHALL 抛 ValueError("不支持的文件类型: {ext}");解析异常 SHALL 包装为 ValueError("文件解析失败: {detail}") 并保留原异常链。
Scenario: 支持的扩展名提取成功
- WHEN 调用
parse_file("notes.md", b"# Hello\n\nWorld") - THEN 返回字符串
"# Hello\n\nWorld"
Scenario: 不支持的扩展名
- WHEN 调用
parse_file("data.xlsx", b"binary") - THEN 抛
ValueError,message 含 "不支持的文件类型: .xlsx"
Scenario: 损坏 PDF
- WHEN 调用
parse_file("bad.pdf", b"not a real pdf") - THEN 抛
ValueError,message 含 "文件解析失败"
Requirement: 文件上传入库端点
系统 SHALL 提供 POST /api/v1/documents/upload 端点,接收 multipart/form-data(file + 可选 title/source/metadata JSON 字符串),返回 202 + {code:0, data:{task_id, status:"pending", saved_path}}。端点流程 SHALL 依次:扩展名校验(基于 settings.upload_allowed_extensions)→ 大小校验(settings.upload_max_size_mb)→ parse_file 提取文本→ 落盘到 settings.upload_dir(失败仅 warning 不阻塞)→ 合并用户 metadata(落盘元数据 raw_file_path/original_filename/original_size_bytes 优先级更高)→ 默认 title 取文件名 stem、默认 source 取 file:{原文件名} → 提交 IngestTaskManager → 返回 202。
Scenario: 合法 .md 上传
- WHEN POST /api/v1/documents/upload 携带 file=notes.md(内容 "# Hello\n\nWorld")、title="我的笔记"、source="manual"
- THEN 返回 202,data.task_id 非空、data.status=="pending"、data.saved_path 指向已存在文件;任务管理器收到 DocumentInput,text 与文件解码一致、metadata.original_filename=="notes.md"
Scenario: 不支持扩展名
- WHEN POST 上传 data.xlsx
- THEN 返回 code=1001、message 含 ".xlsx",任务管理器未收到任何提交
Scenario: 超大文件
- WHEN upload_max_size_mb=1 且上传 2MB txt
- THEN 返回 code=1001、message 含 "大小上限"
Scenario: 解析后为空文本
- WHEN 上传 blank.txt 内容仅空白
- THEN 返回 code=1001、message 含 "无法从文件提取文本"
Scenario: 落盘失败降级
- WHEN upload_dir 指向已存在的文件(mkdir 失败)
- THEN 返回 202,saved_path=="",DocumentInput.metadata 不含 raw_file_path/original_filename
Requirement: 应用启动初始化 upload_dir
应用 lifespan SHALL 在 ensure_default_admin 之后尝试 Path(settings.upload_dir).mkdir(parents=True, exist_ok=True),失败仅 warning 不阻塞启动。
Requirement: 管理后台文件上传 UI
admin.html 入库区块 SHALL 在现有「文本入库」表单旁新增「文件上传」子表单,包含:file input(accept 当前支持的扩展名)+ 可选标题输入框 + 可选来源输入框 + 提交按钮。提交时构造 FormData 调用 /api/v1/documents/upload,复用现有 ingestPollTimer 与 ingest-result 渲染逻辑展示任务进度与最终结果。提交期间 SHALL 禁用提交按钮并显示「上传中…」文案。
Scenario: 用户上传文件
- WHEN 用户选择 notes.md 文件、填标题、点提交
- THEN 按钮禁用显示「上传中…」,FormData 含 file/title,请求返回 202 后开始轮询 task 状态直到 done/failed/timeout
Requirement: 配置项与环境变量
Settings SHALL 新增三项配置:upload_dir: str = "./uploads"、upload_max_size_mb: int = 20、upload_allowed_extensions: str = ".txt,.md,.html,.htm,.pdf,.docx"。.env.example SHALL 增加 --- 文件上传 --- 段落包含对应环境变量 UPLOAD_DIR / UPLOAD_MAX_SIZE_MB / UPLOAD_ALLOWED_EXTENSIONS。
Requirement: 依赖声明
pyproject.toml dependencies SHALL 增加:python-multipart>=0.0.20(FastAPI 文件上传依赖)、pypdf>=5.1.0(PDF 解析)、python-docx>=1.1.2(DOCX 解析)。
Requirement: 文档同步
CLAUDE.md API 清单 SHALL 加入 POST /api/v1/documents/upload 行;「文档入库与三级总结」节 SHALL 简述文件上传通道(multipart 入口→parse_file 提取→复用现有 IngestTaskManager)。