package com.estate.expert.admin.utils.wordtopdf;
|
|
import lombok.extern.slf4j.Slf4j;
|
import org.jodconverter.core.office.OfficeException;
|
import org.jodconverter.core.office.OfficeManager;
|
import org.jodconverter.local.LocalConverter;
|
import org.jodconverter.local.office.LocalOfficeManager;
|
|
import javax.annotation.PreDestroy;
|
import java.io.File;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
/**
|
* Word 转 PDF 工具类(使用 LibreOffice/OpenOffice)
|
* 支持图表、图片、表格等复杂格式
|
*/
|
@Slf4j
|
public class WordToPdfConverter {
|
|
private OfficeManager officeManager;
|
private static WordToPdfConverter instance;
|
private final AtomicInteger conversionCount = new AtomicInteger(0);
|
|
// LibreOffice 安装路径(根据您的安装位置调整)
|
private static final String LIBRE_OFFICE_PATH = "C:\\Program Files\\LibreOffice";
|
//private static final String LIBRE_OFFICE_PATH = "C:\\Program Files (x86)\\LibreOffice";
|
|
// 支持转换的文件格式
|
private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList(
|
".doc", ".docx", ".rtf", ".txt", ".odt"
|
);
|
|
private WordToPdfConverter() {
|
initializeOfficeManager();
|
}
|
|
/**
|
* 获取单例实例
|
*/
|
public static synchronized WordToPdfConverter getInstance() {
|
if (instance == null) {
|
instance = new WordToPdfConverter();
|
}
|
return instance;
|
}
|
|
/**
|
* 初始化 Office 管理器
|
*/
|
private void initializeOfficeManager() {
|
try {
|
log.info("正在初始化 LibreOffice 管理器...");
|
|
// 检查 LibreOffice 是否安装
|
File officeHome = new File(LIBRE_OFFICE_PATH);
|
if (!officeHome.exists() || !officeHome.isDirectory()) {
|
throw new RuntimeException("未找到 LibreOffice 安装目录: " + LIBRE_OFFICE_PATH +
|
"\n请安装 LibreOffice 或修改 LIBRE_OFFICE_PATH 配置");
|
}
|
|
// 创建 Office 管理器
|
officeManager = LocalOfficeManager.builder()
|
.officeHome(officeHome)
|
.portNumbers(2002) // 使用 2002 端口
|
.processTimeout(300000L) // 5分钟超时
|
.taskExecutionTimeout(300000L) // 5分钟任务超时
|
.maxTasksPerProcess(20) // 每个进程最大任务数
|
.install() // 如果未安装则尝试安装
|
.build();
|
|
// 启动服务
|
officeManager.start();
|
log.info("LibreOffice 管理器启动成功,服务端口: 2002");
|
|
} catch (Exception e) {
|
log.error("初始化 LibreOffice 管理器失败", e);
|
throw new RuntimeException("无法启动 LibreOffice 服务: " + e.getMessage(), e);
|
}
|
}
|
|
/**
|
* 转换 Word 文件为 PDF
|
* @param wordFilePath Word 文件路径
|
* @param pdfFilePath PDF 输出路径
|
* @return 是否转换成功
|
*/
|
public boolean convert(String wordFilePath, String pdfFilePath) {
|
return convert(new File(wordFilePath), new File(pdfFilePath));
|
}
|
|
/**
|
* 转换 Word 文件为 PDF
|
* @param wordFile Word 文件
|
* @param pdfFile PDF 输出文件
|
* @return 是否转换成功
|
*/
|
public boolean convert(File wordFile, File pdfFile) {
|
long startTime = System.currentTimeMillis();
|
int currentCount = conversionCount.incrementAndGet();
|
|
try {
|
// 验证输入文件
|
validateInputFile(wordFile);
|
|
// 准备输出目录
|
prepareOutputDirectory(pdfFile);
|
|
log.info("开始转换 [{}]: {} -> {}", currentCount,
|
wordFile.getName(), pdfFile.getName());
|
|
// 执行转换
|
LocalConverter.builder()
|
.officeManager(officeManager)
|
.build()
|
.convert(wordFile)
|
.to(pdfFile)
|
.execute();
|
|
// 验证输出文件
|
boolean success = validateOutputFile(pdfFile);
|
|
long costTime = System.currentTimeMillis() - startTime;
|
if (success) {
|
log.info("转换成功 [{}]: {} -> {} (耗时: {}ms)",
|
currentCount, wordFile.getName(), pdfFile.getName(), costTime);
|
} else {
|
log.warn("转换完成但文件可能异常 [{}]: {} (耗时: {}ms)",
|
currentCount, wordFile.getName(), costTime);
|
}
|
|
return success;
|
|
} catch (Exception e) {
|
log.error("转换失败 [{}]: {}", currentCount, wordFile.getName(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 验证输入文件
|
*/
|
private void validateInputFile(File wordFile) {
|
if (wordFile == null) {
|
throw new IllegalArgumentException("输入文件不能为空");
|
}
|
|
if (!wordFile.exists()) {
|
throw new IllegalArgumentException("输入文件不存在: " + wordFile.getAbsolutePath());
|
}
|
|
if (!wordFile.isFile()) {
|
throw new IllegalArgumentException("输入路径不是文件: " + wordFile.getAbsolutePath());
|
}
|
|
// 检查文件格式
|
String fileName = wordFile.getName().toLowerCase();
|
boolean supported = SUPPORTED_EXTENSIONS.stream()
|
.anyMatch(fileName::endsWith);
|
|
if (!supported) {
|
throw new IllegalArgumentException("不支持的文件格式: " + fileName +
|
"\n支持的格式: " + SUPPORTED_EXTENSIONS);
|
}
|
|
// 检查文件大小(限制 100MB)
|
long fileSize = wordFile.length();
|
long maxSize = 100 * 1024 * 1024; // 100MB
|
if (fileSize > maxSize) {
|
throw new IllegalArgumentException("文件过大: " + (fileSize / 1024 / 1024) +
|
"MB (最大支持 100MB)");
|
}
|
}
|
|
/**
|
* 准备输出目录
|
*/
|
private void prepareOutputDirectory(File pdfFile) {
|
File parentDir = pdfFile.getParentFile();
|
if (parentDir != null && !parentDir.exists()) {
|
boolean created = parentDir.mkdirs();
|
if (!created) {
|
throw new RuntimeException("无法创建输出目录: " + parentDir.getAbsolutePath());
|
}
|
}
|
}
|
|
/**
|
* 验证输出文件
|
*/
|
private boolean validateOutputFile(File pdfFile) {
|
if (!pdfFile.exists()) {
|
log.error("输出文件未创建: {}", pdfFile.getAbsolutePath());
|
return false;
|
}
|
|
if (pdfFile.length() == 0) {
|
log.error("输出文件为空: {}", pdfFile.getAbsolutePath());
|
return false;
|
}
|
|
// 检查文件头是否为 PDF
|
try (java.io.FileInputStream fis = new java.io.FileInputStream(pdfFile)) {
|
byte[] header = new byte[4];
|
int read = fis.read(header);
|
if (read >= 4) {
|
// PDF 文件头应该是 "%PDF"
|
boolean isPdf = header[0] == '%' &&
|
header[1] == 'P' &&
|
header[2] == 'D' &&
|
header[3] == 'F';
|
if (!isPdf) {
|
log.warn("输出文件可能不是有效的 PDF 文件: {}", pdfFile.getAbsolutePath());
|
}
|
return isPdf;
|
}
|
} catch (Exception e) {
|
log.warn("验证 PDF 文件头失败", e);
|
}
|
|
return true;
|
}
|
|
/**
|
* 批量转换
|
* @param wordFiles Word 文件列表
|
* @param outputDir 输出目录
|
* @return 成功转换的文件数量
|
*/
|
public int batchConvert(List<File> wordFiles, File outputDir) {
|
if (!outputDir.exists()) {
|
outputDir.mkdirs();
|
}
|
|
int successCount = 0;
|
for (int i = 0; i < wordFiles.size(); i++) {
|
File wordFile = wordFiles.get(i);
|
try {
|
String baseName = getFileBaseName(wordFile);
|
File pdfFile = new File(outputDir, baseName + ".pdf");
|
|
log.info("批量转换进度: {}/{} - {}", i + 1, wordFiles.size(), wordFile.getName());
|
|
if (convert(wordFile, pdfFile)) {
|
successCount++;
|
}
|
|
} catch (Exception e) {
|
log.error("批量转换失败: {}", wordFile.getName(), e);
|
}
|
}
|
|
log.info("批量转换完成: 成功 {}/{}", successCount, wordFiles.size());
|
return successCount;
|
}
|
|
/**
|
* 获取文件名(不含扩展名)
|
*/
|
private String getFileBaseName(File file) {
|
String name = file.getName();
|
int dotIndex = name.lastIndexOf('.');
|
return (dotIndex == -1) ? name : name.substring(0, dotIndex);
|
}
|
|
/**
|
* 获取转换统计
|
*/
|
public int getConversionCount() {
|
return conversionCount.get();
|
}
|
|
/**
|
* 重启 Office 服务
|
*/
|
public void restart() {
|
log.info("正在重启 LibreOffice 服务...");
|
shutdown();
|
initializeOfficeManager();
|
log.info("LibreOffice 服务重启完成");
|
}
|
|
/**
|
* 关闭 Office 管理器
|
*/
|
@PreDestroy
|
public void shutdown() {
|
if (officeManager != null && officeManager.isRunning()) {
|
try {
|
officeManager.stop();
|
log.info("LibreOffice 服务已关闭");
|
} catch (OfficeException e) {
|
log.error("关闭 LibreOffice 服务时出错", e);
|
}
|
}
|
}
|
|
/**
|
* 检查服务状态
|
*/
|
public boolean isServiceRunning() {
|
return officeManager != null && officeManager.isRunning();
|
}
|
}
|