Files
kplam dce9e31bde feat: 新增多格式文件上传入库与认证体系
- 新增 JWT 认证模块,支持登录/注册/用户管理
- 新增文件上传接口,支持 .txt/.md/.html/.pdf/.docx 等格式解析入库
- 新增检索结果 AI 总结功能
- 新增文本去重缓存机制
- 新增全局认证夹具简化测试
- 新增配置项与环境变量支持
- 完善文档与测试覆盖
2026-07-30 10:30:15 +08:00

6.6 KiB
Raw Permalink Blame History

多格式文件上传入库 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.md API 清单加入 POST /api/v1/documents/upload,并在「文档入库与三级总结」节简述文件上传通道。
  • 单元测试:tests/test_file_parser.py13 例覆盖各格式与异常路径)、tests/test_document_upload_api.py(10 例覆盖合法上传/不支持扩展名/超大/空文本/损坏 PDF/落盘失败降级)。

Impact

  • Affected specs: 无(首次新增能力,不修改既有 spec)
  • Affected code:
    • 新增:app/core/file_parser.pytests/test_file_parser.pytests/test_document_upload_api.py
    • 修改:app/api/v1/document.pyapp/config.pyapp/main.pyapp/static/admin.htmlpyproject.toml.env.exampleCLAUDE.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")
  • THENValueErrormessage 含 "不支持的文件类型: .xlsx"

Scenario: 损坏 PDF

  • WHEN 调用 parse_file("bad.pdf", b"not a real pdf")
  • THENValueErrormessage 含 "文件解析失败"

Requirement: 文件上传入库端点

系统 SHALL 提供 POST /api/v1/documents/upload 端点,接收 multipart/form-datafile + 可选 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 返回 202data.task_id 非空、data.status=="pending"、data.saved_path 指向已存在文件;任务管理器收到 DocumentInputtext 与文件解码一致、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 返回 202saved_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 = 20upload_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.20FastAPI 文件上传依赖)、pypdf>=5.1.0PDF 解析)、python-docx>=1.1.2DOCX 解析)。

Requirement: 文档同步

CLAUDE.md API 清单 SHALL 加入 POST /api/v1/documents/upload 行;「文档入库与三级总结」节 SHALL 简述文件上传通道(multipart 入口→parse_file 提取→复用现有 IngestTaskManager)。