VeapAI 实战(四)附件上传与文档解析
摘要本文是 VeapAI 实战系列的第四篇聚焦企业 AI 知识库全链路中的附件上传与文档解析环节。文章从解析质量决定问答质量这一核心观点出发详细拆解了资产、片段、书签三张核心表的设计思路说明如何通过统一 PDF 预览解决多格式文档的页码口径问题随后梳理了页面操作流程与源码结构重点讲解解析引擎如何通过模式层、扩展名层和 OCR 引擎层实现插件化扩展并分享了预览产物缺失这一踩坑经验与复现方法。# 从零打通企业 AI 知识库全链路VeapAI 实战四附件上传与文档解析 关键词文档解析、知识库 PDF 预览、paddleocr、段落切分 | 首发CSDN | 同步知乎 / 掘金![本系列 8 步流程总览当前④ 附件上传与文档解析]## 解析这步决定后面所有环节的天花板文档解析的质量直接决定知识点的质量进而决定问答的质量。这步做得糙后面调什么都白搭。VeapAI 对解析定的目标不是把文字抠出来而是每段文字都能定位到统一预览里的那一页。目标不一样表设计就不一样。## 三张表资产、片段、书签ai_doc_asset 是文档级唯一事实来源关键字段| 字段 | 说明 || ---- | ---- || file_data_id | 源附件主键对接附件中心uk_doc_asset_file(tenant_id, file_data_id) 保证一附件一资产 || preview_file_data_id | 统一预览附件**要求为可按页定位的 PDF 预览产物** || parse_engine | 解析引擎paddleocr / office-pdf-text / pdf-text || parse_status | 0 待处理 / 1 处理中 / 2 成功 / 3 失败配 parse_fail_reason || content_hash | 文件内容 hash用于重算触发与幂等 || page_count | 统一预览页数 |统一 PDF 预览是这套设计的底座。不管原始文件是 docx、xlsx、pptx 还是扫描件页码只有一个口径预览 PDF 的页码。后面 page_no、locator_json 全部基于它不会出现word 第 3 页 vs pdf 第 5 页这种糊涂账。ai_doc_content 是页级 / 块级片段表关键字段| 字段 | 说明 || ---- | ---- || segment_id | 片段稳定 ID同一 asset 内唯一。**大模型输出的 sourceRefs 只允许引用它** || page_no / page_to | 页码跨页片段可带结束页 || anchor_type / anchor_text | 锚点类型exact_quote / heading / ocr_box / table_cell / pdf_text与短锚文本 || quote_text | 引用原文摘录用于命中回显与人工核对 || char_start / char_end | 全文字符偏移0-based || locator_json | 精确定位信息打开预览时的唯一事实来源 |uk_doc_content_segment(tenant_id, asset_id, segment_id) 保证片段 ID 稳定唯一。第 5 篇的知识点溯源、第 8 篇的在线定位全靠这张表。anchor_type 是稳定枚举exact_quote / heading / ocr_box / table_cell / pdf_text五种锚点对应五种定位方式——原文摘录、标题、OCR 框、表格单元格、PDF 文本。锚点类型跟着解析方式走以后加新解析引擎扩枚举就行下游按锚点类型渲染定位框的逻辑不用动。第三张 ai_doc_locator 是派生层存书签和人工补充定位注释写得很克制**不替代 ai_doc_content 的精确溯源**。![核心表关系 ER 图简化版只标关键属性与核心联动]## 页面操作![资料附件管理]「AI 知识库 → 资料附件管理」前端 veap-ui/src/views/ai/aiFileDatas/上传资料。ai_file_datas 表按附件维度挂配置agent 编码、主题元数据标准、内容元数据标准、提示词、模型、知识主题 ID这些是后面第 5 篇AI 资料处理的输入。上传后解析任务按文件类型选引擎电子文档走文本引擎扫描件走 OCR。解析完成资产记录 page_count、parse_engine片段逐页落库页面上能翻到每页的块列表。## 源码走读控制器在 veap-file 模块com.veap.file.controller.AiDocAssetController、AiDocContentController。解析服务在 com.veap.file.service.FileDataContentParseService引擎选择逻辑里能看到三组值javareturn persistResult(sourceData, f, ext, ocr, paddleocr, previewMaterial, pageTexts);// ...return persistResult(sourceData, f, ext, text, office-pdf-text, previewMaterial, pageTexts);// ...return persistResult(sourceData, f, ext, text, pdf-text, previewMaterial, pageTexts);OCR 走 com.veap.file.service.ocr.PaddleOcrHttpClient实现 IFileOcrClient外部 OCR 服务通过 HTTP 调用。## 引擎这层是怎么插件化的解析引擎的选择不是一坨 if-else 堆在入口而是两层分发加一个接口1. **模式层**ParseMode 分 OCR 和 TEXT。显式传 OCR 模式时非图片类扩展名直接报错报错信息里带支持清单——仅支持 png/jpg/jpeg/pdf。不支持的格式页面上看到的是人话不是空结果。2. **扩展名层**按 ext 分发到 parseByOcr / parseByOfficeText / parseByPdfText / parseByPlainText 四个私有方法。支持的清单在兜底报错里写得明明白白doc/docx/wps/xls/xlsx/pdf/png/jpg/jpeg/txt/md/csv/log/json/xml/html/htm。3. **OCR 引擎层**IFileOcrClient 接口 PaddleOcrHttpClient 实现。要换 OCR 供应商比如本地部署的其它 OCR 服务实现这个接口、换个注入即可解析主流程一行不动。这就是底层架构插件化最朴素的样子。还有一处不易察觉的人性化设计解析前先查缓存。loadExistingParsedResult 会先找该附件已有的解析资产有就直接返回不重复解析。同一份资料反复上传、重试任务不会重复烧 OCR 调用也天然幂等。## 一处踩过的坑preview_file_data_id 允许为空但产品语义上统一预览是硬要求。早期有个分支只写了源附件没写预览产物结果知识点溯源有页码、打开预览却是空白。现在表上留了 idx_doc_asset_preview_file(tenant_id, preview_file_data_id)排查哪些资产缺预览一条 SQL 就能查。字段允许空不意味着业务允许空这类软约束要留索引兜底。## 复现准备一份带目录结构的政策 PDF 和一份扫描件 PDF分别上传解析。对比两种引擎的产物电子件 anchor_type 以 pdf_text 为主扫描件以 ocr_box 为主确认每页 segment_id 稳定、page_no 与预览页一致。下一篇用解析产物做知识内容初始化知识点怎么生成以及它和文档片段之间的来源映射是怎么建的。源码https://gitee.com/mindock/veap