# document2html **Repository Path**: mehan/document2html ## Basic Information - **Project Name**: document2html - **Description**: 常见文本类型转富文本(如 pdf/docx/xlsx等) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-31 - **Last Updated**: 2026-03-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # document2html 一个用于“文档导入并转换为 HTML”的 Spring Boot 业务示例。 它解决的不是“原文件预览”,而是更偏业务落地的问题: - 前端上传一个文档 - 或者业务数据里已经有一个文件 URL - 后端自动识别文件类型 - 将文件内容转换成一段 HTML - 这段 HTML 可以直接回填到富文本编辑器、正文编辑区、可编辑内容字段 如果你接手的是“正文可手工编辑,但附件里又有原始文档”的场景,这套逻辑基本就是一个可落地的最小实现。 ## 1. 这套代码适合什么场景 常见业务场景: - 用户上传 `word/pdf/excel/txt`,系统自动提取正文 - 数据库存的不是正文,而是附件 URL,需要回填成正文内容 - 发布、通知、报告、简报这类业务,需要把附件内容转成可编辑 HTML - 后端希望屏蔽不同文档格式差异,对上层统一只暴露 `String html` 不适合的场景: - 要求像 Office/WPS 一样高精度还原排版 - 要求完整保留图片、页眉页脚、脚注、批注、分页 - 要求把 PDF/Word 100% 无损转成前端富文本结构 这套实现追求的是: - 统一入口 - 结构清晰 - 易扩展 - 能满足大多数“正文回填”业务 ## 2. 最终效果是什么 系统最终返回的是一段 HTML 字符串,而不是复杂对象。 例如: - `txt/doc/docx/pdf` 这类偏正文的文件,返回可编辑的富文本 HTML - `xls/xlsx` 这类偏表格的文件,返回拼接后的 `` HTML 上层业务只需要关心一件事: ```java String html = documentImportService.importFile(...); ``` 至于底层解析的是 `txt`、`docx`、`pdf` 还是 `excel`,上层不需要知道。 ## 3. 支持的文件类型 当前支持: - `txt` - `doc` - `docx` - `pdf` - `xls` - `xlsx` ## 4. 核心调用链 这套代码有两条主要业务入口。 ### 4.1 入口一:前端直接上传文件 适合场景: - 页面上直接选文件上传 - 调试文件导入能力 - 独立测试某种文件格式解析效果 流程图: ```mermaid flowchart TD A[前端上传文件] --> B[DocumentImportController/import-content] B --> C[DocumentImportService#importFile MultipartFile] C --> D[根据文件名识别 FileTypeEnum] D --> E[选择匹配的 FileContentConverter] E --> F[解析文件内容] F --> G[返回 HTML 字符串] ``` ### 4.2 入口二:业务通过 content + contentFileUrl 回填正文 适合场景: - 数据表里正文字段可能为空 - 但附件 URL 已经存在 - 创建、编辑、提交时需要自动补正文 流程图: ```mermaid flowchart TD A[业务传入 content 和 contentFileUrl] --> B{content 是否有值} B -- 是 --> C[直接返回原 content] B -- 否 --> D{contentFileUrl 是否有值} D -- 否 --> E[返回空内容] D -- 是 --> F[RemoteFileDownloadUtils 下载文件] F --> G[拿到 fileName contentType bytes] G --> H[DocumentImportService#importFile fileName bytes contentType] H --> I[选择 converter] I --> J[解析并返回 HTML] ``` 这条链路的核心意义是: - 业务层不需要自己解析文件 - 业务层不需要伪造 `MultipartFile` - 业务层只负责决定“何时需要回填正文” ## 5. 为什么要这样分层 不同文件类型的解析方式完全不同: - `txt` 本质上是纯文本分段 - `docx` 需要处理段落、run、表格 - `doc` 是老版 Word 二进制格式 - `pdf` 更偏文本抽取 - `xls/xlsx` 需要按 sheet/row/cell 读取 如果所有逻辑都堆在一个 service 或 controller 里,通常会出现这些问题: - `if else` 越来越长 - 一种文件格式改动容易影响其他格式 - 不容易新增新格式 - 不容易定位 bug 所以这里采用了“统一入口 + 多 converter”的方式,也就是典型的策略模式。 ## 6. 项目结构说明 ```text src/main/java/org/example/docimport ├─ controller HTTP 接口和统一异常处理 ├─ service 统一导入入口 ├─ converter 各文件类型的具体解析器 ├─ model 上下文、结果、枚举、请求对象 ├─ util HTML 构造、远程文件下载等工具 └─ common 通用返回结构 ``` 关键类说明: - `DocumentImportController` 暴露 HTTP 接口,接收上传文件或回填请求 - `DocumentImportService` 统一定义导入能力入口 - `DocumentImportServiceImpl` 负责识别文件类型、选择 converter、收口最终 HTML - `FileContentConverter` 所有文件解析器的统一接口 - `TxtFileContentConverter` 解析纯文本,按空行分段为 `

