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的整体逻辑其实差不多,只是调用的工具类不一样同时上传后的目录不一样而已

相关推荐
姓蔡小朋友3 小时前
SpringDataRedis
java·开发语言·redis
CodeCraft Studio3 小时前
国产化Excel处理控件Spire.XLS教程:如何使用 Java 将 TXT 文本转换为 Excel 表格
java·word·excel·spire·文档格式转换·txt转excel
Predestination王瀞潞3 小时前
Python3:Eighth 函数
开发语言·python
朝新_3 小时前
【SpringBoot】玩转 Spring Boot 日志:级别划分、持久化、格式配置及 Lombok 简化使用
java·spring boot·笔记·后端·spring·javaee
m0_748248023 小时前
Spring设计模式刨根问底
java·spring·设计模式
喝杯牛奶丶3 小时前
MySQL隔离级别:大厂为何偏爱RC?
java·数据库·mysql·面试
一 乐3 小时前
二手车销售|汽车销售|基于SprinBoot+vue的二手车交易系统(源码+数据库+文档)
java·前端·数据库·vue.js·后端·汽车
夜晚中的人海3 小时前
【C++】分治-快速排序算法习题
开发语言·c++·排序算法
爱编程的鱼4 小时前
想学编程作为今后的工作技能,学哪种语言适用性更强?
开发语言·算法·c#·bug