PDF或Word转图片(多线程+aspose+函数式接口)

PDF或Word转图片(多线程+aspose+函数式接口)

业务需求

有个业务需要把用户上传的PDF或者Word文件转成图片,用于前端展示

实现思路

这里直接用aspose实现即可,由于这种转换是非常慢的,所以还需要用到多线程

具体实现
引入aspose依赖
复制代码
<!--aspose对应的word和pdf的依赖 -->
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-pdf</artifactId>
    <version>17.3.0</version>
</dependency>
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-words</artifactId>
    <version>24.1</version>
    <classifier>jdk17</classifier>
</dependency>

<!--File类转成MultipartFile类需要的依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.20</version>
</dependency>
核心类
复制代码
package com.smart.technology.utils;

import com.aspose.pdf.*;
import com.aspose.pdf.devices.PngDevice;
import com.aspose.pdf.devices.Resolution;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * PDF转图片工具类
 * 使用Aspose.PDF将PDF文档转换为图片
 * @author: daizhongliang
 * @date: 2025/10/28
 */
public class PDFToImageUtils {

    /**
     * 将PDF文档转换为图片
     * @param inputPath  PDF文档的输入路径
     * @param outputDir  输出图片的目录
     * @return           转换后的图片文件列表
     * @throws Exception 如果转换过程中发生错误
     */
    public static List<File> pdfToImg(String inputPath, String outputDir) throws Exception {
        List<File> imageFiles = new ArrayList<>();

        try {
            // 加载PDF文档
            Document pdfDocument = new Document(inputPath);
            System.out.println("文档页数: " + pdfDocument.getPages().size());

            // 创建输出目录
            File outputDirectory = new File(outputDir);
            if (!outputDirectory.exists()) {
                outputDirectory.mkdirs();
            }

            // 创建固定大小的线程池用于并行处理
            int processors = Runtime.getRuntime().availableProcessors();
            ExecutorService executor = Executors.newFixedThreadPool(processors);
            
            // 创建CompletableFuture列表用于并行处理每一页
            List<CompletableFuture<File>> futures = new ArrayList<>();
            
            // 并行处理每一页
            for (int i = 1; i <= pdfDocument.getPages().size(); i++) {
                final int pageIndex = i;
                CompletableFuture<File> future = CompletableFuture.supplyAsync(() -> {
                    try {
                        // 获取当前页
                        Page page = pdfDocument.getPages().get_Item(pageIndex);
                        
                        // 设置图片保存选项
                        Resolution resolution = new Resolution(300); // 设置分辨率
                        PngDevice pngDevice = new PngDevice(resolution);
                        
                        // 设置渲染选项以提高图片质量
                        RenderingOptions options = new RenderingOptions();
                        options.setUseNewImagingEngine(true);
                        pngDevice.setRenderingOptions(options);
                        
                        // 生成输出文件名
                        String outputPath = outputDir + "page_" + pageIndex + ".png";
                        
                        // 保存为图片
                        pngDevice.process(page, outputPath);

                        File imageFile = new File(outputPath);
                        System.out.println("已生成图片: " + outputPath);
                        return imageFile;
                    } catch (Exception e) {
                        throw new RuntimeException("页面转换失败: " + e.getMessage(), e);
                    }
                }, executor);
                
                futures.add(future);
            }
            
            // 等待所有任务完成
            CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
            allFutures.join();
            
            // 收集结果
            for (CompletableFuture<File> future : futures) {
                imageFiles.add(future.get());
            }
            
            // 关闭线程池
            executor.shutdown();
            
            return imageFiles;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 测试main方法:将本地PDF文档转换为多张图片
     */
    public static void main(String[] args) {
        try {
            // 输入PDF文件路径
            String inputPath = "D:\\test\\pdf\\sample.pdf";
            // 输出图片目录
            String outputDir = "D:\\test\\pdf\\output_images\\";

            System.out.println("开始转换PDF文档到图片...");
            System.out.println("输入文件: " + inputPath);
            System.out.println("输出目录: " + outputDir);

            // 执行转换
            List<File> imageFiles = pdfToImg(inputPath, outputDir);

            System.out.println("转换完成!");
            System.out.println("共生成 " + imageFiles.size() + " 张图片");

        } catch (Exception e) {
            System.err.println("转换失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

package com.smart.technology.utils;

import com.aspose.words.Document;
import com.aspose.words.ImageSaveOptions;
import com.aspose.words.PageSet;
import com.aspose.words.SaveFormat;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Word转图片工具类
 * 使用Aspose.Words将Word文档转换为图片
 * @author: daizhongliang
 * @date: 2025/10/28
 */
public class WordToImageUtils {

    /**
     * 将Word文档转换为图片
     * @param inputPath  Word文档的输入路径
     * @param outputDir  输出图片的目录
     * @return           转换后的图片文件列表
     * @throws Exception 如果转换过程中发生错误
     */
    public static List<File> wordToImg(String inputPath, String outputDir) throws Exception {
        List<File> imageFiles = new ArrayList<>();

        try {
            // 加载Word文档
            Document doc = new Document(inputPath);
            System.out.println("文档页数: " + doc.getPageCount());

            // 创建输出目录
            File outputDirectory = new File(outputDir);
            if (!outputDirectory.exists()) {
                outputDirectory.mkdirs();
            }

            // 创建固定大小的线程池用于并行处理
            int processors = Runtime.getRuntime().availableProcessors();
            ExecutorService executor = Executors.newFixedThreadPool(processors);
            
            // 创建CompletableFuture列表用于并行处理每一页
            List<CompletableFuture<File>> futures = new ArrayList<>();
            
            // 并行处理每一页
            for (int i = 0; i < doc.getPageCount(); i++) {
                final int pageIndex = i;
                CompletableFuture<File> future = CompletableFuture.supplyAsync(() -> {
                    try {
                        ImageSaveOptions options = new ImageSaveOptions(SaveFormat.PNG);
                        options.setPrettyFormat(true);
                        options.setUseAntiAliasing(true);
                        options.setUseHighQualityRendering(true);
                        options.setPageSet(new PageSet(pageIndex)); // 设置当前页

                        // 生成输出文件名
                        String outputPath = outputDir + "page_" + (pageIndex + 1) + ".png";
                        doc.save(outputPath, options);

                        File imageFile = new File(outputPath);
                        System.out.println("已生成图片: " + outputPath);
                        return imageFile;
                    } catch (Exception e) {
                        throw new RuntimeException("页面转换失败: " + e.getMessage(), e);
                    }
                }, executor);
                
                futures.add(future);
            }
            
            // 等待所有任务完成
            CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
            allFutures.join();
            
            // 收集结果
            for (CompletableFuture<File> future : futures) {
                imageFiles.add(future.get());
            }
            
            // 关闭线程池
            executor.shutdown();
            
            return imageFiles;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 测试main方法:将本地Word文档转换为多张图片
     */
    public static void main(String[] args) {
        try {
            // 输入Word文件路径
            String inputPath = "D:\\test\\1.docx";
            // 输出图片目录
            String outputDir = "D:\\test\\output_images\\";

            System.out.println("开始转换Word文档到图片...");
            System.out.println("输入文件: " + inputPath);
            System.out.println("输出目录: " + outputDir);

            // 执行转换
            List<File> imageFiles = wordToImg(inputPath, outputDir);

            System.out.println("转换完成!");
            System.out.println("共生成 " + imageFiles.size() + " 张图片");

        } catch (Exception e) {
            System.err.println("转换失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
线程池配置类
复制代码
package com.smart.technology.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步线程池配置类
 * 用于优化文件转换等耗时操作的性能
 */
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig {

    /**
     * 文件转换专用线程池
     * 核心线程数: 10
     * 最大线程数: 20
     * 队列容量: 100
     * 线程空闲时间: 60秒
     * 拒绝策略: 调用者运行策略
     *
     * @return Executor 线程池执行器
     */
    @Bean("fileConvertTaskExecutor")
    public Executor fileConvertTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int processors = Runtime.getRuntime().availableProcessors();
        // 核心线程数
        executor.setCorePoolSize(processors);
        // 最大线程数
        executor.setMaxPoolSize(processors * 2);
        // 队列容量
        executor.setQueueCapacity(100);
        // 线程空闲时间
        executor.setKeepAliveSeconds(60);
        // 线程名称前缀
        executor.setThreadNamePrefix("file-convert-");
        // 拒绝策略: 当线程池达到最大容量且队列已满时,新任务将在调用者线程中执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化线程池
        executor.initialize();
        return executor;
    }
}
业务层具体引用
复制代码
@Autowired
private Executor fileConvertTaskExecutor;

/**
 * 将Word/PDF文件转换为图片并上传
 * @param file 上传的文件
 * @param packageId 课程包ID
 * @return 转换结果,包含原始文件URL和图片列表
 */
@Override
public Result<FileImagesUploadDTO> uploadWord2Img(MultipartFile file, Long packageId) {
    // 验证参数
    if (file == null || file.isEmpty()) {
        return Result.fail("文件不能为空");
    }
    if (packageId == null) {
        return Result.fail("课程包ID不能为空");
    }
    // 获取文件后缀
    String originalFilename = file.getOriginalFilename();
    if (originalFilename == null) {
        return Result.fail("文件名不能为空");
    }
    // 获取文件扩展名
    String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
    // word或者pdf目录名称
    String dirName = "";
    // word或者pdf转换后的图片目录名称
    String dirImagesName = "";
    if (fileExtension.contains(".doc") || fileExtension.contains(".docx")) {
        dirName = "Word";
        dirImagesName = "WordImage";
    } else if (fileExtension.contains(".pdf")) {
        dirName = "PDF";
        dirImagesName = "PDFImage";
    } else {
        return Result.fail("不支持的文件类型");
    }
    
    String fileUUID = String.valueOf(UUID.randomUUID());
    // 生成唯一文件名
    String fileName = fileUUID + fileExtension;
    // 构建文件路径
    String filePath = String.format("/Course/default/%d/%s/", packageId, dirName);
    // 1、上传word或pdf文件到COS云存储
    FileUploadDTO fileInfo = cosService.uploadFile(file, filePath, fileName);
    
    try {
        // 2、根据文件类型调用对应的转换方法
        List<String> imageUrls;
        if ("Word".equals(dirName)) {
            imageUrls = convertAndUploadImages(file, packageId, fileUUID, dirName, dirImagesName, 
                (tempFilePath, tempImagePath) -> WordToImageUtils.wordToImg(tempFilePath, tempImagePath));
            log.info("Word文件转换为图片并上传完成,共{}张图片", imageUrls.size());
        } else if ("PDF".equals(dirName)) {
            imageUrls = convertAndUploadImages(file, packageId, fileUUID, dirName, dirImagesName, 
                (tempFilePath, tempImagePath) -> PDFToImageUtils.pdfToImg(tempFilePath, tempImagePath));
            log.info("PDF文件转换为图片并上传完成,共{}张图片", imageUrls.size());
        } else {
            return Result.fail("不支持的文件类型");
        }
        
        // 创建返回对象
        FileImagesUploadDTO result = new FileImagesUploadDTO();
        result.setFileUrl(fileInfo.getFilePath());
        result.setFileImages(imageUrls);
        
        return Result.success(result);
    } catch (Exception e) {
        throw new RuntimeException("文件转换为图片失败: " + e.getMessage(), e);
    }
}

/**
 * 函数接口,用于定义文件转图片的转换方法
 */
@FunctionalInterface
private interface ImageConverter {
    List<File> convert(String sourcePath, String targetPath) throws Exception;
}

/**
 * 通用的文件转换和图片上传方法
 * @param file 原始文件
 * @param packageId 课程包ID
 * @param fileUUID 文件UUID
 * @param dirName 目录名称
 * @param dirImagesName 图片目录名称
 * @param converter 转换接口实现
 * @return 图片URL列表
 * @throws Exception 转换或上传过程中的异常
 */
private List<String> convertAndUploadImages(MultipartFile file, Long packageId, String fileUUID, 
                                         String dirName, String dirImagesName, ImageConverter converter) throws Exception {
    // 创建临时目录
    String tempDir = System.getProperty("java.io.tmpdir") + dirName + "ToImages" + File.separator + System.currentTimeMillis();
    File tempDirFile = new File(tempDir);
    tempDirFile.mkdirs();
    
    try {
        // 将上传的文件保存到临时路径
        String tempFilePath = tempDir + File.separator + file.getOriginalFilename();
        try (FileOutputStream fos = new FileOutputStream(tempFilePath)) {
            IOUtils.copy(file.getInputStream(), fos);
        }
        
        // 临时存储转换后图片的路径
        String tempImagePath = tempDir + File.separator + dirImagesName;
        
        // 调用转换器将文件转换为图片(异步并行处理)
        List<File> imageFiles = convertImagesAsync(tempFilePath, tempImagePath, converter);
        
        // 上传转换后的图片到COS(异步并行上传)
        List<String> imageUrls = uploadImagesAsync(imageFiles, packageId, fileUUID, dirImagesName);
        
        return imageUrls;
    } finally {
        // 清理临时目录
        tempDirFile.delete();
    }
}

/**
 * 异步并行转换文件为图片
 * @param sourcePath 源文件路径
 * @param targetPath 目标图片路径
 * @param converter 转换器
 * @return 图片文件列表
 * @throws Exception 转换过程中的异常
 */
private List<File> convertImagesAsync(String sourcePath, String targetPath, ImageConverter converter) throws Exception {
    // 调用转换器将文件转换为图片
    return converter.convert(sourcePath, targetPath);
}

/**
 * 异步并行上传图片到COS
 * @param imageFiles 图片文件列表
 * @param packageId 课程包ID
 * @param fileUUID 文件UUID
 * @param dirImagesName 图片目录名称
 * @return 图片URL列表
 * @throws Exception 上传过程中的异常
 */
private List<String> uploadImagesAsync(List<File> imageFiles, Long packageId, String fileUUID, String dirImagesName) throws Exception {
    // 上传转换后的图片到COS
    String imageDirPath = String.format("/Course/default/%d/%s/%s", packageId, dirImagesName, fileUUID);
    List<String> imageUrls = new ArrayList<>();
    
    // 创建CompletableFuture列表用于并行上传
    List<CompletableFuture<FileUploadDTO>> futures = new ArrayList<>();
    
    // 遍历图片文件列表,异步上传到COS
    int imageIndex = 1;
    for (File imageFile : imageFiles) {
        final int index = imageIndex;
        CompletableFuture<FileUploadDTO> future = CompletableFuture.supplyAsync(() -> {
            try {
                // 生成自定义文件名:uuid_1格式
                String customFileName = UUID.randomUUID() + "_" + index + ".png";
                
                // 将File对象转换为MultipartFile
                MultipartFile multipartImageFile = new MockMultipartFile(
                        customFileName,
                        customFileName,
                        "image/png",
                        Files.newInputStream(imageFile.toPath())
                );
                
                // 上传到COS,使用指定的文件名
                return cosService.uploadFile(multipartImageFile, imageDirPath, customFileName);
            } catch (Exception e) {
                log.error("图片上传失败: {}", imageFile.getName(), e);
                throw new RuntimeException("图片上传失败: " + e.getMessage(), e);
            }
        }, fileConvertTaskExecutor);
        
        futures.add(future);
        imageIndex++;
    }
    
    // 等待所有上传任务完成
    CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
    allFutures.join();
    
    // 收集上传结果
    for (CompletableFuture<FileUploadDTO> future : futures) {
        FileUploadDTO imageInfo = future.get();
        imageUrls.add(imageInfo.getFilePath());
        log.info("图片上传成功: {}, URL: {}", imageInfo.getFileName(), imageInfo.getFilePath());
    }
    
    return imageUrls;
}

骚戴解析:上面的逻辑其实很简单,首先把用户上传的pdf或者word上传到腾讯云cos云存储,然后把上传的pdf或者word文件复制到一个临时目录,再临时目录建立一个子目录用于存储转换后的图片,调用上面核心类的工具类去转换pdf或者word,把转换的List<File>转成MultipartFile然后调用腾讯云cos云存储的上传接口把转换后的图片上传到腾讯云cos云存储的桶上面去,最后把临时文件目录给删除掉。中间用了高级的东西,函数式接口来解耦和复用,因为pdf和word的整体逻辑其实差不多,只是调用的工具类不一样同时上传后的目录不一样而已

相关推荐
热心网友俣先生5 分钟前
2026年第二十三届五一数学建模竞赛C题超详细解题思路+各问题可用模型推荐+部分模型结果展示
c语言·开发语言·数学建模
01漫游者10 分钟前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript
GottdesKrieges11 分钟前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU11 分钟前
Java高级开发进阶教程之系列
java·开发语言
leo825...14 分钟前
Claude Code Skills 清单(本地)
java·python·ai编程
csbysj202017 分钟前
SQL NULL 函数详解
开发语言
其实防守也摸鱼20 分钟前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp21 分钟前
Java进阶——如何查看Java字节码
java·开发语言
We་ct1 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
身如柳絮随风扬1 小时前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务