背景
最近在测试在线小工具(IEasyTool)站点时,发现 Word 转 PDF 功能突然用不了了,点击转换后直接报错:
转换失败: class com.documents4j.conversion.msoffice.MicrosoftWordBridge could not be created by a (File, long, TimeUnit) constructor
本文记录这次故障的排查过程和解决方案。
一、排查过程
1.1 定位错误来源
错误堆栈指向 documents4j 库。后台转换代码位于 FileConvertServiceImpl.java,核心实现如下:
java
IConverter converter = LocalConverter.builder().build();
converter.convert(is).as(DocumentType.DOCX).to(os).as(DocumentType.PDF).execute();
1.2 翻历史日志
在 src/logs/ 目录下找到 6 月 21 号的日志,发现当时转换成功过:
MicrosoftWordBridge : From-Microsoft-Word-Converter was started successfully
LocalConverter : The documents4j local converter has started successfully
但注意看路径------全是 C:\Users\jaime.yu\AppData\Local\Temp\...,这是 Windows 开发机的路径。
1.3 找到根因
对比环境后发现:
| 环境 | OS | 有无 MS Office | 结果 |
|---|---|---|---|
| 开发机 | Windows 11 | 有(Microsoft 365) | 成功 |
| 生产服务器 | Linux (CentOS) | 无 | 失败 |
documents4j 的工作原理 是启动本机的 Microsoft Word 进程(通过 COM/Automation),让 Word 打开 .docx 文件,再另存为 PDF。所以它必须在安装了 Microsoft Office 的 Windows 机器上运行。
生产环境是 Linux 服务器,根本没有 WINWORD.EXE,MicrosoftWordBridge 自然创建失败。
该功能从一开始在生产环境就没正常过------6 月 21 日的成功日志全是本地开发机的。
二、解决方案
方案选型
| 方案 | 优点 | 缺点 |
|---|---|---|
| 装 Windows Server + Office | 保真度最高 | 授权费贵、部署重 |
| Aspose.Words(商业库) | 纯 Java、保真度高 | 商业授权,按年付费 |
| LibreOffice headless | 免费、跨平台、保真度好 | 需要额外安装 |
| Apache POI + PDFBox | 纯 Java、无额外依赖 | 复杂排版丢失严重 |
最终选择 LibreOffice headless------免费、跨平台、Word 转 PDF 效果接近原生,是 Linux 上做 Office 转换的事实标准。
实现
改造后的 wordToPdf() 方法:
java
@Override
public File wordToPdf(MultipartFile file) throws Exception {
Path inputFile = tempDir.resolve("in_" + UUID.randomUUID() + ".docx");
Path outputFile = tempDir.resolve("out_" + UUID.randomUUID() + ".pdf");
try {
file.transferTo(inputFile);
String loPath = findLibreOffice();
ProcessBuilder pb = new ProcessBuilder(
loPath, "--headless", "--convert-to", "pdf",
"--outdir", tempDir.toString(), inputFile.toString());
pb.redirectErrorStream(true);
Process p = pb.start();
boolean finished = p.waitFor(120, TimeUnit.SECONDS);
if (!finished) {
p.destroyForcibly();
throw new RuntimeException("LibreOffice conversion timed out after 120s");
}
// ... 读取输出、校验退出码、定位生成的 PDF ...
Path generatedPdf = tempDir.resolve(inputFile.getFileName().toString()
.replaceFirst("\\.docx$", ".pdf"));
Files.move(generatedPdf, outputFile, StandardCopyOption.REPLACE_EXISTING);
return outputFile.toFile();
} finally {
Files.deleteIfExists(inputFile);
}
}
自动探测 LibreOffice 位置,兼容 Windows / Linux:
java
private String findLibreOffice() {
// Windows
String[] winPaths = {
"C:\\Program Files\\LibreOffice\\program\\soffice.exe",
"C:\\Program Files (x86)\\LibreOffice\\program\\soffice.exe"};
for (String p : winPaths) {
if (new File(p).exists()) return p;
}
// Linux / macOS
String[] nixNames = {"libreoffice", "soffice"};
for (String name : nixNames) {
try {
ProcessBuilder pb = new ProcessBuilder(name, "--version");
Process p = pb.start();
if (p.waitFor(5, TimeUnit.SECONDS) && p.exitValue() == 0) {
return name;
}
} catch (Exception ignored) {}
}
throw new RuntimeException("LibreOffice not found.");
}
生产环境安装 LibreOffice
bash
# CentOS / RHEL
sudo yum install -y libreoffice-headless
# Ubuntu / Debian
sudo apt-get install -y libreoffice-headless
# 手动安装(最可靠)
# 下载安装包
cd /tmp && \
wget https://downloadarchive.documentfoundation.org/libreoffice/old/7.5.9.2/rpm/x86_64/LibreOffice_7.5.9.2_Linux_x86-64_rpm.tar.gz && \
# 解压并安装
tar -xzf LibreOffice_7.5.9.2_Linux_x86-64_rpm.tar.gz && \
cd LibreOffice_7.5.9.2_Linux_x86-64_rpm/RPMS && \
dnf localinstall -y *.rpm && \
# 创建软链接
ln -sf /opt/libreoffice7.5/program/soffice /usr/local/bin/libreoffice && \
# 用简单文本测试转换功能
echo "test content" > /tmp/test.txt
libreoffice --headless --convert-to pdf /tmp/test.txt --outdir /tmp
ls -l /tmp/test.pdf
三、顺手修了另一个坑
同一天还修了 IP 归属地查询功能失效的问题,根因类似:
前端 LocationQuery.vue 直接用 fetch 调两个第三方 API:
- 主 API
https://ipapi.co/→ CORS 限制,浏览器拦截 - 备 API
http://ip-api.com/→ HTTP 明文,但生产环境强制 HTTPS,触发浏览器 Mixed Content 策略
两个都挂了,错误被 catch 静默吞掉,用户点查询完全没反应。
修法 :在后端加一个 /api/pub/ip/query 代理接口,由服务端转发请求。前端不再直接调第三方 API。
四、总结
-
开发环境 vs 生产环境 :像
documents4j这种绑定特定 OS/软件的库,在开发机正常不代表生产环境正常。部署前一定要确认生产环境的 OS 和依赖。 -
第三方 API 不要从前端直调:浏览器有 CORS、Mixed Content 等安全策略,不确定目标 API 是否支持跨域访问时,统一走后端代理更稳妥。
-
异常不要静默吞掉 :
catch(e){}让排查问题变得非常困难。至少打个日志,或者在前端给用户一个明确的错误提示。 -
LibreOffice headless 是 Linux 上做 Office 文档转换的标配方案,免费且效果好。如果你的项目需要在 Linux 上转 Word / Excel / PPT,优先考虑它。