基于 Spring Boot + JODConverter 实现文档在线转换为 PDF 功能

基于 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) 流程如下:

  1. 校验文件有效性;

  2. 保存为临时文件 (因 JODConverter 只接受 File 对象);

  3. 调用 LibreOffice 转换;

  4. 读取 PDF 字节数组;

  5. 清理临时文件(防止磁盘爆满)。

    @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


如有疑问,欢迎评论区交流!如果你觉得这篇文章对你有帮助,别忘了点赞、收藏、转发~

相关推荐
silvamux_2 小时前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
步行cgn3 小时前
StringHttpMessageConverter 详解:Spring MVC 中字符串读写的默认转换器
java·spring·mvc
摇滚侠3 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
java·笔记·python
小蒜学长3 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
Deng桑3 小时前
SpringAI写第一个Agent-把工单循环跑起来
java·agent·工具调用·spring ai
步行cgn5 小时前
@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
java·spring·mvc
jason成都6 小时前
RTKLIB 的 Java 移植版来了:rtklib-java v3.3.0 功能全景
java·gnss·rtklib·rtklib_java
LazyHunter96 小时前
JDK 28:Java 下一步方向解析与新特性展望
java·jdk
m0_639906946 小时前
Java 重写 Override 与重载 Overload 区别、访问修饰符、static 关键字
java·开发语言
GIS数据转换器6 小时前
基于CIM的城市智慧供热监管服务平台
java·大数据·开发语言·人工智能·后端·物联网·struts