spring boot 和php 调用 LibreOffice 转换 Excel 到 PDF 完整实现 在实际企业应用开发中经常需要将 Excel 报表、采购订单、入库单等文档转换为 PDF 格式以便于存档、打印或分发。相比直接操作 Excel 文件PDF 具有跨平台、防篡改、版面固定等优点。而 LibreOffice 作为一款开源的办公套件提供了强大的命令行转换能力能够高质量地保留 Excel 的复杂样式、图表、公式和排版是 Java 后端实现文档转换的理想选择。本文将详细介绍如何使用 Java 调用 LibreOffice 将 Excel 文件转换为 PDF并提供完整的代码示例、参数说明及常见问题解决方案。比如下面这种复杂excel表格转换pdf就恒麻烦尤其是样式回错乱。下面介绍一种方式来实现windows环境下的无损转换pdflinux只需要安装luinx下的包即可这里不做演示。一、为什么选择 LibreOffice目前主流的 Excel 转 PDF 方案有以下几种方案优点缺点Apache POI iText纯 Java无需安装额外软件样式还原度差对复杂表格、图表支持弱JodConverter封装了 LibreOffice 的调用API 友好同样需要安装 LibreOffice但依赖较重直接调用 LibreOffice 命令行样式保真度最高免费开源跨平台需要安装 LibreOffice依赖外部进程在企业级应用中样式保真度往往是最重要的指标。LibreOffice 能够完美呈现 Excel 中的字体、颜色、边框、合并单元格、公式计算结果、甚至嵌入式图表这是其他纯 Java 方案难以比拟的。因此推荐使用 Java 调用 LibreOffice 命令行的方式。二、环境准备2.1 安装 LibreOfficeWindows从 LibreOffice 官网 下载 LibreOffice | LibreOffice 简体中文官方网站 - 自由免费的办公套件下载安装包默认安装路径为C:\Program Files\LibreOffice\program\soffice.exeLinux (Ubuntu/Debian)sudo apt install libreoffice -yLinux (CentOS/RHEL)sudo yum install libreoffice -ymacOS通过 Homebrew 安装brew install --cask libreoffice安装后在终端执行soffice --version验证是否成功。2.2 Java 环境JDK 17及以上版本任何 Java 框架均可Spring Boot、普通 Maven 项目等三、核心原理LibreOffice 提供无界面headless模式可以通过命令行参数完成文档格式转换而不启动图形界面。Java 通过ProcessBuilder或Runtime.exec()调用系统命令执行 LibreOffice 的转换指令然后读取生成的 PDF 文件即可。基本命令格式如下bashsoffice --headless --convert-to pdf --outdir /output/dir /path/to/input.xlsx--headless无界面模式必需--convert-to pdf转换为 PDF--outdir输出目录最后为输入文件路径四、Java 实现步骤4.1 创建转换器类javapublic class LibreOfficeConverter { private static final Logger log LoggerFactory.getLogger(LibreOfficeConverter.class); private String sofficePath; private int timeoutSeconds; public LibreOfficeConverter(String sofficePath, int timeoutSeconds) { this.sofficePath sofficePath; this.timeoutSeconds timeoutSeconds; } public String excelToPdf(String excelPath, String outputDir) throws Exception { // ... 方法实现见之前的代码 File excelFile new File(excelPath); if (!excelFile.exists()) { throw new Exception(Excel文件不存在 excelPath); } File outputDirFile new File(outputDir); if (!outputDirFile.exists()) { outputDirFile.mkdirs(); } String absoluteExcelPath excelFile.getAbsolutePath(); String absoluteOutputDir outputDirFile.getAbsolutePath(); String command String.format( %s --headless --convert-to pdf:writer_pdf_Export --outdir %s %s, sofficePath, absoluteOutputDir, absoluteExcelPath ); log.info(LibreOffice转换命令{}, command); ProcessBuilder processBuilder new ProcessBuilder(); if (System.getProperty(os.name).toLowerCase().contains(windows)) { processBuilder.command(cmd.exe, /c, command); } else { processBuilder.command(bash, -c, command); } processBuilder.environment().put(LANG, zh_CN.UTF-8); processBuilder.environment().put(LANGUAGE, zh_CN.UTF-8); processBuilder.redirectErrorStream(true); Process process null; try { process processBuilder.start(); StringBuilder output new StringBuilder(); try (BufferedReader reader new BufferedReader( new InputStreamReader(process.getInputStream(), UTF-8))) { String line; while ((line reader.readLine()) ! null) { output.append(line).append(\n); } } boolean finished process.waitFor(timeoutSeconds, TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); throw new Exception(LibreOffice转换超时 timeoutSeconds 秒); } int exitCode process.exitValue(); if (exitCode ! 0) { throw new Exception(PDF转换失败退出码 exitCode); } String excelBaseName excelFile.getName(); int dotIndex excelBaseName.lastIndexOf(.); String pdfName (dotIndex 0 ? excelBaseName.substring(0, dotIndex) : excelBaseName) .pdf; String pdfPath absoluteOutputDir File.separator pdfName; return pdfPath; } finally { if (process ! null process.isAlive()) { process.destroyForcibly(); } } } public boolean isAvailable() { try { // 方法1直接检查文件是否存在 File sofficeFile new File(sofficePath); System.out.println(检查路径: sofficeFile.getAbsolutePath()); System.out.println(文件是否存在: sofficeFile.exists()); System.out.println(是否可读: sofficeFile.canRead()); System.out.println(是否可执行: sofficeFile.canExecute()); if (!sofficeFile.exists()) { // 尝试常见路径 String[] commonPaths { D:/installsoftware/program/soffice.exe, D:\\installsoftware\\program\\soffice.exe, }; for (String path : commonPaths) { File testFile new File(path); if (testFile.exists()) { sofficePath path; sofficeFile testFile; System.out.println(找到LibreOffice: path); break; } } if (!sofficeFile.exists()) { System.err.println(未找到LibreOffice可执行文件); return false; } } // 方法2直接执行命令使用 ProcessBuilder ProcessBuilder pb new ProcessBuilder( sofficeFile.getAbsolutePath(), --version ); pb.redirectErrorStream(true); Process process pb.start(); // 读取输出 StringBuilder output new StringBuilder(); try (BufferedReader reader new BufferedReader( new InputStreamReader(process.getInputStream(), UTF-8))) { String line; while ((line reader.readLine()) ! null) { output.append(line); } } boolean finished process.waitFor(5, TimeUnit.SECONDS); if (finished process.exitValue() 0) { System.out.println(LibreOffice版本: output.toString()); return true; } else { System.err.println(退出码: process.exitValue()); return false; } } catch (Exception e) { System.err.println(检查失败: e.getMessage()); e.printStackTrace(); return false; } } }javaRestController RequestMapping(/api/convert) public class ExcelToPdfController { Value(${libreoffice.path}) private String sofficePath; Value(${libreoffice.timeout:300}) private int timeout; Value(${file.upload-dir:./uploads}) private String uploadDir; Value(${file.pdf-dir:./pdfs}) private String pdfDir; /** * 上传Excel并转换为PDF */ PostMapping(/excel-to-pdf) public ResponseEntity? convertExcelToPdf(RequestParam(file) MultipartFile file) { Path excelFilePath null; try { // 使用绝对路径并规范化路径 Path uploadPath Paths.get(uploadDir).toAbsolutePath().normalize(); Path pdfPath Paths.get(pdfDir).toAbsolutePath().normalize(); // 打印调试信息 System.out.println(上传目录绝对路径: uploadPath); System.out.println(PDF目录绝对路径: pdfPath); // 创建目录 if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); System.out.println(创建上传目录: uploadPath); } if (!Files.exists(pdfPath)) { Files.createDirectories(pdfPath); System.out.println(创建PDF目录: pdfPath); } // 保存上传的Excel文件 String originalFilename file.getOriginalFilename(); System.out.println(原始文件名: originalFilename); // 处理文件扩展名 String ext ; if (originalFilename ! null originalFilename.contains(.)) { ext originalFilename.substring(originalFilename.lastIndexOf(.)); } else { ext .xlsx; } String fileName UUID.randomUUID().toString() ext; excelFilePath uploadPath.resolve(fileName); // 确保父目录存在 Files.createDirectories(excelFilePath.getParent()); // 保存文件 file.transferTo(excelFilePath.toFile()); System.out.println(保存Excel到: excelFilePath); // 检查文件是否保存成功 if (!Files.exists(excelFilePath)) { throw new Exception(Excel文件保存失败); } // 检查LibreOffice LibreOfficeConverter converter new LibreOfficeConverter(sofficePath, timeout); boolean available converter.isAvailable(); System.out.println(LibreOffice可用性: available); if (!available) { throw new Exception(LibreOffice不可用请检查路径: sofficePath); } // 转换 String pdfFilePath converter.excelToPdf(excelFilePath.toString(), pdfPath.toString()); System.out.println(生成PDF: pdfFilePath); // 获取PDF文件名 Path pdfPathObj Paths.get(pdfFilePath); String pdfFileName pdfPathObj.getFileName().toString(); MapString, Object result new HashMap(); result.put(success, true); result.put(pdfPath, pdfFilePath); result.put(pdfName, pdfFileName); result.put(downloadUrl, /api/convert/download/ pdfFileName); return ResponseEntity.ok(result); } catch (Exception e) { e.printStackTrace(); MapString, Object error new HashMap(); error.put(success, false); error.put(message, e.getMessage()); error.put(type, e.getClass().getName()); return ResponseEntity.internalServerError().body(error); } finally { // 删除临时Excel文件 if (excelFilePath ! null Files.exists(excelFilePath)) { try { Files.deleteIfExists(excelFilePath); System.out.println(删除临时文件: excelFilePath); } catch (IOException e) { System.err.println(删除临时文件失败: e.getMessage()); } } } } }4.3 配置文件 (application.yml)yamllibreoffice: path: D:\\installsoft\\program\\soffice.exe timeout: 300 upload-dir: D:\\javaproject\\code\\regionprogect\\search\\uploads # 使用绝对路径 pdf-dir: D:\\javaproject\\code\\regionprogect\\search\\pdfs # 使用绝对路径五、关键参数详解参数作用--headless无图形界面模式不启动 UI--nofirststartwizard禁止首次运行向导弹窗--norestore不恢复上次崩溃未保存的文档--nologo不显示启动 Logo--invisible完全不可见模式配合 headless--convert-to pdf[:writer_pdf_Export]转换为 PDF可指定导出过滤器--outdir指定输出目录-env:UserInstallationfile:///path指定用户配置目录避免弹窗5.1 避免弹窗的额外技巧如果依然弹出“Press Enter to continue...”或配置向导可以添加-env:UserInstallation参数指定一个临时目录javaString tempUserDir System.getProperty(java.io.tmpdir) libreoffice_user_ System.currentTimeMillis(); String[] command { sofficePath, --headless, --nofirststartwizard, -env:UserInstallationfile:/// tempUserDir.replace(\\, /), --convert-to, pdf, --outdir, outputDir, excelPath }; // 转换完成后删除临时目录测试一下转换出来的pdf样式和之前的excel一样的如果是使用php的话也是支持的php实现的代码如下try { $sofficePath D:\installsoft\program\soffice.exe; // 构建命令 // 使用 :writer_pdf_Export 导出器确保 Excel 转 PDF 最佳效果 $command sprintf( %s --headless --convert-to pdf:writer_pdf_Export --outdir %s %s 21, escapeshellcmd($sofficePath), escapeshellarg($saveDir), escapeshellarg($filePath) ); $pdfName pathinfo($filePath, PATHINFO_FILENAME); Log::info(LibreOffice转换命令 . $command); // 设置环境变量解决中文乱码问题 putenv(LANGzh_CN.UTF-8); putenv(LANGUAGEzh_CN.UTF-8); // 执行转换 $output []; $returnCode 0; exec($command, $output, $returnCode); if ($returnCode ! 0) { $errorMsg implode(\n, $output); Log::error(LibreOffice转换失败 . $errorMsg); throw new \Exception(PDF转换失败 . $errorMsg); } if (!file_exists(app()-getRootPath() . public/storage/ .$savePath./.$filename..pdf)) { throw new \Exception(服务器繁忙请稍后再试); } // 返回成功结果 return [ code 200, msg 导出成功, data [ fileName $filename..pdf, fileUrl $savePath , ] ]; } catch (\Exception $e) { return [code 500, msg $e-getMessage()]; }六、异常处理与优化建议6.1 常见异常及解决方法异常现象可能原因解决方案进程超时文件过大或 LibreOffice 卡死增大 timeout 值或使用异步任务退出码非 0文件损坏、密码保护、路径含空格检查文件路径用双引号包裹弹出“User Installation”对话框首次运行缺少配置使用-env:UserInstallation参数中文文件名乱码系统编码问题设置LANGzh_CN.UTF-8环境变量6.2 性能优化复用用户配置目录指定固定的UserInstallation目录避免每次创建临时目录减少初始化开销。控制并发LibreOffice 进程启动较慢约 2-3 秒高并发时建议使用队列 单进程或连接池如 JodConverter 内置的进程池。异步处理对于大文件可改为异步转换 轮询结果避免 HTTP 请求阻塞。6.3 设置环境变量解决中文乱码javapb.environment().put(LANG, zh_CN.UTF-8); pb.environment().put(LANGUAGE, zh_CN.UTF-8);七、完整项目示例Maven 依赖不需要额外依赖仅使用 JDK 标准库。 Spring Boot 项目只需添加 Spring Web 起步依赖xmldependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency八、总结通过 Java 调用 LibreOffice 命令行我们可以低成本地获得企业级 Excel 转 PDF 功能样式保留度接近 100%。该方法不依赖昂贵的商业组件部署简单只需在服务器上安装 LibreOffice 即可。关键步骤回顾安装 LibreOffice记录可执行文件路径。使用ProcessBuilder执行带--headless等参数的转换命令。正确处理进程超时、输出目录、临时文件清理。利用-env:UserInstallation避免弹窗设置环境变量解决中文乱码。该方案已在多个生产环境中稳定运行支持 Excel、Word、PPT 转 PDF是开源技术栈中非常实用的文档转换方案。