liujie
2026-01-26 753097b2693fec43c8bbbd766b8c1b32d73d7e43
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
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();
    }
}