跳到主要内容
Spring Boot 实现 DOCX 转 PDF(基于 docx4j) | 极客日志
Java java
Spring Boot 实现 DOCX 转 PDF(基于 docx4j) 综述由AI生成 介绍如何在 Spring Boot 项目中集成 docx4j 库实现 DOCX 到 PDF 的转换。内容涵盖环境搭建、依赖配置、核心转换代码实现、中文字体乱码处理、RESTful API 接口集成以及测试验证。重点解决了服务器端字体映射问题及内存管理优化,提供了一套轻量级、开源且不依赖外部 Office 软件的解决方案。
月光旅人 发布于 2026/3/29 更新于 2026/6/4 25 浏览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-FO 和 docx4j-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 项目
可以使用以下方式之一创建项目:
Spring Initializr : 在网页上选择 Maven Project、Java、Spring Boot 版本,添加 依赖,生成项目并下载。
Spring Web
IDE 创建向导 : IntelliJ IDEA 或 Eclipse 通常内置了 Spring Initializr 支持,可以通过向导创建。
命令行 : (可选) 使用 curl 或直接下载。项目创建后,确保基本的 Spring Boot 应用结构正常 (src/main/java, src/main/resources, pom.xml)。
2.3 添加 docx4j 及相关依赖 <dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.docx4j</groupId >
<artifactId > docx4j</artifactId >
<version > 11.4.4</version >
</dependency >
<dependency >
<groupId > org.docx4j</groupId >
<artifactId > docx4j-export-PDF</artifactId >
<version > 11.4.4</version >
</dependency >
<dependency >
<groupId > org.apache.xmlgraphics</groupId >
<artifactId > fop</artifactId >
<version > 2.7</version >
</dependency >
<dependency >
<groupId > org.apache.xmlgraphics</groupId >
<artifactId > xmlgraphics-commons</artifactId >
<version > 2.7</version >
</dependency >
</dependencies >
请务必访问 docx4j Maven Repository 和 FOP Maven Repository 查看并替换为最新的稳定版本号。
添加 fop 和 xmlgraphics-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 基础转换流程
加载 DOCX :将输入的 DOCX 文件加载到 docx4j 的内存表示 (WordprocessingMLPackage) 中。
配置 PDF 选项 :(可选) 设置 PDF 输出的各种属性。
执行转换 :调用 docx4j 的方法将 WordprocessingMLPackage 转换为 PDF 字节流或文件。
输出 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 ();
pdfSettings.setFoProcessorName("Plutext" );
pdfSettings.setObfuscateFonts(false );
return pdfSettings;
}
3.4 执行转换 (Docx4J.toPDF) docx4j 提供了便捷的方法 Docx4J.toPDF 来执行转换。它需要 WordprocessingMLPackage 和 PdfSettings,并输出到指定的 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 {
public void convertDocxToPdf (InputStream docxInputStream, OutputStream pdfOutputStream) throws Docx4JException, IOException {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxInputStream);
PdfSettings pdfSettings = createPdfSettings();
Docx4J.toPDF(wordMLPackage, pdfOutputStream, pdfSettings);
pdfOutputStream.flush();
}
private PdfSettings createPdfSettings () {
PdfSettings pdfSettings = new PdfSettings ();
pdfSettings.setFoProcessorName("Plutext" );
return pdfSettings;
}
public void convertDocxFileToPdfFile (File inputDocxFile, File outputPdfFile) throws Docx4JException, IOException {
try (FileOutputStream fos = new FileOutputStream (outputPdfFile)) {
convertDocxToPdf(new java .io.FileInputStream(inputDocxFile), fos);
}
}
}
convertDocxToPdf 方法接受 InputStream 和 OutputStream,使其非常灵活,可以处理来自网络上传、文件系统或内存的数据流,并输出到文件、HTTP 响应或内存。
使用了 try-with-resources 语句 (在 convertDocxFileToPdfFile 中) 确保 FileOutputStream 被正确关闭。在 convertDocxToPdf 方法中,流的关闭责任交给了调用者(例如,Controller 处理 HTTP 响应流)。
flush() 确保所有缓冲的数据都写入输出流。
createPdfSettings() 方法提供了配置扩展点。目前使用默认 Plutext 设置。
4. 高级配置与优化
4.1 处理中文字体与乱码问题 问题描述: 如果 DOCX 文档中使用了服务器上未安装的字体(尤其是中文字体如宋体、黑体),转换后的 PDF 可能会出现字体替换(如显示为 Times New Roman)或方块乱码。
物理安装字体 :将需要的字体文件 (TTF 或 OTF) 安装到运行 Spring Boot 应用的服务器操作系统上。docx4j 的 Plutext 转换器会尝试使用系统已安装的字体进行渲染。这是最直接有效的方法,但可能受限于服务器环境和字体许可。
使用 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 ();
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" ));
MappedFonts mappedFonts = new MappedFonts ();
mappedFonts.setMapper(fontMapper);
pdfSettings.setFontMapping(mappedFonts);
return pdfSettings;
}
PhysicalFonts.get(String fontName) 尝试查找系统中安装的、与给定名称匹配的物理字体。
BestMatchingMapper 或 PhysicalFontMapper 是 FontMapper 的实现类,用于处理映射逻辑。
你需要准确知道 DOCX 中使用的字体名称(可以通过 Word 查看或程序分析)以及服务器上可用的对应字体名称。
如果服务器没有安装所需字体,你需要将字体文件放在应用可以访问的位置(例如 src/main/resources/fonts),并使用 PhysicalFonts.addPhysicalFont(String path) 注册,然后在 FontMapper 中映射到这个注册的字体名。注意字体文件许可问题!
PhysicalFonts.addPhysicalFont("/fonts/simsun.ttf" );
PhysicalFonts.addPhysicalFont("/fonts/simhei.ttf" );
fontMapper.put("宋体" , PhysicalFonts.get("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 = pdfSettings.getPdfConversion();
pdfConversion.setTitle("Converted Document" );
pdfConversion.setAuthor("My Application" );
return pdfSettings;
}
注意: 深入设置 PDF 权限 (PdfWriter 的 ALLOW_XXX 常量) 通常需要直接访问底层的 PDF 生成库 (如 iText)。docx4j 的抽象层可能无法方便地设置所有权限。如果对权限有严格要求,可能需要考虑其他更底层的 PDF 生成库,或者生成 PDF 后再使用其他库 (如 Apache PDFBox) 进行权限修改。使用 iText 5.x 需要注意其 AGPL 许可证对分发的要求。
4.3 处理转换异常与日志记录
Docx4JException: DOCX 加载、解析或转换过程中的错误(例如文件损坏、格式不支持)。
IOException: 流读写错误。
字体缺失导致的渲染问题(可能表现为乱码,不一定抛异常)。
内存不足 (OutOfMemoryError):处理大型复杂文档时。
在 Service 方法中声明抛出 :如示例所示,将 Docx4JException 和 IOException 抛给调用者 (Controller)。
Controller 层捕获并处理 :在 Controller 中捕获异常,转换为友好的 HTTP 错误响应 (如 500 错误,附带错误信息)。
记录日志 :在 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;
}
}
}
@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) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("转换失败:" + e.getMessage());
}
}
}
4.4 性能考量与内存管理
内存消耗 :docx4j 在加载 DOCX 时会将整个文档(包括嵌入的图片等资源)解析到内存中的 WordprocessingMLPackage 对象。处理大型 或包含高分辨率图片 的 DOCX 文件时,可能导致显著的堆内存消耗,甚至 OutOfMemoryError。
性能 :转换过程(特别是复杂排版和大量图片)可能是 CPU 密集型的。
增加 JVM 堆内存 :在启动 Spring Boot 应用时,通过 -Xmx 参数设置更大的最大堆空间 (如 -Xmx1024m 或 -Xmx2048m)。
流式处理? :docx4j 主要基于内存模型,没有直接的流式处理接口来处理超大文档。对于超大文件,可能需要考虑分拆文档或使用其他方案。
图片处理 :如果 DOCX 包含大量图片,考虑在转换前或转换过程中 (如果 docx4j 支持) 对图片进行压缩或缩放。这可能需要深入操作 WordprocessingMLPackage 中的 BinaryPart。
异步处理 :对于耗时较长的转换任务,使用 Spring 的 @Async 或消息队列 (如 RabbitMQ, Kafka) 进行异步处理,避免阻塞 HTTP 请求线程。将转换任务提交到线程池,完成后通过通知 (如 WebSocket, 邮件,回调 URL) 或提供下载链接。
资源清理 :确保及时关闭不再使用的 InputStream, OutputStream。WordprocessingMLPackage 对象在转换完成后应解除引用,以便 GC 回收。
监控 :使用监控工具 (如 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) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("请上传一个 DOCX 文件" );
}
String contentType = file.getContentType();
if (contentType == null || !contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document" )) {
return ResponseEntity.badRequest().body("仅支持 DOCX 格式 (.docx)" );
}
try (ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream ()) {
docxToPdfService.convertDocxToPdf(file.getInputStream(), pdfOutputStream);
byte [] pdfBytes = pdfOutputStream.toByteArray();
ByteArrayResource resource = new ByteArrayResource (pdfBytes);
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) {
return ResponseEntity.internalServerError()
.body("转换失败:" + e.getMessage());
}
}
}
5.2 文件上传与下载处理
上传 :使用 @RequestParam("file") MultipartFile file 接收上传的文件。Spring Boot 自动处理 multipart/form-data 请求。
下载 :
将转换后的 PDF 数据写入一个 ByteArrayOutputStream。
将 ByteArrayOutputStream 的内容转换为 ByteArrayResource。
设置 HTTP 响应头:
Content-Disposition: attachment; filename=...:提示浏览器下载文件,并指定文件名。
Content-Type: application/pdf:声明响应体是 PDF 格式。
Content-Length:设置文件大小。
将 ByteArrayResource 作为响应体返回。
对于非常大的文件,将整个 PDF 先写入内存 (ByteArrayOutputStream) 可能不高效或导致 OOM。可以考虑:
将上传的 DOCX 文件先保存到服务器临时目录 (File.createTempFile())。
使用 docxToPdfService.convertDocxFileToPdfFile(inputTempFile, outputTempFile) 进行转换。
使用 FileSystemResource 或 InputStreamResource 包装输出 PDF 临时文件。
在响应发送完成后或在 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)
启动 Spring Boot 应用。
打开 Postman。
创建一个 POST 请求,URL 为 http://localhost:8080/api/convert/docx-to-pdf (端口可能不同)。
在 Body 选项卡中选择 form-data。
添加一个 key 为 file (与 @RequestParam("file") 匹配) 的类型为 File 的参数。
选择一个本地的 .docx 文件。
点击 Send。
期望:收到一个 200 OK 响应,内容类型为 application/pdf,浏览器或 PDF 阅读器会自动下载或打开转换后的 PDF 文件。
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 {
ClassPathResource sampleDocxResource = new ClassPathResource ("testfiles/sample.docx" );
byte [] docxBytes = StreamUtils.copyToByteArray(sampleDocxResource.getInputStream());
ByteArrayInputStream docxInputStream = new ByteArrayInputStream (docxBytes);
ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream ();
docxToPdfService.convertDocxToPdf(docxInputStream, pdfOutputStream);
byte [] pdfBytes = pdfOutputStream.toByteArray();
assertNotNull(pdfBytes);
assertTrue(pdfBytes.length > 0 );
String pdfHeader = new String (pdfBytes, 0 , 5 );
assertEquals("%PDF-" , pdfHeader);
File tempPdfFile = File.createTempFile("test-output" , ".pdf" );
Files.write(tempPdfFile.toPath(), pdfBytes);
System.out.println("Test PDF output: " + tempPdfFile.getAbsolutePath());
}
}
使用 @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: 转换过程抛出 NoClassDefFoundError 或 ClassNotFoundException?
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 的转换。内容包括:
方案选型 :解释了选择 docx4j 的原因。
环境搭建 :创建项目、添加依赖。
核心实现 :加载 DOCX、配置转换选项、执行转换的代码示例。
高级配置 :重点解决了中文字体问题,介绍了 PDF 属性设置、异常处理和性能优化。
Web 集成 :创建 REST API 处理文件上传和 PDF 下载。
测试验证 :单元测试和结果检查方法。
常见问题 :提供了典型问题的解决方案。
docx4j 提供了一个相对轻量级、开源且不依赖外部 Office 软件的解决方案,非常适合集成到 Java 后端服务中。虽然处理极端复杂的文档或某些特殊元素时可能存在挑战,但对于大多数常见的业务文档转换需求,它是一个强大而实用的工具。通过本指南的步骤和注意事项,你应该能够成功地在 Spring Boot 应用中实现 DOCX 转 PDF 功能。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online