LibreOffice 实现 Word 转 PDF

LibreOffice 实现 Word 转 PDF

一、功能说明

通过 Java 调用 LibreOffice 的命令行工具 soffice,实现无界面(静默)模式 将 Word 文件(.doc/.docx 都支持)高质量转换为 PDF 文件,支持自定义输出文件名、自动创建输出目录,完全适配 Windows/Linux 服务器环境,是生产环境中最常用的办公文件转换方案之一。

二、前置准备(必做)

1. 下载安装 LibreOffice

官网下载:https://www.libreoffice.org/download/download-libreoffice/

  • Windows:安装后必须将 LibreOffice 的安装目录 /program/ 配置到系统环境变量 Path (比如 C:\Program Files\LibreOffice\program),配置后重启 IDE/命令行生效
  • Linux:直接通过命令安装 yum install libreoffice-headless libreoffice-writer -y(推荐,极简无界面)

2. 核心依赖说明

该方案为纯 Java 原生实现无任何第三方依赖包,无需引入 poi/itext 等 jar 包,通过系统命令调用实现转换,稳定无兼容问题。

三、实现代码

java 复制代码
import java.io.*;

/**
 * LibreOffice 实现 Word(doc/docx) 转 PDF 工具类
 * 支持:Windows/Linux 跨平台、无界面静默转换、自定义输出文件名、中文/空格路径
 */
public class LibreOfficeWordToPdfUtil {

    // 转换目标格式常量,便于扩展(如修改为jpg/png/txt等)
    private static final String TARGET_FORMAT = "pdf";
    // LibreOffice 核心命令
    private static final String LIBRE_OFFICE_CMD = "soffice";

    /**
     * Word 转 PDF 核心方法
     * @param inputFilePath 输入文件的完整路径(如:D:/test/测试文件.docx)
     * @param outputDirPath 输出目录路径(如:D:/pdf/)
     * @param outputFileName 输出PDF的文件名(如:转换后的文件.pdf,可省略.pdf后缀,会自动补全)
     * @throws IOException 文件不存在/读写失败/转换失败时抛出
     * @throws InterruptedException 进程执行被中断时抛出
     */
    public static void convertToPdf(String inputFilePath, String outputDirPath, String outputFileName)
            throws IOException, InterruptedException {
        // 1. 封装文件对象
        File inputFile = new File(inputFilePath);
        File outputDir = new File(outputDirPath);

        // 2. 校验输入文件是否存在
        if (!inputFile.exists() || !inputFile.isFile()) {
            throw new IOException("【转换失败】输入的Word文件不存在或不是文件:" + inputFilePath);
        }

        // 3. 校验输入文件是否为 doc/docx 格式
        String inputFileName = inputFile.getName();
        if (!inputFileName.endsWith(".doc") && !inputFileName.endsWith(".docx")) {
            throw new IOException("【转换失败】输入文件不是Word格式,仅支持 .doc / .docx 后缀:" + inputFilePath);
        }

        // 4. 自动创建输出目录(多级目录也能创建)
        if (!outputDir.exists()) {
            boolean mkdirsFlag = outputDir.mkdirs();
            if (!mkdirsFlag) {
                throw new IOException("【转换失败】创建输出目录失败,权限不足:" + outputDirPath);
            }
        }

        // 5. 处理输出文件名,自动补全 .pdf 后缀
        String finalOutputFileName = outputFileName.endsWith("." + TARGET_FORMAT)
                ? outputFileName
                : outputFileName + "." + TARGET_FORMAT;

        // 6. 构建默认输出文件(LibreOffice转换后的默认文件名,和原Word文件名一致)
        String defaultPdfName = inputFileName.substring(0, inputFileName.lastIndexOf(".")) + "." + TARGET_FORMAT;
        File defaultOutputFile = new File(outputDir, defaultPdfName);
        // 构建最终目标文件(自定义文件名)
        File targetOutputFile = new File(outputDir, finalOutputFileName);

        // 7. 构建 LibreOffice 转换命令,使用ProcessBuilder避免空格路径问题
        ProcessBuilder processBuilder = new ProcessBuilder(
                LIBRE_OFFICE_CMD,
                "--headless",        // 无界面模式,核心!服务器环境必须加,不会弹出窗口
                "--nolockcheck",     // 关闭文件锁检查,避免文件被占用
                "--convert-to", TARGET_FORMAT,
                "--outdir", outputDir.getAbsolutePath(),
                inputFile.getAbsolutePath()
        );
        // 合并错误流到标准流,方便统一读取日志
        processBuilder.redirectErrorStream(true);

        // 8. 执行转换命令并获取进程
        Process process = processBuilder.start();
        // 读取进程输出日志,避免进程阻塞卡死(核心优化点)
        readProcessStream(process.getInputStream());

        // 9. 等待进程执行完成,获取退出码(0=成功,非0=失败)
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("【转换失败】LibreOffice执行命令失败,退出码:" + exitCode + ",请检查文件是否损坏或LibreOffice环境配置");
        }

