在日常项目开发中,我们经常遇到这样的需求:
用户上传 Word(
.docx
)文件,希望后台自动生成 PDF,用于下载、归档或在线预览。
网上方案很多——有收费的 Aspose、有重量级的 LibreOffice,也有轻量的 docx4j。
本文将介绍一个完全开源、部署简单、纯 Java 的实现方案:
使用 docx4j 在 Spring Boot 中实现
.docx → .pdf
转换。
一、方案选型对比
方案 | 是否开源 | 外部依赖 | 样式保真度 | 部署复杂度 | 备注 |
---|---|---|---|---|---|
Apache POI + iText | ✅ | 无 | 中 | ⭐ | 对复杂格式支持差 |
docx4j | ✅ | 无 | 高 | ⭐⭐ | 推荐,纯 Java |
LibreOffice + JODConverter | ✅ | 需安装 LibreOffice | 很高 | ⭐⭐⭐ | 部署复杂 |
Aspose.Words | ❌ | 无 | 最高 | ⭐ | 收费,商业许可 |
💡 选择理由:
- docx4j 是纯 Java 实现,无需安装 Office 或 LibreOffice;
- 开源(Apache 2.0 License),免费可商用;
- 转换质量好,能保留图片、表格、页眉页脚;
- 容易集成进 Spring Boot。
二、添加 Maven 依赖
在 pom.xml
中加入以下依赖:
<dependencies>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>11.4.8</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>11.4.8</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>11.4.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
三、核心工具类:DocxToPdfUtil
在 utils
包下创建 DocxToPdfUtil.java
:
package com.donglin.utils;
import org.docx4j.Docx4J;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFont;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import java.io.File;
import java.io.FileOutputStream;
public class DocxToPdfUtil {
/**
* 将 docx 文件转换为 PDF
*
* @param docxPath 输入文件路径
* @param pdfPath 输出文件路径
*/
public static void convert(String docxPath, String pdfPath) {
try {
// 1. 加载 Word 文档
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(docxPath));
// 2. 配置字体映射(防止中文乱码)
Mapper fontMapper = new IdentityPlusMapper();
PhysicalFonts.discoverPhysicalFonts();
PhysicalFont simsun = PhysicalFonts.get("SimSun");
if (simsun != null) {
fontMapper.put("SimSun", simsun);
// 常用中文字体映射表
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft YaHei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
// ⚙️ 修复 “宋体(正文)/宋体(标题)” 乱码
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
wordMLPackage.setFontMapper(fontMapper);
}
// 3. 创建输出流并执行转换
try (FileOutputStream os = new FileOutputStream(pdfPath)) {
Docx4J.toPDF(wordMLPackage, os);
}
System.out.println("✅ PDF 生成成功:" + pdfPath);
} catch (Exception e) {
System.err.println("❌ 转换失败:" + e.getMessage());
}
}
}
四、Controller 示例
在 controller
包中创建一个上传接口:
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@RestController
@RequestMapping("/convert")
public class FileController {
@GetMapping("/convertToPdf")
public void convertToPdf(@RequestParam String filePath, HttpServletResponse response) throws Exception {
// 1、 检查文件是否存在
File inputFile = new File(filePath);
if (!inputFile.exists()) {
throw new RuntimeException("文件不存在: " + filePath);
}
// 2、 定义输出路径(临时文件)
String pdfPath = filePath.replace(".docx", ".pdf");
// 3、 调用转换工具
DocxToPdfUtil.convert(filePath, pdfPath);
// 4、 设置响应头并输出 PDF 文件
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=" + new File(pdfPath).getName());
try (FileInputStream fis = new FileInputStream(pdfPath);
OutputStream os = response.getOutputStream()) {
fis.transferTo(os);
os.flush();
}
// 可选:删除临时 PDF 文件
new File(pdfPath).delete();
}
}
使用 Postman 发送请求:
GET http://localhost:8080/convertToPdf?filePath=filePath=E:/ai/report.docx
选择一个 .docx
文件上传,即可生成同名 .pdf
文件。
五、Windows解决中文乱码问题
增加字体的类别
fontMapper.put("SimSun", simsun);
// 常用中文字体映射表
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft YaHei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
// ⚙️ 修复 “宋体(正文)/宋体(标题)” 乱码
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
wordMLPackage.setFontMapper(fontMapper);
六、Linux解决中文乱码问题
在 Linux 环境中安装 Windows 字体
新建字体文件夹
sudo mkdir -p /usr/share/fonts/win_font
拷贝 Windows 字体文件
将 Windows 10 系统中路径为 C:\Windows\Fonts
的字体文件
拷贝到 Linux 的 /usr/share/fonts/win_font
目录中。
加载字体文件
进入字体目录并执行以下命令:
cd /usr/share/fonts/win_font
sudo mkfontscale # 生成字体缩放文件
sudo mkfontdir # 生成字体目录索引
sudo fc-cache -fv # 刷新字体缓存
查看字体安装情况
执行以下命令查看中文字体是否成功加载:
fc-list :lang=zh
七、总结
- 使用开源库 docx4j;
- 无需安装 Office 或 LibreOffice;
- 保留常见样式、图片、表格;
- 性能高、部署轻量。