Spring Boot 实现DOCX转PDF(基于docx4j的轻量级开源方案)

Spring Boot 实现DOCX转PDF(基于docx4j的轻量级开源方案)

好的,这是一份详细、实用且权威的 Java Spring Boot 实现 DOCX 转 PDF(基于 docx4j)的(基于docx4j的轻量级开源方案)全面指南。


目录

  1. 引言
    • 1.1 DOCX 与 PDF 格式简介
    • 1.2 转换需求与应用场景
    • 1.3 方案选型:为什么选择 docx4j?
  2. 环境准备
    • 2.1 基础环境要求
    • 2.2 创建 Spring Boot 项目
    • 2.3 添加 docx4j 及相关依赖
  3. 核心转换实现
    • 3.1 基础转换流程
    • 3.2 加载 DOCX 文档 (WordprocessingMLPackage)
    • 3.3 配置 PDF 转换选项 (PDFSettings)
    • 3.4 执行转换 (Docx4J.toPDF)
    • 3.5 完整代码示例 (Service 层)
  4. 高级配置与优化
    • 4.1 处理中文字体与乱码问题
    • 4.2 设置 PDF 输出属性 (权限、元数据)
    • 4.3 处理转换异常与日志记录
    • 4.4 性能考量与内存管理
  5. 集成到 Spring Boot 应用
    • 5.1 创建 RESTful API 接口 (Controller)
    • 5.2 文件上传与下载处理
    • 5.3 接口测试 (使用 Postman 或 curl)
  6. 测试与验证
    • 6.1 单元测试 (JUnit)
    • 6.2 转换结果验证
  7. 常见问题与解决方案 (FAQ)
  8. 总结

1. 引言

1.1 DOCX 与 PDF 格式简介

  • DOCX: 是 Microsoft Office Word 2007 及以后版本使用的基于 XML 的文档格式标准 (Office Open XML)。它包含了文本内容、样式、图像、表格、图表等多种元素,主要用于文档的编辑和修改。
  • PDF: 是由 Adobe Systems 开发的一种用于可靠地呈现和交换文档的文件格式。其特点是跨平台、保真度高、不易被编辑,非常适合用于文档的发布、共享和存档。

1.2 转换需求与应用场景

将 DOCX 文档转换为 PDF 的需求非常普遍,常见场景包括:

  • 文档发布与共享: 确保接收方看到的内容与原始文档一致,不受软件版本或字体差异影响。
  • 合同与协议签署: PDF 格式更利于电子签名和长期保存。
  • 报告生成系统: 后端生成 DOCX 格式的报告,转换为 PDF 后提供给用户下载。
  • 内容管理系统 (CMS): 用户上传 DOCX,系统自动转换为 PDF 存储或分发。
  • 归档与合规: 某些行业或法规要求文档必须以 PDF 格式存档。

1.3 方案选型:为什么选择 docx4j?

有多种技术可以实现 DOCX 转 PDF,例如:

  • Microsoft Office 互操作性 (COM): 依赖安装 Office,不适用于服务器环境,性能差,稳定性低。
  • Apache POI: 主要擅长读写 Office 文档,其 PDF 转换功能较弱(特别是复杂格式)。
  • 商业库 (如 Aspose.Words): 功能强大稳定,但需要付费。
  • docx4j: 一个专注于处理 Open XML 文档 (DOCX, PPTX, XLSX) 的开源 Java 库。其优势在于:
    • 纯 Java 实现: 不依赖外部软件,可在任何支持 Java 的平台上运行,包括 Linux 服务器。
    • 轻量级: 相较于一些商业库,体积和依赖相对较小。
    • 开源免费 (LGPL 许可证): 可自由使用于商业项目。
    • 功能专注: 对 DOCX 的结构和内容有深入的支持。
    • PDF 输出: 通过 docx4j-export-FOdocx4j-export-PDF 模块,利用 Apache FOP (Formatting Objects Processor) 或其他渲染器将 DOCX 内容转换为 PDF。本指南使用其内置的 Plutext PDF 转换器(基于 docx4j-export-PDF)。

因此,docx4j 提供了一个在 Spring Boot 应用中实现轻量级、开源、可移植的 DOCX 转 PDF 功能的优秀方案。


2. 环境准备

2.1 基础环境要求

  • Java Development Kit (JDK): 推荐使用 JDK 8, 11 或 17 (LTS 版本)。
  • 构建工具: Apache Maven (推荐) 或 Gradle。
  • 集成开发环境 (IDE): IntelliJ IDEA, Eclipse, VS Code 等。
  • Spring Boot: 推荐使用较新稳定版本 (如 2.7.x, 3.0.x+)。

2.2 创建 Spring Boot 项目