        // 10. 校验默认转换后的文件是否生成
        if (!defaultOutputFile.exists()) {
            throw new IOException("【转换失败】PDF文件转换成功但未生成,未知异常:" + defaultOutputFile.getAbsolutePath());
        }

        // 11. 重命名文件:默认文件名 -> 自定义文件名
        renameOutputFile(defaultOutputFile, targetOutputFile);

        // 12. 释放资源
        process.getInputStream().close();
        process.destroy();

        System.out.println("【转换成功】PDF文件生成路径:" + targetOutputFile.getAbsolutePath());
    }

    /**
     * 读取进程的输出流,避免进程阻塞
     */
    private static void readProcessStream(InputStream inputStream) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = br.readLine()) != null) {
            // 可根据需要打印日志,排查转换问题
            // System.out.println("转换日志:" + line);
        }
        br.close();
    }

    /**
     * 重命名文件,处理目标文件已存在的情况
     */
    private static void renameOutputFile(File sourceFile, File targetFile) throws IOException {
        // 如果目标文件已存在,先删除(删除前判断是否可写,避免权限问题)
        if (targetFile.exists()) {
            boolean deleteFlag = targetFile.delete();
            if (!deleteFlag) {
                throw new IOException("【重命名失败】目标文件已存在且无法删除:" + targetFile.getAbsolutePath());
            }
        }
        // 执行重命名
        boolean renameFlag = sourceFile.renameTo(targetFile);
        if (!renameFlag) {
            throw new IOException("【重命名失败】无法将文件从 " + sourceFile.getAbsolutePath() + " 重命名为 " + targetFile.getAbsolutePath());
        }
    }

    // ========== 测试调用示例 ==========
    public static void main(String[] args) {
        try {
            // 测试参数:输入文件路径、输出目录、自定义PDF文件名
            String inputPath = "D:/test/我的测试文档.docx";
            String outputDir = "D:/test/pdf输出";
            String outputFileName = "测试文档_转换版";
            convertToPdf(inputPath, outputDir, outputFileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、核心API说明 & 关键参数解释

1. 核心命令参数详解

bash 复制代码
soffice --headless --nolockcheck --convert-to pdf --outdir 输出目录 输入文件路径
  • --headless:无界面模式,最核心参数,服务器/生产环境必须添加,不会弹出LibreOffice窗口,静默执行
  • --nolockcheck:关闭文件锁检查,避免文件被占用导致转换失败
  • --convert-to pdf:指定转换的目标格式为PDF(可替换为 jpg/png/txt/odt 等,LibreOffice支持超多格式)
  • --outdir:指定转换后的文件输出目录
  • 最后跟待转换的文件路径

2. 方法入参说明

  • inputFilePath必填 ,Word文件的完整绝对路径,例如 D:/test/abc.docx
  • outputDirPath必填 ,PDF输出的目录路径,例如 D:/test/out,目录不存在会自动创建
  • outputFileName必填 ,PDF的自定义文件名,支持「带后缀」和「不带后缀」两种写法,例如 test.pdf / test 均可

五、使用注意事项(避坑指南 · 重中之重)

通用注意事项

  1. 转换后的PDF文件会完美保留原Word的格式、图片、表格、字体样式,比poi+itext的方案转换质量高得多
  2. 必须保证输出目录有写入权限,输入文件有读取权限,否则会抛出权限异常
  3. 支持批量转换,循环调用该方法即可,无并发限制

Windows 环境专属

  1. LibreOffice安装后必须配置环境变量 ,否则会提示 soffice 不是内部或外部命令
  2. 如果转换中文文件乱码,需要在Windows上安装对应的中文字体(宋体、微软雅黑等)

Linux 服务器环境专属(生产环境99%是Linux,必看)

  1. 安装命令推荐:yum install libreoffice-headless libreoffice-writer libreoffice-langpack-zh-CN -y,安装中文语言包避免中文乱码
  2. Linux下无需配置环境变量soffice 命令全局可用
  3. 解决Linux中文乱码核心方案:把Windows的中文字体(比如 simsun.ttc 宋体)上传到Linux的 /usr/share/fonts 目录,执行 fc-cache -fv 更新字体缓存
  4. Linux下执行时,确保运行Java程序的用户有执行 soffice 命令的权限

六、常见问题排查

问题1:转换失败,退出码 exitCode != 0

  • 原因1:LibreOffice环境未配置好,执行 soffice --version 命令测试是否能正常输出版本信息
  • 原因2:输入文件损坏或不是标准的doc/docx文件
  • 原因3:输出目录权限不足,无法写入文件
  • 原因4:文件被其他程序占用(比如Word打开未关闭)

问题2:转换后的PDF中文显示为方框/乱码

  • 解决方案:系统缺少中文字体,Windows安装中文字体,Linux上传中文字体并更新缓存(上文已说明)

问题3:进程卡死,无响应,不抛出异常

  • 原因:没有读取进程的输出流,导致缓冲区满了,进程阻塞
  • 解决方案:代码中已经通过 readProcessStream 方法处理,无需额外修改

问题4:重命名文件失败

  • 原因:目标文件已存在且被占用,或权限不足
  • 解决方案:代码中会先删除已存在的目标文件,再执行重命名,确保无冲突

七、扩展能力

LibreOffice的转换能力极强,该工具类仅需修改常量 TARGET_FORMAT,即可实现:

  • Word 转 图片(jpg/png
  • Word 转 纯文本(txt
  • Word 转 电子书(epub
  • Excel/PowerPoint 转 PDF 等

总结

该方案是生产环境最优的办公文件转换方案,相比POI+IText的纯Java代码方案,优势在于:

  1. 无需处理复杂的Office格式兼容问题,转换质量极高
  2. 无第三方依赖,代码简洁,维护成本低
  3. 支持所有Office文件格式,扩展能力强
  4. 跨平台兼容,Windows/Linux无缝切换
相关推荐
大道之简1 小时前
SpringBoot自定义链路追踪
java·spring boot·spring
hhzz2 小时前
Springboot项目中使用EasyPOI操作Excel(详细教程系列4/4)
java·spring boot·后端·spring·excel·poi·easypoi
星火开发设计2 小时前
表达式与语句:C++ 程序的执行逻辑基础
java·开发语言·c++·学习·知识·表达式
计算机毕设指导62 小时前
基于微信小程序求职招聘-兼职管理系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·求职招聘
小白不会Coding2 小时前
一文讲清楚JVM字节码文件的组成
java·jvm·字节码文件
深念Y2 小时前
IDEA下载JDK慢的真相:权限、DNS与CDN的解析
java·ide·intellij-idea
Remember_9932 小时前
【数据结构】二叉树:从基础到应用全面解析
java·数据结构·b树·算法·leetcode·链表
冷冷的菜哥2 小时前
springboot调用ffmpeg实现对视频的截图,截取与水印
java·spring boot·ffmpeg·音视频·水印·截图·截取
C++chaofan2 小时前
JUC并发编程:LockSupport.park() 与 unpark() 深度解析
java·开发语言·c++·性能优化·高并发·juc