跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring Boot 实现 DOCX 转 PDF(基于 docx4j)

介绍如何在 Spring Boot 项目中集成 docx4j 库实现 DOCX 到 PDF 的转换。内容涵盖环境搭建、依赖配置、核心转换代码实现、中文字体乱码处理、RESTful API 接口集成以及测试验证。重点解决了服务器端字体映射问题及内存管理优化,提供了一套轻量级、开源且不依赖外部 Office 软件的解决方案。

月光旅人发布于 2026/3/29更新于 2026/7/2551 浏览
Spring Boot 实现 DOCX 转 PDF(基于 docx4j)

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 项目

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

  1. 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 及相关依赖

    在项目的 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 内部可能使用或需要其部分功能) -->
        <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 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 基础转换流程

    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 来执行转换。它需要 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 {
    
        /**
         * 将输入的 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 方法接受 InputStream 和 OutputStream,使其非常灵活,可以处理来自网络上传、文件系统或内存的数据流,并输出到文件、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) 尝试查找系统中安装的、与给定名称匹配的物理字体。
    • BestMatchingMapper 或 PhysicalFontMapper 是 FontMapper 的实现类,用于处理映射逻辑。
    • 你需要准确知道 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 权限 (PdfWriter 的 ALLOW_XXX 常量) 通常需要直接访问底层的 PDF 生成库 (如 iText)。docx4j 的抽象层可能无法方便地设置所有权限。如果对权限有严格要求,可能需要考虑其他更底层的 PDF 生成库,或者生成 PDF 后再使用其他库 (如 Apache PDFBox) 进行权限修改。使用 iText 5.x 需要注意其 AGPL 许可证对分发的要求。

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

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

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

    异常处理策略:

    1. 在 Service 方法中声明抛出:如示例所示,将 Docx4JException 和 IOException 抛给调用者 (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, OutputStream。WordprocessingMLPackage 对象在转换完成后应解除引用,以便 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. 使用 FileSystemResource 或 InputStreamResource 包装输出 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: 转换过程抛出 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 的转换。内容包括:

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

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

    目录

    1. 1. 引言
    2. 1.1 DOCX 与 PDF 格式简介
    3. 1.2 转换需求与应用场景
    4. 1.3 方案选型:为什么选择 docx4j?
    5. 2. 环境准备
    6. 2.1 基础环境要求
    7. 2.2 创建 Spring Boot 项目
    8. 2.3 添加 docx4j 及相关依赖
    9. 3. 核心转换实现
    10. 3.1 基础转换流程
    11. 3.2 加载 DOCX 文档 (WordprocessingMLPackage)
    12. 3.3 配置 PDF 转换选项 (PDFSettings)
    13. 3.4 执行转换 (Docx4J.toPDF)
    14. 3.5 完整代码示例 (Service 层)
    15. 4. 高级配置与优化
    16. 4.1 处理中文字体与乱码问题
    17. 4.2 设置 PDF 输出属性 (权限、元数据)
    18. 4.3 处理转换异常与日志记录
    19. 4.4 性能考量与内存管理
    20. 5. 集成到 Spring Boot 应用
    21. 5.1 创建 RESTful API 接口 (Controller)
    22. 5.2 文件上传与下载处理
    23. 5.3 接口测试 (使用 Postman 或 curl)
    24. 6. 测试与验证
    25. 6.1 单元测试 (JUnit)
    26. 6.2 转换结果验证
    27. 7. 常见问题与解决方案 (FAQ)
    28. 8. 总结
    • 免费图片AI生成工具免费生成了解详情
    • Magick API 一键接入全球大模型注册送1000万token查看
    • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
    • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
    • 100+免费在线小游戏爽一把
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • PyTorch 时序预测 Dataloader 构建:GRU 与 Shuffle 机制解析
    • 本地化部署 GraphRAG+LangChain+Ollama 驱动 Llama 3.1 集成 Neo4j 实战
    • AI 大模型重构软件开发全流程:从需求分析到自愈运维
    • JavaQuestPlayer 跨平台 QSP 游戏播放器与开发工具教程
    • VMware Workstation 安装 Kali Linux 虚拟机教程
    • GitHub Copilot 学生认证实战:从零开发个人博客系统
    • 基于 Verilog FPGA 的双线性插值视频缩放系统实现
    • Win10/11 系统下 WSL2 安装 Ubuntu 20.04 并迁移至 D 盘指南
    • Qdrant 向量数据库完全指南:从入门到 Spring AI/LangChain4J 集成实践
    • 电池管理系统 BMS 三级架构:BMU、BCU 与 BAU 详解
    • AI 大模型在短视频处理和剪辑中的应用
    • Go Channel 深入解析:状态、底层原理与工程实践
    • Linux 内核配置添加 WiFi 驱动和固件
    • 基于 HomeAssistant 实现跨品牌智能家居联动
    • Python生成器函数深度解析:asyncio事件循环底层实现与异步编程实战
    • OpenClaw 对接飞书机器人高频踩坑与解决方案
    • Stable Diffusion WebUI 启动报错:MessageFactory GetPrototype 修复方案
    • Windows 10 本地部署 DataX 与 DataX-Web
    • 本地大模型助手 Chatbox 安装、配置与应用指南
    • PyCharm Linux 安装与使用指南

    相关免费在线工具

    • 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