` - `DocFileContentConverter` 解析 `.doc`,重点做文本提取 - `DocxFileContentConverter` 解析 `.docx`,支持段落、基础 run 样式、表格 - `PdfFileContentConverter` 解析 PDF 文本,不追求复杂版式还原 - `ExcelFileContentConverter` 读取 sheet/row/cell,输出 `

` - `RemoteFileDownloadUtils` 根据 URL 下载远程文件,为“正文回填”链路服务 ## 7. 各层职责边界 ### controller 层 只负责: - 接收请求 - 参数绑定 - 调 service - 返回统一结果 不负责: - 判断文件类型 - 解析文档格式 - 拼接 HTML 细节 ### service 层 只负责: - 统一导入入口 - 识别文件类型 - 选择匹配的 converter - 收口返回值 - 处理 `content` 为空时按 URL 回填正文 不负责: - 每种文件的具体解析细节 ### converter 层 只负责: - 某一种或某一类文件格式的解析 例如: - `TxtFileContentConverter` 只关心 `txt` - `ExcelFileContentConverter` 只关心 `xls/xlsx` 这样新增文件格式时,只需要新增 converter,而不必大改已有逻辑。 ## 8. 目前每种格式大概怎么处理 ### txt 处理方式: - 按 UTF-8 读取文本 - 按空行分段 - 每段包成 `

` ### doc 处理方式: - 用 Apache POI 的 `HWPFDocument` 提取段落文本 - 不追求复杂样式保留 ### docx 处理方式: - 遍历文档 body element - 段落转成 `

` - run 的加粗、斜体、下划线做基础保留 - 表格转成 HTML `

` ### pdf 处理方式: - 使用 PDFBox 抽取文本 - 按空行切段 - 转成 `

` 限制: - 不保证复杂布局、图文混排、表格精准恢复 ### xls/xlsx 处理方式: - 逐个 sheet 遍历 - 按 row/cell 读取显示值 - 转成 `

