摘要:本文是 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`,引擎选择逻辑里能看到三组值:
```java
return 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
附件上传与文档解析&spm=1001.2101.3001.5002&articleId=164097042&d=1&t=3&u=b6a332fe4dc64642889dd14233abc56f)
317

被折叠的 条评论
为什么被折叠?



