2025-01-08
技术分享
00
请注意,本文编写于 314 天前,最后修改于 23 天前,其中某些信息可能已经过时。

目录

Jaocb_v1.21调用WPS COM组件😀👍
优化版本
原始版本

Jaocb_v1.21调用WPS COM组件😀👍

https://sourceforge.net/projects/jacob-project/

https://sourceforge.net/projects/jacob-project/

优化版本

​​优化版本(自定义 ThreadPoolExecutor + 有界队列 + 超时 + 异常处理)更适合生产,更健壮

java
package top.zhoudeshui.utils; import com.jacob.activeX.ActiveXComponent; import com.jacob.com.ComThread; import com.jacob.com.Dispatch; import com.jacob.com.Variant; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.util.concurrent.*; /** * WPS文档转换工具类(使用Jacob调用WPS Office进行格式转换) * 通过线程池队列机制保证并发安全,避免WPS进程冲突 * * @author zhoudeshui * @date 2025-03-20 */ public class WPSConvertUtils { private static final Logger logger = LoggerFactory.getLogger(WPSConvertUtils.class); // 转换常量 private static final int WD_FORMAT_PDF = 17; // Word 转 PDF 格式 private static final int XL_TYPE_PDF = 0; // Excel 转 PDF 格式 private static final int PP_SAVE_AS_PDF = 32; // PPT 转 PDF 格式 /** * 线程池核心线程数 */ private static final int CORE_POOL_SIZE = 1; /** * 线程池最大线程数 */ private static final int MAX_POOL_SIZE = 3; /** * 空闲线程存活时间(秒) */ private static final long KEEP_ALIVE_TIME = 60L; /** * 任务队列容量 */ private static final int QUEUE_CAPACITY = 50; /** * 转换任务超时时间(秒) */ private static final long CONVERSION_TIMEOUT = 120L; /** * CompletableFuture 等待结果的超时时间 */ private static final long FUTURE_TIMEOUT = 130L; /** * 单线程线程池用于串行执行WPS转换任务,避免多实例冲突 */ private static final ExecutorService CONVERSION_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<>(QUEUE_CAPACITY), r -> new Thread(r, "WPS-Conversion-Thread"), new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行 ); /** * 将文件转换为PDF(支持Word、Excel、PPT、图片、PDF复制) * * @param inputFilePath 输入文件路径 * @param outputDirOrFilePath 输出目录或完整PDF路径 * @return CompletableFuture<String> 转换成功返回PDF路径,失败返回null */ public static CompletableFuture<String> convertToPDF(String inputFilePath, String outputDirOrFilePath) { try { // 异步提交任务 return CompletableFuture.supplyAsync(() -> { try { return processConversionTask(inputFilePath, outputDirOrFilePath); } catch (Exception e) { logger.error("转换任务执行异常: {}", inputFilePath, e); return null; } }, CONVERSION_EXECUTOR) .orTimeout(FUTURE_TIMEOUT, TimeUnit.SECONDS) .exceptionally(throwable -> { logger.error("转换任务超时或异常: {}", inputFilePath, throwable); return null; }); } catch (Exception e) { logger.error("提交转换任务失败: {}", inputFilePath, e); CompletableFuture<String> future = new CompletableFuture<>(); future.complete(null); return future; } } /** * 处理具体的转换任务 * * @param inputFilePath 输入文件路径 * @param outputDirOrFilePath 输出路径 * @return PDF文件路径,失败返回null */ private static String processConversionTask(String inputFilePath, String outputDirOrFilePath) { File inputFile = new File(inputFilePath); if (!inputFile.exists()) { logger.warn("原文件不存在: {}", inputFilePath); return null; } String kind = getFileSuffix(inputFilePath).toLowerCase(); if ("pdf".equalsIgnoreCase(kind)) { return copyPdfFile(inputFilePath, outputDirOrFilePath); } String baseName = getBaseName(inputFile.getName()); String outputFilePath = determineOutputPath(outputDirOrFilePath, baseName, ".pdf"); if (outputFilePath == null) { logger.error("无法确定输出路径: {}", outputDirOrFilePath); return null; } // 创建输出目录 File outputFile = new File(outputFilePath); if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) { logger.error("创建输出目录失败: {}", outputFile.getParentFile().getAbsolutePath()); return null; } // 执行转换 boolean success; switch (kind) { case "doc": case "docx": case "txt": success = wordToPDF(inputFilePath, outputFilePath); break; case "xls": case "xlsx": success = exToPDF(inputFilePath, outputFilePath); break; case "ppt": case "pptx": case "pptm": case "ppsx": success = pptToPDF(inputFilePath, outputFilePath); break; case "png": case "jpg": case "jpeg": case "gif": success = imageToPDF(inputFilePath, outputFilePath); break; default: logger.warn("不支持的文件格式: {}", kind); return null; } return success ? outputFilePath : null; } /** * 复制PDF文件(不做转换) * * @param inputFilePath 源PDF路径 * @param outputDirOrFilePath 输出路径 * @return 目标PDF路径,失败返回null */ private static String copyPdfFile(String inputFilePath, String outputDirOrFilePath) { try { Path source = Path.of(inputFilePath); Path target = Path.of(outputDirOrFilePath); if (!Files.exists(source)) { logger.error("源文件不存在: {}", source); return null; } // 如果目标是目录,则拼接文件名 if (Files.isDirectory(target)) { target = target.resolve(source.getFileName()); } // 确保父目录存在 Path parent = target.getParent(); if (parent != null && !Files.exists(parent)) { Files.createDirectories(parent); } Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); logger.info("PDF 文件已复制: {} -> {}", source, target); return target.toString(); } catch (IOException e) { logger.error("复制PDF文件失败", e); return null; } } /** * 确定输出文件路径 * * @param outputDirOrFilePath 输出参数(目录或完整路径) * @param baseName 文件基础名 * @param extension 扩展名 * @return 完整输出路径,失败返回null */ private static String determineOutputPath(String outputDirOrFilePath, String baseName, String extension) { File output = new File(outputDirOrFilePath); if (outputDirOrFilePath.toLowerCase().endsWith(extension)) { return outputDirOrFilePath; } else { if (!output.exists() && !output.mkdirs()) { logger.error("创建输出目录失败: {}", output.getAbsolutePath()); return null; } return new File(output, baseName + extension).getAbsolutePath(); } } /** * 获取文件扩展名 * * @param fileName 文件名 * @return 扩展名(不含点) */ private static String getFileSuffix(String fileName) { int lastDot = fileName.lastIndexOf('.'); return (lastDot == -1) ? "" : fileName.substring(lastDot + 1).toLowerCase(); } /** * 获取文件基础名(不含扩展名) * * @param fileName 文件名 * @return 基础名 */ private static String getBaseName(String fileName) { int lastDot = fileName.lastIndexOf('.'); return (lastDot == -1) ? fileName : fileName.substring(0, lastDot); } /** * Word 转 PDF * * @param inputFile 输入文件路径 * @param pdfFile 输出PDF路径 * @return 是否成功 */ private static boolean wordToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KWPS.Application"); app.setProperty("Visible", new Variant(false)); app.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏 Dispatch docs = app.getProperty("Documents").toDispatch(); Dispatch doc = Dispatch.call(docs, "Open", inputFile, false, true).toDispatch(); Dispatch.call(doc, "ExportAsFixedFormat", pdfFile, WD_FORMAT_PDF); Dispatch.call(doc, "Close", false); logger.info("Word 转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("Word 转换失败: {}", inputFile, e); return false; } finally { releaseWPSApp(app); } } /** * Excel 转 PDF * * @param inputFile 输入文件路径 * @param pdfFile 输出PDF路径 * @return 是否成功 */ private static boolean exToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KET.Application"); app.setProperty("Visible", new Variant(false)); app.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏 Dispatch workbooks = app.getProperty("Workbooks").toDispatch(); Dispatch workbook = Dispatch.invoke(workbooks, "Open", Dispatch.Method, new Object[]{inputFile, new Variant(false), new Variant(false)}, new int[9]).toDispatch(); Dispatch.invoke(workbook, "ExportAsFixedFormat", Dispatch.Method, new Object[]{new Variant(XL_TYPE_PDF), pdfFile}, new int[1]); Dispatch.call(workbook, "Close", new Variant(false)); logger.info("Excel 转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("Excel 转换失败: {}", inputFile, e); return false; } finally { releaseWPSApp(app); } } /** * PPT 转 PDF * * @param inputFile 输入文件路径 * @param pdfFile 输出PDF路径 * @return 是否成功 */ private static boolean pptToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KWPP.Application"); app.setProperty("Visible", new Variant(false)); app.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏 Dispatch presentations = app.getProperty("Presentations").toDispatch(); Dispatch ppt = Dispatch.call(presentations, "Open", inputFile, true, false).toDispatch(); Dispatch.invoke(ppt, "SaveAs", Dispatch.Method, new Object[]{pdfFile, new Variant(PP_SAVE_AS_PDF)}, new int[1]); Dispatch.call(ppt, "Close"); logger.info("PPT 转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("PPT 转换失败: {}", inputFile, e); return false; } finally { releaseWPSApp(app); } } /** * 图片转 PDF(使用PDFBox) * * @param inputFile 输入图片路径 * @param pdfFile 输出PDF路径 * @return 是否成功 */ private static boolean imageToPDF(String inputFile, String pdfFile) { try (PDDocument document = new PDDocument()) { PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); BufferedImage image = ImageIO.read(new File(inputFile)); PDImageXObject pdImage = LosslessFactory.createFromImage(document, image); float scaleX = PDRectangle.A4.getWidth() / pdImage.getWidth(); float scaleY = PDRectangle.A4.getHeight() / pdImage.getHeight(); float scale = Math.min(scaleX, scaleY); float x = (PDRectangle.A4.getWidth() - pdImage.getWidth() * scale) / 2; float y = (PDRectangle.A4.getHeight() - pdImage.getHeight() * scale) / 2; try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { contentStream.drawImage(pdImage, x, y, pdImage.getWidth() * scale, pdImage.getHeight() * scale); } document.save(pdfFile); logger.info("图片转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (IOException e) { logger.error("图片转换失败: {}", inputFile, e); return false; } } /** * 安全释放WPS应用实例 * * @param app ActiveXComponent 实例 */ private static void releaseWPSApp(ActiveXComponent app) { if (app != null) { try { app.invoke("Quit"); } catch (Exception e) { logger.warn("WPS Quit调用异常", e); } finally { app.safeRelease(); } } ComThread.Release(); } /** * 关闭转换线程池(应用关闭时调用) */ public static void shutdown() { if (!CONVERSION_EXECUTOR.isShutdown()) { CONVERSION_EXECUTOR.shutdown(); try { if (!CONVERSION_EXECUTOR.awaitTermination(CONVERSION_TIMEOUT, TimeUnit.SECONDS)) { CONVERSION_EXECUTOR.shutdownNow(); } } catch (InterruptedException e) { CONVERSION_EXECUTOR.shutdownNow(); Thread.currentThread().interrupt(); } } } }

原始版本

(使用 Executors.newSingleThreadExecutor() 无界队列)极易在高并发下造成 OOM

java
package top.zhoudeshui.utils; import com.jacob.activeX.ActiveXComponent; import com.jacob.com.ComThread; import com.jacob.com.Dispatch; import com.jacob.com.Variant; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.concurrent.*; public class WPSConvertUtils { private static final Logger logger = LoggerFactory.getLogger(WPSConvertUtils.class); private static final int WD_FORMAT_PDF = 17; private static final int XL_TYPE_PDF = 0; private static final int PP_SAVE_AS_PDF = 32; private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); public static CompletableFuture<String> convertToPDF(String inputFilePath, String outputDirOrFilePath) { File inputFile = new File(inputFilePath); if (!inputFile.exists()) { logger.warn("原文件不存在: {}", inputFilePath); return CompletableFuture.completedFuture(null); } String kind = getFileSuffix(inputFile.getName()); // 检查文件是否为PDF if ("pdf".equalsIgnoreCase(kind)) { try { Path source = new File(inputFilePath).toPath(); Path target = new File(outputDirOrFilePath).toPath(); // 检查源文件是否存在 if (!Files.exists(source)) { logger.error("Source file does not exist: {}", source); return CompletableFuture.completedFuture(null); } // 如果目标是目录,创建一个以源文件名命名的新文件 if (Files.isDirectory(target)) { target = target.resolve(source.getFileName()); } // 确保目标目录存在 Path targetParent = target.getParent(); if (targetParent != null && !Files.exists(targetParent)) { Files.createDirectories(targetParent); } Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); logger.info("PDF 文件已复制: {} -> {}", source, target); return CompletableFuture.completedFuture(target.toString()); } catch (IOException e) { logger.error("Error copying PDF file", e); return CompletableFuture.completedFuture(null); } } String baseName = inputFile.getName().substring(0, inputFile.getName().lastIndexOf(".")); File outputDirOrFile = new File(outputDirOrFilePath); String outputFilePath; // 检查输出路径是否包含.pdf if (outputDirOrFilePath.toLowerCase().endsWith(".pdf")) { // 输出路径已经包含.pdf,直接使用该文件名 File pdfFile = new File(outputDirOrFilePath); // 确保文件的父目录存在,如果不存在则创建 if (!pdfFile.getParentFile().exists() && !pdfFile.getParentFile().mkdirs()) { logger.error("创建目录失败,请检查目录权限!"); return CompletableFuture.completedFuture(null); } outputFilePath = outputDirOrFilePath; } else { // 输出路径不包含.pdf,使用源文件的基本名称作为输出文件名 if (!outputDirOrFile.exists() && !outputDirOrFile.mkdirs()) { logger.error("创建目录失败,请检查目录权限!"); return CompletableFuture.completedFuture(null); } outputFilePath = new File(outputDirOrFile, baseName + ".pdf").getAbsolutePath(); } // 提交任务到线程池并返回 CompletableFuture return CompletableFuture.supplyAsync(() -> processTask(inputFilePath, outputFilePath, kind), executorService) .thenApply(result -> { if (result) { logger.info("转换成功: {} -> {}", inputFilePath, outputFilePath); return outputFilePath; } else { logger.error("转换失败: {}", inputFilePath); return null; } }); } private static boolean processTask(String inputFilePath, String outputFilePath, String kind) { switch (kind.toLowerCase()) { case "doc": case "docx": case "txt": return wordToPDF(inputFilePath, outputFilePath); case "ppt": case "pptx": case "pptm": case "ppsx": return pptToPDF(inputFilePath, outputFilePath); case "xls": case "xlsx": return exToPDF(inputFilePath, outputFilePath); case "png": case "jpg": case "jpeg": case "gif": return imageToPDF(inputFilePath, outputFilePath); default: logger.warn("不支持的文件格式: {}", kind); return false; } } private static String getFileSuffix(String fileName) { int splitIndex = fileName.lastIndexOf("."); return splitIndex == -1 ? "" : fileName.substring(splitIndex + 1); } private static boolean wordToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KWPS.Application"); app.setProperty("Visible", new Variant(false)); app.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏 Dispatch docs = app.getProperty("Documents").toDispatch(); Dispatch doc = Dispatch.call(docs, "Open", inputFile, false, true).toDispatch(); Dispatch.call(doc, "ExportAsFixedFormat", pdfFile, WD_FORMAT_PDF); Dispatch.call(doc, "Close", false); logger.info("Word 文件转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("Word 文件转换失败: {}", inputFile, e); return false; } finally { if (app != null) { app.invoke("Quit", 0); app.safeRelease(); // 释放 ActiveXComponent 资源 } ComThread.Release(); } } private static boolean exToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KET.Application"); app.setProperty("Visible", new Variant(false)); app.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏 Dispatch excels = app.getProperty("Workbooks").toDispatch(); Dispatch excel = Dispatch.invoke(excels, "Open", Dispatch.Method, new Object[]{inputFile, new Variant(false), new Variant(false)}, new int[9]).toDispatch(); Dispatch.invoke(excel, "ExportAsFixedFormat", Dispatch.Method, new Object[]{new Variant(XL_TYPE_PDF), pdfFile}, new int[1]); Dispatch.call(excel, "Close", new Variant(false)); logger.info("Excel 文件转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("Excel 文件转换失败: {}", inputFile, e); return false; } finally { if (app != null) { app.invoke("Quit"); app.safeRelease(); // 释放 ActiveXComponent 资源 } ComThread.Release(); } } private static boolean pptToPDF(String inputFile, String pdfFile) { ActiveXComponent app = null; try { ComThread.InitSTA(); app = new ActiveXComponent("KWPP.Application"); Dispatch ppts = app.getProperty("Presentations").toDispatch(); Dispatch ppt = Dispatch.call(ppts, "Open", inputFile, true, false).toDispatch(); Dispatch.invoke(ppt, "SaveAs", Dispatch.Method, new Object[]{pdfFile, new Variant(PP_SAVE_AS_PDF)}, new int[1]); Dispatch.call(ppt, "Close"); logger.info("PPT 文件转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (Exception e) { logger.error("PPT 文件转换失败: {}", inputFile, e); return false; } finally { if (app != null) { app.invoke("Quit"); app.safeRelease(); // 释放 ActiveXComponent 资源 } ComThread.Release(); } } /** * 将图片转换为PDF * * @param inputFile 图片文件路径 * @param pdfFile 输出PDF文件路径 * @return 是否转换成功 */ public static boolean imageToPDF(String inputFile, String pdfFile) { try (PDDocument document = new PDDocument()) { PDPage page = new PDPage(PDRectangle.A4); // 创建一个A4页面 document.addPage(page); // 加载图片并创建一个PDF图像对象 File file = new File(inputFile); BufferedImage bufferedImage = ImageIO.read(file); PDImageXObject pdImage = LosslessFactory.createFromImage(document, bufferedImage); // 计算图片缩放比例以适应页面 float originalWidth = pdImage.getWidth(); float originalHeight = pdImage.getHeight(); float scaleX = PDRectangle.A4.getWidth() / originalWidth; float scaleY = PDRectangle.A4.getHeight() / originalHeight; float scale = Math.min(scaleX, scaleY); // 取较小的缩放比例以完全适应页面 // 开始在页面上绘制内容 try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { // 将图片绘制到页面上,居中并按比例缩放 float x = (PDRectangle.A4.getWidth() - originalWidth * scale) / 2; float y = (PDRectangle.A4.getHeight() - originalHeight * scale) / 2; contentStream.drawImage(pdImage, x, y, originalWidth * scale, originalHeight * scale); } // 保存PDF文件 document.save(pdfFile); logger.info("图片转换成功: {} -> {}", inputFile, pdfFile); return true; } catch (IOException e) { logger.error("图片转换失败: {}", inputFile, e); return false; } } }

本文作者:周得水

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!