` 限制: - 目前重点是内容,不是样式 - 合并单元格、复杂格式、颜色、边框没有精细还原 ## 9. 一次完整请求在代码里怎么走 下面以上传文件为例: 1. [DocumentImportController.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/controller/DocumentImportController.java) 接收 `MultipartFile` 2. 调用 [DocumentImportService.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/service/DocumentImportService.java) 的 `importFile` 3. [DocumentImportServiceImpl.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/service/impl/DocumentImportServiceImpl.java) 根据文件名识别 [FileTypeEnum.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/model/FileTypeEnum.java) 4. `Service` 在已注入的 `FileContentConverter` 列表里找出支持当前类型的实现 5. 选中的 converter 开始解析文件 6. converter 返回 [ImportResult.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/model/ImportResult.java) 7. `Service` 将结果收口成最终 HTML 字符串 8. controller 返回给前端 ## 10. 为什么 service 要保留两个 importFile 方法 ### `importFile(MultipartFile file)` 适合: - Controller 直接接收上传文件 - 最常规的接口调用方式 ### `importFile(String fileName, byte[] content, String contentType)` 适合: - 文件已经从 URL 下载到内存 - 文件来自对象存储、数据库、消息、第三方系统 - 内部业务代码不方便也不应该构造 `MultipartFile` 这个设计非常重要,因为它把“上传入口”和“文件解析能力”解耦了。 ## 11. 对上层业务最有价值的地方 如果你的真实业务是“创建发布/编辑发布/提交发布”这一类流程,那么通常逻辑会变成: 1. 先看 `content` 有没有值 2. 有值就直接用 3. 没值再看 `contentFileUrl` 4. 有 URL 就下载文件并转成 HTML 5. 把 HTML 回填到正文字段 6. 最后再落库 也就是说,这套代码真正抽象出来的是: - 业务系统不直接操作复杂文档格式 - 业务系统只消费统一的 HTML 字符串 ## 12. 快速启动 环境要求: - JDK 17 - Maven 3.9+(或任意可用 Maven 版本) 启动: ```bash mvn spring-boot:run ``` 默认端口: ```text 8088 ``` 配置文件: - [application.yml](C:/Users/lhm/Desktop/document-import-demo/src/main/resources/application.yml) ## 13. 接口说明 ### 13.1 上传文件转 HTML 请求: ```http POST /api/import-content Content-Type: multipart/form-data ``` 表单参数: - `file`: 上传的文档文件 返回: ```json { "code": 0, "msg": "success", "data": "

转换后的 HTML 内容

" } ``` ### 13.2 按 content + contentFileUrl 解析正文 请求: ```http POST /api/resolve-content Content-Type: application/json ``` 请求体: ```json { "content": "", "contentFileUrl": "https://example.com/demo.docx" } ``` 逻辑规则: - 如果 `content` 非空,直接返回 `content` - 如果 `content` 为空且 `contentFileUrl` 有值,则下载文件并转 HTML - 如果两者都空,则返回空内容 ## 14. 扩展新文件格式时怎么做 假设你以后要支持 `md`、`pptx` 或其他格式,通常只需要做这几步: 1. 在 [FileTypeEnum.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/model/FileTypeEnum.java) 增加新类型 2. 新增一个 `xxxFileContentConverter` 3. 实现 `supports` 和 `convert` 4. 给类加上 `@Component` 这样 Spring 会自动注入,`DocumentImportServiceImpl` 不需要大改。 这就是当前结构最大的价值: - 扩展点清晰 - 旧逻辑稳定 - 新逻辑容易接入 ## 15. 当前实现的边界和限制 为了让业务能尽快落地,这套实现是“优先可用”,不是“优先完美还原”。 当前边界包括: - `docx` 只保留基础文字样式,不保留全部 Office 格式能力 - `pdf` 更偏文本提取,不保证版面和表格完整恢复 - `excel` 重点保留内容,不做复杂样式还原 - 远程 URL 下载依赖目标地址可访问 - 文件类型主要按扩展名识别,不是按文件魔数做强校验 如果你的后续要求提高,可以继续往这几个方向增强: - 增加文件头校验 - 增加图片抽取 - 增加合并单元格处理 - 增加更丰富的 `docx` 样式映射 - 增加单元测试和样例文件回归测试 ## 16. 建议接手者优先阅读哪些类 如果你是第一次看这套代码,建议按下面顺序读: 1. [DocumentImportController.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/controller/DocumentImportController.java) 2. [DocumentImportServiceImpl.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/service/impl/DocumentImportServiceImpl.java) 3. [FileContentConverter.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/converter/FileContentConverter.java) 4. [DocxFileContentConverter.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/converter/DocxFileContentConverter.java) 5. [ExcelFileContentConverter.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/converter/ExcelFileContentConverter.java) 6. [RemoteFileDownloadUtils.java](C:/Users/lhm/Desktop/document-import-demo/src/main/java/org/example/docimport/util/RemoteFileDownloadUtils.java) 读完这几处,整条业务链基本就清楚了。 ## 17. 一句话总结 这套代码的核心不是“会解析几种文档”,而是: - 把不同文件格式统一收口成业务可消费的 HTML 字符串 只要抓住这一点,后面无论你要接发布模块、通知模块、报告模块,还是继续扩展更多文件格式,思路都不会乱。 ## 18. 迭代说明 从当前版本开始,每次修改代码后,都会在这里追加一段简短的迭代说明,至少记录: - 本次范围 - 涉及文件 - 行为变化 - 验证结果 ### 2026-03-31 Iteration 1 - Scope: 建立 converter 层的基础回归测试基线,覆盖 `txt/doc/docx/pdf/xls/xlsx` 六类格式 - Code changes: 新增 `TxtFileContentConverterTest` 新增 `DocFileContentConverterTest` 新增 `DocxFileContentConverterTest` 新增 `ExcelFileContentConverterTest` 新增 `PdfFileContentConverterTest` 新增测试资源 `src/test/resources/samples/simple.doc` - Behavior locked in: `txt` 段落拆分与 HTML 转义 `doc` 基础段落抽取 `docx` 基础 run 样式和表格输出 `excel` sheet 标题、普通单元格和公式结果输出 `pdf` 当前文本抽取结果以 `
` 形式保留同段换行 - Verification: `mvn test` 结果:`Tests run: 25, Failures: 0, Errors: 0, Skipped: 0` ### 2026-03-31 Iteration 2 - Scope: 提升复杂样例场景下的保真度,先覆盖 `docx` 标题段落和 `excel` 合并单元格 - Code changes: 更新 `src/main/java/org/mohan/docimport/converter/DocxFileContentConverter.java` 更新 `src/main/java/org/mohan/docimport/converter/ExcelFileContentConverter.java` 更新 `src/test/java/org/mohan/docimport/converter/DocxFileContentConverterTest.java` 更新 `src/test/java/org/mohan/docimport/converter/ExcelFileContentConverterTest.java` - Behavior changes: `docx` 识别 `Heading1-6` 样式并映射为 `h1-h6` `excel` 输出 merged region 的 `colspan/rowspan` - Verification: `mvn test` 结果:`Tests run: 27, Failures: 0, Errors: 0, Skipped: 0`