基于 Spring Boot + JODConverter 实现文档在线转换为 PDF 功能
适用场景 :企业办公系统、OA、知识库、文档管理平台等需要将 Word/Excel/PPT 等格式转为 PDF 的 Web 应用
技术栈:Spring Boot 3.x + JODConverter + LibreOffice + Hutool + OSS
一、背景与需求
在很多企业级应用中,用户上传的 .docx、.xlsx、.pptx 等 Office 文档,往往需要统一转换为 PDF 格式进行预览、归档或打印。原因包括:
- PDF 具有跨平台一致性;
- 防止内容被随意编辑;
- 便于前端使用 PDF.js 等库进行安全预览。
而市面上成熟的解决方案中,LibreOffice + JODConverter 是一个开源、稳定且支持多种格式的组合,非常适合集成到 Spring Boot 项目中。
本文将带你从零搭建一个 RESTful 文档转 PDF 接口,并附上完整的生产级代码。
二、技术选型说明
| 组件 | 作用 |
|---|---|
| LibreOffice | 开源办公套件,提供文档渲染与转换能力(需后台运行) |
| JODConverter | Java 封装库,通过 UNO API 调用 LibreOffice 进行格式转换 |
| Spring Boot | 快速构建 Web 服务 |
| Hutool | 简化文件操作、路径处理、工具类封装 |
| OSS Service | 可选,用于临时文件存储或结果上传(如 MinIO、阿里云 OSS) |
⚠️ 注意:JODConverter 依赖 LibreOffice 进程,必须确保服务器已安装并启动 LibreOffice 服务 (通常以
--headless --accept="socket..."模式运行)。
三、核心实现步骤
1. 添加依赖(Maven)
<!-- JODConverter 核心 -->
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-spring-boot-starter</artifactId>
<version>4.4.6</version>
</dependency>
<!-- Hutool 工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
如果你使用的是 JODConverter Local (本地调用 LibreOffice),还需确保
libreoffice已安装:
# Ubuntu sudo apt install libreoffice # 后台启动 LibreOffice(无界面模式) soffice --headless --accept="socket,host=127.0.0.1,port=2002;urp;" --nofirststartwizard &
2. 编写 Controller:接收文件并返回 PDF
@RestController
@RequestMapping("${spring.application.name}/document/converter")
@AllArgsConstructor
@Tag(name = "DocumentConverterController", description = "文档转换API")
public class DocumentConverterController {
private final DocumentConverterService documentConverterService;
@PostMapping(value = "/toPdf", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "上传任意文档,转换为PDF并下载")
public ResponseEntity<byte[]> toPdf(@RequestPart("file") MultipartFile file) {
byte[] pdfBytes = documentConverterService.toPdfByte(file);
String filename = FileNameUtil.getPrefix(file.getOriginalFilename()) + "." + "pdf";
return HttpFileUtil.downloadByte(pdfBytes, filename);
}
}
✅ 支持
.doc,.docx,.xls,.xlsx,.ppt,.pptx等常见格式。
3. 实现 Service:核心转换逻辑
关键方法 toPdf(MultipartFile) 流程如下:
-
校验文件有效性;
-
保存为临时文件 (因 JODConverter 只接受
File对象); -
调用 LibreOffice 转换;
-
读取 PDF 字节数组;
-
清理临时文件(防止磁盘爆满)。
@Slf4j
@Service
@AllArgsConstructor
public class DocumentConverterServiceImpl implements DocumentConverterService {private final OssProperties ossProperties; private final OssService ossService; private DocumentConverter getConverter() { try { DocumentConverter converter = SpringUtil.getBean(DocumentConverter.class); AssertUtil.notNull(converter, "请先配置LibreOffice!"); return converter; } catch (NoSuchBeanDefinitionException ex) { throw new ServiceException("请先配置LibreOffice!"); } } @Override public File toPdf(MultipartFile file) { if (file == null || file.isEmpty()) { throw new ServiceException("文件不能为空"); } // 构建临时目录 String tempDir = ossProperties.getLocal().getPath() + "/temp/" + IdGeneratorUtil.getSnowflakeNextIdStr(); File inputFile = FileUtil.writeFromStream(file.getInputStream(), tempDir + "/" + file.getOriginalFilename()); try { return this.toPdf(inputFile); // 重载方法 } finally { FileUtil.del(tempDir); // 清理整个临时目录 } } @Override public File toPdf(File inputFile) { String ext = FileNameUtil.getSuffix(inputFile.getName()).toLowerCase(); if ("pdf".equals(ext)) { return inputFile; // 已是 PDF,无需转换 } String outputPath = inputFile.getParent() + "/" + FileUtil.getPrefix(inputFile.getName()) + ".pdf"; File outputFile = FileUtil.touch(outputPath); long start = System.currentTimeMillis(); try { getConverter().convert(inputFile).to(outputFile).execute(); log.info("转换成功,耗时 {} ms", System.currentTimeMillis() - start); return outputFile; } catch (Exception e) { log.error("文档转换失败", e); throw new ServiceException("不支持的文档格式或转换异常"); } } @Override public byte[] toPdfByte(MultipartFile file) { File pdfFile = null; try { pdfFile = toPdf(file); return FileUtil.readBytes(pdfFile); } finally { if (pdfFile != null) { FileUtil.del(pdfFile.getParentFile()); // 安全删除父目录 } } }}
🔒 安全提示 :务必在
finally块中删除临时文件,避免攻击者通过大量上传占满磁盘。
4. 工具类补充:HttpFileUtil
用于生成可下载的 PDF 响应:
public class HttpFileUtil {
public static ResponseEntity<byte[]> downloadByte(byte[] content, String filename) {
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", URLEncoder.encode(filename, StandardCharsets.UTF_8));
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentLength(content.length);
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
}
四、部署与配置建议
1. LibreOffice 启动脚本(推荐 systemd)
创建 /etc/systemd/system/libreoffice.service:
[Unit]
Description=LibreOffice Headless Service
After=network.target
[Service]
Type=simple
User=your-app-user
ExecStart=/usr/bin/soffice --headless --accept="socket,host=127.0.0.1,port=2002;urp;" --nofirststartwizard
Restart=always
[Install]
WantedBy=multi-user.target
然后启用:
sudo systemctl daemon-reload
sudo systemctl enable libreoffice
sudo systemctl start libreoffice
2. JODConverter 配置(application.yml)
jodconverter:
local:
office-home: /usr/lib/libreoffice # 可选,自动检测通常足够
port: 2002
task-execution-timeout: 120000 # 超时2分钟
五、扩展功能(可选)
- 异步转换 :使用
@Async避免大文件阻塞 HTTP 请求; - 结果上传 OSS :如代码中的
toPdfAndUpload方法; - 格式白名单校验 :防止恶意文件(如
.exe)上传; - 转换队列限流:避免 LibreOffice 进程过载。
六、总结
通过 Spring Boot + JODConverter + LibreOffice,我们实现了高效、稳定的文档转 PDF 能力。该方案已在多个生产系统中验证,支持高并发(配合连接池)、格式丰富、易于维护。
💡 最后提醒 :LibreOffice 是重量级进程,不要在容器中频繁启停 ,建议常驻运行;若追求轻量,可考虑 OnlyOffice 或 Pandoc 等替代方案。
GitHub 示例项目 (可自行搭建):欢迎 Star ⭐
关键词:#SpringBoot #PDF #文档转换 #LibreOffice #JODConverter #Java
如有疑问,欢迎评论区交流!如果你觉得这篇文章对你有帮助,别忘了点赞、收藏、转发~