可以使用以下方式之一创建项目:

  1. Spring Initializr (https://start.spring.io/): 在网页上选择 Maven Project、Java、Spring Boot 版本,添加 Spring Web 依赖,生成项目并下载。
  2. IDE 创建向导: IntelliJ IDEA 或 Eclipse 通常内置了 Spring Initializr 支持,可以通过向导创建。
  3. 命令行: (可选) 使用 curl 或直接下载。

项目创建后,确保基本的 Spring Boot 应用结构正常 (src/main/java, src/main/resources, pom.xml)。

2.3 添加 docx4j 及相关依赖

在项目的 pom.xml 文件中,添加以下依赖:

<dependencies> <!-- Spring Boot Starter Web (提供 RESTful 支持) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- docx4j 核心库 --> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>11.4.4</version> <!-- 请检查并使用最新版本 --> </dependency> <!-- docx4j 导出 PDF 模块 (使用 Plutext PDF 转换器) --> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-export-PDF</artifactId> <version>11.4.4</version> <!-- 版本应与核心库一致 --> </dependency> <!-- Apache FOP (可选,但 docx4j-export-PDF 内部可能使用或需要其部分功能) --> <!-- docx4j-export-PDF 的 Plutext 转换器通常不直接依赖 FOP,但添加以防兼容性问题 --> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>fop</artifactId> <version>2.7</version> <!-- 请检查并使用最新版本 --> </dependency> <!-- FOP 需要 XML Graphics Commons --> <dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>xmlgraphics-commons</artifactId> <version>2.7</version> <!-- 请检查并使用最新版本 --> </dependency> <!-- 日志依赖 (SLF4J + Logback, 通常由 Spring Boot starter 提供) --> <!-- 确保项目中存在有效的日志实现 --> </dependencies> 

注意:

  • 请务必访问 docx4j Maven RepositoryFOP Maven Repository 查看并替换为最新的稳定版本号。
  • 添加 fopxmlgraphics-commons 是为了避免 docx4j-export-PDF 在转换时可能因缺少某些类而报错(如 org.apache.xmlgraphics.util.MimeConstants)。虽然 Plutext 转换器可能不完全依赖它们,但添加它们是常见做法。
  • Spring Boot 的 spring-boot-starter-web 通常已经包含了 spring-boot-starter-logging,它提供了 SLF4J 接口和 Logback 实现。确保日志配置正确,以便记录转换过程中的信息或错误。

运行 mvn clean install (或使用 IDE 的 Maven 工具) 下载依赖。


3. 核心转换实现

DOCX 转 PDF 的核心逻辑封装在一个 Service 类中。

3.1 基础转换流程

  1. 加载 DOCX: 将输入的 DOCX 文件加载到 docx4j 的内存表示 (WordprocessingMLPackage) 中。
  2. 配置 PDF 选项: (可选) 设置 PDF 输出的各种属性。
  3. 执行转换: 调用 docx4j 的方法将 WordprocessingMLPackage 转换为 PDF 字节流或文件。
  4. 输出 PDF: 将转换后的 PDF 字节流写入文件或 HTTP 响应。

3.2 加载 DOCX 文档 (WordprocessingMLPackage)

docx4j 使用 WordprocessingMLPackage 对象来表示一个 DOCX 文档。可以从多种来源加载:

  • File 对象: 从本地文件系统加载。
  • InputStream: 从输入流加载 (例如上传的文件流)。
  • URL: 从网络资源加载。
import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; public WordprocessingMLPackage loadDocx(File docxFile) throws Docx4JException { return WordprocessingMLPackage.load(docxFile); } public WordprocessingMLPackage loadDocx(InputStream inputStream) throws Docx4JException { return WordprocessingMLPackage.load(inputStream); } 

3.3 配置 PDF 转换选项 (PDFSettings)

PDFSettings 对象允许你定制 PDF 输出。常用设置包括:

  • setFoProcessorName(String): 设置使用的 FO 处理器。对于 docx4j-export-PDF,通常使用 "Plutext" (这是默认值)。
  • setObfuscateFonts(boolean): 是否混淆字体 (可能用于规避某些字体许可问题,慎用)。
  • setFontMapping(MappedFonts): 字体映射 (解决缺失字体问题,见 4.1 节)。
  • setAccessibility(boolean): (实验性) 是否添加 PDF 可访问性标签。
  • setRunOnly(boolean): (高级) 仅运行转换,不进行其他处理。
import org.docx4j.convert.out.pdf.PdfConversion; import org.docx4j.convert.out.pdf.PdfSettings; public PdfSettings createPdfSettings() { PdfSettings pdfSettings = new PdfSettings(); // 使用 Plutext 转换器 (通常是默认) pdfSettings.setFoProcessorName("Plutext"); // 是否混淆字体 (一般保持 false) pdfSettings.setObfuscateFonts(false); // 其他设置... return pdfSettings; } 

3.4 执行转换 (Docx4J.toPDF)

docx4j 提供了便捷的方法 Docx4J.toPDF 来执行转换。它需要 WordprocessingMLPackagePdfSettings,并输出到指定的 OutputStream

import org.docx4j.Docx4J; import org.docx4j.openpackaging.exceptions.Docx4JException; public void convertToPdf(WordprocessingMLPackage wordMLPackage, PdfSettings pdfSettings, OutputStream outputStream) throws Docx4JException { Docx4J.toPDF(wordMLPackage, outputStream, pdfSettings); } 

3.5 完整代码示例 (Service 层)

创建一个 Spring Service (DocxToPdfService) 来封装转换逻辑:

package com.example.docx2pdf.service; import org.docx4j.Docx4J; import org.docx4j.convert.out.pdf.PdfConversion; import org.docx4j.convert.out.pdf.PdfSettings; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @Service public class DocxToPdfService { /** * 将输入的 DOCX 文件流转换为 PDF,并写入输出流 * * @param docxInputStream DOCX 文件输入流 * @param pdfOutputStream PDF 输出流 * @throws Docx4JException DOCX 处理或转换错误 * @throws IOException 流操作错误 */ public void convertDocxToPdf(InputStream docxInputStream, OutputStream pdfOutputStream) throws Docx4JException, IOException { // 1. 加载 DOCX 文档 WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxInputStream); // 2. 创建 PDF 设置 (使用默认或自定义设置) PdfSettings pdfSettings = createPdfSettings(); // 调用下面的方法创建设置 // 3. 执行转换 Docx4J.toPDF(wordMLPackage, pdfOutputStream, pdfSettings); // 4. 重要:确保刷新输出流 (通常在调用方处理关闭) pdfOutputStream.flush(); } /** * (可选) 创建并配置 PDF 设置 * * @return PdfSettings 对象 */ private PdfSettings createPdfSettings() { PdfSettings pdfSettings = new PdfSettings(); // 使用 Plutext 转换器 (通常是默认,显式设置也可) pdfSettings.setFoProcessorName("Plutext"); // 设置其他选项,例如字体映射等 (见高级配置) return pdfSettings; } /** * 便捷方法:将 DOCX 文件转换为 PDF 文件 * * @param inputDocxFile 输入 DOCX 文件 * @param outputPdfFile 输出 PDF 文件 * @throws Docx4JException * @throws IOException */ public void convertDocxFileToPdfFile(File inputDocxFile, File outputPdfFile) throws Docx4JException, IOException { try (FileOutputStream fos = new FileOutputStream(outputPdfFile)) { convertDocxToPdf(new java.io.FileInputStream(inputDocxFile), fos); } } } 

关键点说明:

  • convertDocxToPdf 方法接受 InputStreamOutputStream,使其非常灵活,可以处理来自网络上传、文件系统或内存的数据流,并输出到文件、HTTP 响应或内存。
  • 使用了 try-with-resources 语句 (在 convertDocxFileToPdfFile 中) 确保 FileOutputStream 被正确关闭。在 convertDocxToPdf 方法中,流的关闭责任交给了调用者(例如,Controller 处理 HTTP 响应流)。
  • flush() 确保所有缓冲的数据都写入输出流。
  • createPdfSettings() 方法提供了配置扩展点。目前使用默认 Plutext 设置。

4. 高级配置与优化

4.1 处理中文字体与乱码问题

问题描述: 如果 DOCX 文档中使用了服务器上未安装的字体(尤其是中文字体如宋体、黑体),转换后的 PDF 可能会出现字体替换(如显示为 Times New Roman)或方块乱码。

解决方案:

  1. 物理安装字体: 将需要的字体文件 (TTF 或 OTF) 安装到运行 Spring Boot 应用的服务器操作系统上。docx4j 的 Plutext 转换器会尝试使用系统已安装的字体进行渲染。这是最直接有效的方法,但可能受限于服务器环境和字体许可。
  2. 使用 FontMapper 进行映射: docx4j 提供了 FontMapper 接口,允许你将 DOCX 中使用的字体名称映射到 PDF 中应该使用的字体名称或物理字体文件路径。这对于中文字体尤为重要!
import org.docx4j.fonts.MappedFonts; import org.docx4j.fonts.PhysicalFont; import org.docx4j.fonts.PhysicalFonts; import org.docx4j.fonts.FontMapper; private PdfSettings createPdfSettings() { PdfSettings pdfSettings = new PdfSettings(); pdfSettings.setFoProcessorName("Plutext"); // 创建字体映射器 FontMapper fontMapper = new BestMatchingMapper(); // 或者 PhysicalFontMapper // 关键:注册中文字体映射 // 假设服务器安装了 SimSun (宋体) 和 SimHei (黑体) // 将 DOCX 中的 "宋体" 映射到物理字体 "SimSun" fontMapper.put("宋体", PhysicalFonts.get("SimSun")); fontMapper.put("SimSun", PhysicalFonts.get("SimSun")); // 有时字体名是英文的 fontMapper.put("黑体", PhysicalFonts.get("SimHei")); fontMapper.put("SimHei", PhysicalFonts.get("SimHei")); // 如果需要,映射其他常用字体 fontMapper.put("Calibri", PhysicalFonts.get("Calibri")); // 假设服务器有 fontMapper.put("Arial", PhysicalFonts.get("Arial")); // 将 FontMapper 设置到 PdfSettings 的 MappedFonts 中 MappedFonts mappedFonts = new MappedFonts(); mappedFonts.setMapper(fontMapper); pdfSettings.setFontMapping(mappedFonts); return pdfSettings; } 

说明:

  • PhysicalFonts.get(String fontName) 尝试查找系统中安装的、与给定名称匹配的物理字体。
  • BestMatchingMapperPhysicalFontMapperFontMapper 的实现类,用于处理映射逻辑。
  • 你需要准确知道 DOCX 中使用的字体名称(可以通过 Word 查看或程序分析)以及服务器上可用的对应字体名称。
  • 如果服务器没有安装所需字体,你需要将字体文件放在应用可以访问的位置(例如 src/main/resources/fonts),并使用 PhysicalFonts.addPhysicalFont(String path) 注册,然后在 FontMapper 中映射到这个注册的字体名。注意字体文件许可问题!
// 示例:加载资源目录下的字体文件 (打包在 JAR 内) PhysicalFonts.addPhysicalFont("/fonts/simsun.ttf"); // 注意路径,可能需要使用 ClassLoader PhysicalFonts.addPhysicalFont("/fonts/simhei.ttf"); // 然后在 FontMapper 中映射 fontMapper.put("宋体", PhysicalFonts.get("simsun")); // 注意这里用的是注册时的名字,可能不是"SimSun" 

4.2 设置 PDF 输出属性 (权限、元数据)

PdfSettings 允许通过 org.docx4j.convert.out.pdf.PdfConversion 设置一些 PDF 属性。一种常见方式是创建 PdfConversion 实例进行配置:

private PdfSettings createPdfSettings() { PdfSettings pdfSettings = new PdfSettings(); pdfSettings.setFoProcessorName("Plutext"); // 创建 PdfConversion 实例进行更详细的设置 PdfConversion pdfConversion = pdfSettings.getPdfConversion(); // 设置 PDF 标题、作者等元数据 (可选) pdfConversion.setTitle("Converted Document"); pdfConversion.setAuthor("My Application"); // 设置 PDF 权限 (可选,需要了解 iText 的 PdfWriter 常量) // 注意:docx4j 内部使用 iText 5.x (AGPL 许可) 或 Flying Saucer 等,权限设置可能受限或复杂。 // pdfConversion.setPdfPermissions(...); // 通常需要直接操作底层 iText PdfWriter // 其他高级设置... // pdfConversion.setPdfVersion(...); // pdfConversion.setTagged(...); // 可访问性 return pdfSettings; } 

注意: 深入设置 PDF 权限 (PdfWriterALLOW_XXX 常量) 通常需要直接访问底层的 PDF 生成库 (如 iText)。docx4j 的抽象层可能无法方便地设置所有权限。如果对权限有严格要求,可能需要考虑其他更底层的 PDF 生成库,或者生成 PDF 后再使用其他库 (如 Apache PDFBox) 进行权限修改。使用 iText 5.x 需要注意其 AGPL 许可证对分发的要求。

4.3 处理转换异常与日志记录

转换过程可能遇到多种错误:

  • Docx4JException: DOCX 加载、解析或转换过程中的错误(例如文件损坏、格式不支持)。
  • IOException: 流读写错误。
  • 字体缺失导致的渲染问题(可能表现为乱码,不一定抛异常)。
  • 内存不足 (OutOfMemoryError): 处理大型复杂文档时。

异常处理策略:

  1. 在 Service 方法中声明抛出: 如示例所示,将 Docx4JExceptionIOException 抛给调用者 (Controller)。
  2. Controller 层捕获并处理: 在 Controller 中捕获异常,转换为友好的 HTTP 错误响应 (如 500 错误,附带错误信息)。
  3. 记录日志: 在 Service 或 Controller 中使用日志记录器 (Logger) 详细记录错误信息、堆栈跟踪和可能相关的文档信息(注意隐私和安全),便于排查。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Service public class DocxToPdfService { private static final Logger logger = LoggerFactory.getLogger(DocxToPdfService.class); public void convertDocxToPdf(InputStream docxInputStream, OutputStream pdfOutputStream) throws Docx4JException, IOException { try { WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxInputStream); PdfSettings pdfSettings = createPdfSettings(); Docx4J.toPDF(wordMLPackage, pdfOutputStream, pdfSettings); pdfOutputStream.flush(); } catch (Docx4JException e) { logger.error("DOCX 处理或转换失败", e); throw e; // 重新抛出,由调用方处理 } catch (IOException e) { logger.error("IO 操作失败", e); throw e; } } } 

在 Controller 中处理:

@RestController @RequestMapping("/api/convert") public class ConversionController { @Autowired private DocxToPdfService docxToPdfService; @PostMapping("/docx-to-pdf") public ResponseEntity<Resource> convertDocxToPdf(@RequestParam("file") MultipartFile file) { try { // ... 创建临时文件或直接使用流 ... ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); docxToPdfService.convertDocxToPdf(file.getInputStream(), pdfOutputStream); ByteArrayResource resource = new ByteArrayResource(pdfOutputStream.toByteArray()); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=converted.pdf") .contentType(MediaType.APPLICATION_PDF) .body(resource); } catch (Docx4JException | IOException e) { // 记录日志 (Controller 也可以有自己的 Logger) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("转换失败: " + e.getMessage()); // 注意:简单返回字符串,生产环境应更友好 } } } 

4.4 性能考量与内存管理

  • 内存消耗: docx4j 在加载 DOCX 时会将整个文档(包括嵌入的图片等资源)解析到内存中的 WordprocessingMLPackage 对象。处理大型或包含高分辨率图片的 DOCX 文件时,可能导致显著的堆内存消耗,甚至 OutOfMemoryError
  • 性能: 转换过程(特别是复杂排版和大量图片)可能是 CPU 密集型的。

优化建议:

  1. 增加 JVM 堆内存: 在启动 Spring Boot 应用时,通过 -Xmx 参数设置更大的最大堆空间 (如 -Xmx1024m-Xmx2048m)。
  2. 流式处理?: docx4j 主要基于内存模型,没有直接的流式处理接口来处理超大文档。对于超大文件,可能需要考虑分拆文档或使用其他方案。
  3. 图片处理: 如果 DOCX 包含大量图片,考虑在转换前或转换过程中 (如果 docx4j 支持) 对图片进行压缩或缩放。这可能需要深入操作 WordprocessingMLPackage 中的 BinaryPart
  4. 异步处理: 对于耗时较长的转换任务,使用 Spring 的 @Async 或消息队列 (如 RabbitMQ, Kafka) 进行异步处理,避免阻塞 HTTP 请求线程。将转换任务提交到线程池,完成后通过通知 (如 WebSocket, 邮件, 回调 URL) 或提供下载链接。
  5. 资源清理: 确保及时关闭不再使用的 InputStream, OutputStreamWordprocessingMLPackage 对象在转换完成后应解除引用,以便 GC 回收。
  6. 监控: 使用监控工具 (如 Spring Boot Actuator, Prometheus) 监控应用的内存使用情况和转换接口的性能指标 (耗时、成功率)。

5. 集成到 Spring Boot 应用

5.1 创建 RESTful API 接口 (Controller)

创建一个 Controller 来提供 DOCX 转 PDF 的 HTTP 接口。通常使用 POST 请求接收上传的 DOCX 文件。

package com.example.docx2pdf.controller; import com.example.docx2pdf.service.DocxToPdfService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.docx4j.openpackaging.exceptions.Docx4JException; import java.io.ByteArrayOutputStream; import java.io.IOException; @RestController @RequestMapping("/api/convert") public class ConversionController { private final DocxToPdfService docxToPdfService; @Autowired public ConversionController(DocxToPdfService docxToPdfService) { this.docxToPdfService = docxToPdfService; } @PostMapping("/docx-to-pdf") public ResponseEntity<Resource> convertDocxToPdf(@RequestParam("file") MultipartFile file) { // 1. 检查文件是否为空 if (file.isEmpty()) { return ResponseEntity.badRequest().body("请上传一个 DOCX 文件"); } // 2. 检查文件类型 (可选,非绝对可靠) String contentType = file.getContentType(); if (contentType == null || !contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) { return ResponseEntity.badRequest().body("仅支持 DOCX 格式 (.docx)"); } // 3. 执行转换 try (ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream()) { docxToPdfService.convertDocxToPdf(file.getInputStream(), pdfOutputStream); // 4. 准备 PDF 响应 byte[] pdfBytes = pdfOutputStream.toByteArray(); ByteArrayResource resource = new ByteArrayResource(pdfBytes); // 5. 构建响应:PDF 文件下载 String filename = file.getOriginalFilename(); if (filename != null) { filename = filename.replaceFirst("\\.docx$", "") + ".pdf"; // 替换扩展名 } else { filename = "converted.pdf"; } return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename) .contentType(MediaType.APPLICATION_PDF) .contentLength(pdfBytes.length) .body(resource); } catch (IOException | Docx4JException e) { // 6. 处理错误 return ResponseEntity.internalServerError() .body("转换失败: " + e.getMessage()); // 生产环境应返回更友好的错误对象 } } } 

5.2 文件上传与下载处理

  • 上传: 使用 @RequestParam("file") MultipartFile file 接收上传的文件。Spring Boot 自动处理 multipart/form-data 请求。
  • 下载
    1. 将转换后的 PDF 数据写入一个 ByteArrayOutputStream
    2. ByteArrayOutputStream 的内容转换为 ByteArrayResource
    3. 设置 HTTP 响应头:
      • Content-Disposition: attachment; filename=...: 提示浏览器下载文件,并指定文件名。
      • Content-Type: application/pdf: 声明响应体是 PDF 格式。
      • Content-Length: 设置文件大小。
    4. ByteArrayResource 作为响应体返回。

使用临时文件 (替代方案):

对于非常大的文件,将整个 PDF 先写入内存 (ByteArrayOutputStream) 可能不高效或导致 OOM。可以考虑:

  1. 将上传的 DOCX 文件先保存到服务器临时目录 (File.createTempFile())。
  2. 使用 docxToPdfService.convertDocxFileToPdfFile(inputTempFile, outputTempFile) 进行转换。
  3. 使用 FileSystemResourceInputStreamResource 包装输出 PDF 临时文件。
  4. 在响应发送完成后或在 finally 块中删除临时文件。
try { File inputTempFile = File.createTempFile("upload-", ".docx"); file.transferTo(inputTempFile); // 保存上传文件到临时位置 File outputTempFile = File.createTempFile("converted-", ".pdf"); docxToPdfService.convertDocxFileToPdfFile(inputTempFile, outputTempFile); Path pdfPath = outputTempFile.toPath(); InputStreamResource resource = new InputStreamResource(new FileInputStream(outputTempFile)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=converted.pdf") .contentType(MediaType.APPLICATION_PDF) .contentLength(Files.size(pdfPath)) .body(resource); } finally { // 尝试删除临时文件 if (inputTempFile != null) inputTempFile.delete(); if (outputTempFile != null) outputTempFile.delete(); } 

5.3 接口测试 (使用 Postman 或 curl)

使用 Postman:

  1. 启动 Spring Boot 应用。
  2. 打开 Postman。
  3. 创建一个 POST 请求,URL 为 http://localhost:8080/api/convert/docx-to-pdf (端口可能不同)。
  4. Body 选项卡中选择 form-data
  5. 添加一个 key 为 file (与 @RequestParam("file") 匹配) 的类型为 File 的参数。
  6. 选择一个本地的 .docx 文件。
  7. 点击 Send
  8. 期望:收到一个 200 OK 响应,内容类型为 application/pdf,浏览器或 PDF 阅读器会自动下载或打开转换后的 PDF 文件。

使用 curl:

curl -X POST -F "file=@/path/to/your/document.docx" http://localhost:8080/api/convert/docx-to-pdf --output converted.pdf 

6. 测试与验证

6.1 单元测试 (JUnit)

DocxToPdfService 编写单元测试,验证其核心转换功能。需要使用测试用的 DOCX 文件。

package com.example.docx2pdf.service; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StreamUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest public class DocxToPdfServiceTest { @Autowired private DocxToPdfService docxToPdfService; @Test public void testConvertSampleDocxToPdf() throws IOException, Docx4JException { // 1. 从测试资源目录加载一个小的 DOCX 样本文件 ClassPathResource sampleDocxResource = new ClassPathResource("testfiles/sample.docx"); byte[] docxBytes = StreamUtils.copyToByteArray(sampleDocxResource.getInputStream()); // 2. 准备输入流和输出流 ByteArrayInputStream docxInputStream = new ByteArrayInputStream(docxBytes); ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); // 3. 执行转换 docxToPdfService.convertDocxToPdf(docxInputStream, pdfOutputStream); // 4. 验证输出 byte[] pdfBytes = pdfOutputStream.toByteArray(); assertNotNull(pdfBytes); assertTrue(pdfBytes.length > 0); // 5. (可选) 简单验证 PDF 头 // PDF 文件通常以 "%PDF-" 开头 String pdfHeader = new String(pdfBytes, 0, 5); assertEquals("%PDF-", pdfHeader); // 6. (可选) 将 PDF 写入临时文件检查 File tempPdfFile = File.createTempFile("test-output", ".pdf"); Files.write(tempPdfFile.toPath(), pdfBytes); System.out.println("Test PDF output: " + tempPdfFile.getAbsolutePath()); // 手动检查 tempPdfFile 是否正确 // tempPdfFile.deleteOnExit(); // 让 JVM 退出时删除 } } 

说明:

  • 使用 @SpringBootTest 加载 Spring 上下文并注入 DocxToPdfService
  • 使用 ClassPathResource 加载位于 src/test/resources/testfiles/sample.docx 的测试 DOCX 文件。
  • 验证转换后:
    • 输出流非空。
    • 输出字节长度大于 0。
    • (基本验证) 检查字节流是否以 PDF 文件头 %PDF- 开头。
  • 可以将生成的 PDF 写入临时文件,方便手动打开验证格式是否正确。

6.2 转换结果验证

  • 内容完整性: 打开生成的 PDF,逐页核对文本、图片、表格、页眉页脚、页码等内容是否与原始 DOCX 一致。
  • 格式保真度
    • 检查字体是否正确(特别是中文字体)。
    • 检查段落缩进、间距、对齐。
    • 检查表格边框、单元格对齐。
    • 检查图片位置、大小、清晰度。
    • 检查超链接是否有效。
  • 特殊元素: 测试文档应包含 DOCX 的各种常见元素(文本框、形状、SmartArt、图表、公式等),验证它们在 PDF 中的呈现效果。注意: docx4j 对某些复杂元素(如 VBA 宏、ActiveX 控件)的支持可能有限。
  • 边缘情况: 测试空文件、超大文件、损坏文件、包含特殊字符文件等。

7. 常见问题与解决方案 (FAQ)

  • Q: 转换后中文显示为方块或乱码?
    • A: 这是最常见的问题。请参考 4.1 处理中文字体与乱码问题。确保正确使用 FontMapper 映射中文字体到服务器上已安装或注册的物理字体文件。
  • Q: 转换过程抛出 NoClassDefFoundErrorClassNotFoundException
    • A: 通常是缺少依赖。请仔细检查 pom.xml 中的依赖是否完整,特别是 docx4j, docx4j-export-PDF, fop, xmlgraphics-commons 的版本是否兼容且已下载。运行 mvn dependency:tree 检查依赖树。
  • Q: 转换大型文件时内存溢出 (OutOfMemoryError)?
    • A: 参考 4.4 性能考量与内存管理。增加 JVM 堆内存 (-Xmx),考虑异步处理,优化文档图片。
  • Q: 转换后的 PDF 格式错乱(文字重叠、布局混乱)?
    • A: 这通常是因为 DOCX 文档使用了非常复杂的布局、样式或 docx4j 不完全支持的元素。尝试简化 DOCX 文档的样式。检查 docx4j 的 issue 列表或社区论坛看是否有类似问题报告。确保使用最新版本的 docx4j。
  • Q: 如何设置 PDF 的密码保护?
    • A: docx4j 本身不直接提供简单的 PDF 加密接口。转换完成后,你需要使用专门的 PDF 库(如 Apache PDFBox, iText)对生成的 PDF 文件进行二次加密操作。
  • Q: Docx4J.toPDF 方法内部使用的是哪个 PDF 库?
    • A:PdfSettings 配置为使用 "Plutext" 时,docx4j-export-PDF 模块使用 Plutext 的 PDF 转换器。其底层在旧版本可能基于 iText 5.x (AGPL),新版本可能使用其他渲染器(如 Flying Saucer + iText 或 PDFBox)。务必注意其依赖库的许可证要求(特别是 iText AGPL 对分发的影响)。
  • Q: 是否支持 DOC (旧版 Word 格式) 转 PDF?
    • A: docx4j 主要处理 Open XML 格式 (DOCX)。对于旧版 .doc 文件,docx4j 本身不直接支持加载。你需要先将 DOC 转换为 DOCX(例如使用 Apache POI 的 HWPF 组件读取 DOC 并写入 DOCX),或者使用其他专门处理 DOC 的库。

8. 总结

本指南详细介绍了如何在 Spring Boot 应用中,使用开源的 docx4j 库实现 DOCX 文档到 PDF 的转换。内容包括:

  1. 方案选型: 解释了选择 docx4j 的原因。
  2. 环境搭建: 创建项目、添加依赖。
  3. 核心实现: 加载 DOCX、配置转换选项、执行转换的代码示例。
  4. 高级配置: 重点解决了中文字体问题,介绍了 PDF 属性设置、异常处理和性能优化。
  5. Web 集成: 创建 REST API 处理文件上传和 PDF 下载。
  6. 测试验证: 单元测试和结果检查方法。
  7. 常见问题: 提供了典型问题的解决方案。

docx4j 提供了一个相对轻量级、开源且不依赖外部 Office 软件的解决方案,非常适合集成到 Java 后端服务中。虽然处理极端复杂的文档或某些特殊元素时可能存在挑战,但对于大多数常见的业务文档转换需求,它是一个强大而实用的工具。通过本指南的步骤和注意事项,你应该能够成功地在 Spring Boot 应用中实现 DOCX 转 PDF 功能。

Read more

Python——Pandas库,超详细教程

Python——Pandas库,超详细教程

前言 1、Python的Pandas是一个基于Python构建的开源数据分析库,它提供了强大的数据结构和运算功能。 2、 * Series:一维数组,类似于Numpy中的一维array,但具有索引标签,可以保存不同类型的数据,如字符串、布尔值、数字等。 * DataFrame:二维表格型数据结构,与SQL表或Excel工作表类似,每列可以是不同的数据类型(如数值、字符串或日期),并且具有列名和行索引。DataFrame是Pandas的核心数据结构,提供了丰富的数据操作方法。 接下来我们将逐步介绍他的用法 一、导入Pandas库         简写为pd import pandas as pd 二、使用Series,创建一维数组 从0开始存储 三、index查看下标,values查看下标的值 注意:不知道标签和下标的区别请看目录五的解释 1、index的输出类似于range:         start代表起始标签;stop代表结束标签(不会到这个值,到n-1值);step代表步长。 2、valuses:         直接查看下标的值,记

By Ne0inhk
DataAgent:企业级智能数据分析师,Text-to-SQL+Python 分析 + 自动出报告一站式搞定(开源项目)

DataAgent:企业级智能数据分析师,Text-to-SQL+Python 分析 + 自动出报告一站式搞定(开源项目)

DataAgent * 开始 * 启动服务 * 启动后端服务 * 模型配置 今天发现了一个开源项目,辛辛苦苦找到的一个text2sql的开源项目,今天分享一下我使用经历。 DataAgent 是一个基于 Spring AI Alibaba Graph 打造的企业级智能数据分析 Agent。它超越了传统的 Text-to-SQL 工具,进化为一个能够执行 Python 深度分析、生成 多维度图表报告 的 AI 智能数据分析师。 系统采用高度可扩展的架构设计,全面兼容 OpenAI 接口规范的对话模型与 Embedding 模型,并支持灵活挂载任意向量数据库。无论是私有化部署还是接入主流大模型服务(如 Qwen, Deepseek),都能轻松适配,为企业提供灵活、可控的数据洞察服务。 这个是他的访问地址:DataAgent 他这里也有很多友好的参考手册 开始 环境准备 * JDK 17+ * MySQL 5.

By Ne0inhk

企业微信可信IP配置的Python完美解决方案

在企业微信开发中,配置可信IP是保障接口安全的关键步骤。但很多开发者会卡在一个前置要求上:配置可信IP需要先完成“可信域名”或“接收消息服务器URL”配置。如果手头没有备案域名,难道就只能止步于此? 最近看到一篇Java实现的无备案域名配置方案,核心思路是通过“接收消息服务器URL”验证替代可信域名,完美避开备案限制。今天就给大家带来这套方案的Python适配版本,从原理解析到代码实现,再到部署验证,一步到位帮你搞定! 一、方案核心逻辑:为什么可行? 先明确企业微信的规则:配置可信IP并非一定要备案域名,而是二选一——要么有可信域名,要么完成“接收消息服务器URL”配置。 这套方案的核心就是利用“接收消息服务器URL”的验证机制:企业微信会向你填写的URL发送验证请求,只要你的服务器能正确响应(完成签名校验和加密字符串解密),就算通过验证。通过后就能正常配置可信IP,全程无需备案域名,只需要一台有公网IP的服务器。 关键匹配点:Java版本用WXBizMsgCrypt工具类处理加密解密,Python中我们用pycryptodome库实现相同的AES加密解密逻辑,确

By Ne0inhk

WindowsCleaner v5.0:一款功能强大的Python桌面磁盘清理工具

WindowsCleaner v5.0:一款功能强大的Python桌面磁盘清理工具 作者:孤客 日期:2026年 标签:Python、Tkinter、系统优化、磁盘清理、桌面应用 🎯 项目简介 WindowsCleaner v5.0是一款基于Python Tkinter开发的Windows系统优化工具,具备专业的磁盘清理、系统优化和管理功能。该工具不仅界面美观,还支持多主题切换、多语言支持和动漫风格UI,为用户提供全方位的系统维护体验。 ✨ 核心特性 1. 🎨 现代化的用户界面 * 三套主题皮肤:日光模式、黑暗模式、冬季主题 * 动漫风格字体:使用Segoe UI Emoji字体,界面更加生动有趣 * 响应式布局:自适应窗口大小,提供更好的用户体验 2. 🔧 强大的系统清理功能 * 垃圾文件扫描:智能识别临时文件、缓存文件、日志文件 * 注册表清理:检测和清理无效的注册表项(需要管理员权限) * 启动项管理:

By Ne0inhk