Java 实现图片转 PDF(单张 / 多张合并)

在日常开发中,图片转 PDF 是非常常见的需求,比如证件照生成 PDF、多图片合并成一个 PDF 文件等。本文基于实际项目代码,详细讲解如何用 Java 实现单张图片转 PDF多张图片合并为单个 PDF,同时支持本地图片和网络图片(URL),并附上完整的代码、依赖配置和注意事项。

一、技术选型与环境说明

1. 核心依赖

本文实现图片转 PDF 的核心依赖是 Aspose.PDF for Java(处理 PDF 文档),辅助依赖包括 Hutool(文件操作)、Apache Commons Codec(Base64 编码)、PDFBox(IO 工具)等。

2. 环境要求

  • JDK 版本:JDK 8 及以上(兼容 JDK 11/17)
  • 构建工具:Maven/Gradle(本文以 Maven 为例)

二、Maven 依赖配置

pom.xml中引入以下依赖,覆盖核心功能所需的所有组件:

<!-- Aspose.PDF 核心依赖(图片转PDF核心) -->
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-pdf</artifactId>
    <version>24.1</version>
</dependency>

<!-- Hutool 工具类(文件操作简化) -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version>
</dependency>

<!-- Apache Commons Codec(Base64编码) -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>

<!-- Apache PDFBox(IO流工具) -->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.32</version>
</dependency>

<!-- 日志依赖(可选,与项目日志框架匹配) -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

三、核心功能实现

1. 工具类完整代码

封装PdfUtils工具类,包含单张图片转 PDF、多张图片合并 PDF、URL 图片转 PDF、PDF 转 Base64 等核心功能:

package org.dromara.common.utils.file.pdf;

import cn.hutool.core.io.FileUtil;
import com.aspose.pdf.Document;
import com.aspose.pdf.Image;
import com.aspose.pdf.Page;
import com.aspose.pdf.Rectangle;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.io.IOUtils;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

/**
 * PDF转化工具类
 * 支持:单张图片转PDF、多张图片合并PDF、URL图片转PDF、PDF文件转Base64
 * JDK版本:JDK 8+
 * 核心依赖:Aspose.PDF for Java
 */
public class PdfUtils {

    private static final Log log = LogFactory.getLog(PdfUtils.class);

    /**
     * 单个本地图片转PDF
     * @param file 本地图片文件
     * @return 生成的PDF文件(临时文件)
     */
    public static File imgToPdfFile(File file) {
        long startTime = System.currentTimeMillis();
        log.info("------------开始执行图片转PDF------------");
        FileInputStream imageStream = null;
        Document doc = null;
        try {
            String pdfName = file.getName();
            // 创建临时PDF文件(前缀img_,后缀pdf)
            File pdfPath = File.createTempFile("img_", "pdf");
            // 拼接最终PDF路径(临时文件+原图片名)
            String pdfFilePath = pdfPath + pdfName;
            
            // 1. 初始化PDF文档
            doc = new Document();
            // 2. 添加PDF页面并设置尺寸(A4:595x842)
            Page page = doc.getPages().add();
            page.setCropBox(new Rectangle(0, 0, 595, 842));
            
            // 3. 读取图片流并添加到PDF页面
            imageStream = new FileInputStream(file.getPath());
            Image image1 = new Image();
            page.getParagraphs().add(image1);
            image1.setImageStream(imageStream);
            
            // 4. 保存PDF文件
            doc.save(pdfFilePath);
            
            // 校验文件是否存在
            File mOutputPdfFile = new File(pdfFilePath);
            if (!mOutputPdfFile.exists()) {
                mOutputPdfFile.deleteOnExit();
                return null;
            }
            
            log.info("------------图片转PDF执行完成-----------耗时:" + (System.currentTimeMillis() - startTime));
            return mOutputPdfFile;
        } catch (IOException e) {
            log.error("图片转PDF失败:file:" + file.getPath() + ", error:" + e.getMessage());
        } finally {
            // 关闭流和文档,避免资源泄漏
            if (imageStream != null) {
                try {
                    imageStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (doc != null) {
                doc.close();
            }
        }
        return null;
    }

    /**
     * 网络图片(URL)转PDF
     * @param url 图片URL地址(http/https)
     * @return 生成的PDF文件
     */
    public static File imgToPdfFile(URL url) {
        try (InputStream inputStream = url.openStream();) {
            // 将网络图片保存为临时文件,再调用本地图片转PDF方法
            File tempFile = FileUtil.createTempFile();
            FileUtil.writeFromStream(inputStream, tempFile);
            return PdfUtils.imgToPdfFile(tempFile);
        } catch (Exception e) {
            throw new RuntimeException("网络图片转PDF失败:" + e.getMessage(), e);
        }
    }

    /**
     * 网络图片(URL字符串)转PDF
     * @param urlPath 图片URL字符串
     * @return 生成的PDF文件
     */
    public static File imgToPdfFile(String urlPath) {
        try {
            URL url = new URL(urlPath);
            // 校验URL协议(仅支持http/https)
            if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
                throw new RuntimeException("url地址必须是http/https类型地址");
            }
            return imgToPdfFile(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException("URL格式错误:" + urlPath, e);
        }
    }

    /**
     * PDF文件转Base64编码
     * @param file PDF文件
     * @return Base64字符串
     */
    public final static String encodeBase64String(File file) {
        try {
            return Base64.encodeBase64String(IOUtils.toByteArray(new FileInputStream(file)));
        } catch (IOException e) {
            throw new RuntimeException("PDF文件转Base64失败:" + e.getMessage(), e);
        }
    }

    /**
     * 多张本地图片合并为单个PDF(核心私有方法)
     * @param imageFiles 本地图片文件数组
     * @return 合并后的PDF文件
     */
    private static File mergeImagesToPdf(File[] imageFiles) {
        // 入参校验
        if (imageFiles == null || imageFiles.length == 0) {
            log.error("图片文件列表不能为空");
            return null;
        }
        
        long startTime = System.currentTimeMillis();
        log.info("------------开始执行多张图片转PDF------------,共" + imageFiles.length + "张图片");
        
        FileInputStream imageStream = null;
        Document doc = null;
        File pdfFile = null;
        
        try {
            // 创建合并后的临时PDF文件
            pdfFile = File.createTempFile("merged_img_", ".pdf");
            pdfFile.deleteOnExit(); // JVM退出时自动删除临时文件
            String pdfFilePath = pdfFile.getAbsolutePath();
            
            // 初始化PDF文档
            doc = new Document();
            
            // 遍历图片,每张图片生成一个PDF页面
            for (File imageFile : imageFiles) {
                if (imageFile == null || !imageFile.exists()) {
                    log.warn("图片文件无效,跳过:" + (imageFile == null ? "null" : imageFile.getPath()));
                    continue;
                }
                
                // 添加新页面并设置尺寸
                Page page = doc.getPages().add();
                page.setCropBox(new Rectangle(0, 0, 595, 842));
                
                // 读取图片并添加到页面
                imageStream = new FileInputStream(imageFile);
                Image image = new Image();
                page.getParagraphs().add(image);
                image.setImageStream(imageStream);
                
                // 关闭当前图片流,避免资源泄漏
                if (imageStream != null) {
                    imageStream.close();
                    imageStream = null;
                }
            }
            
            // 保存合并后的PDF
            if (doc.getPages().size() > 0) {
                doc.save(pdfFilePath);
            } else {
                // 无有效页面,删除空文件
                if (pdfFile.exists()) {
                    pdfFile.delete();
                }
                return null;
            }
            
            log.info("------------多张图片转PDF-----------耗时:" + (System.currentTimeMillis() - startTime));
            return pdfFile;
        } catch (IOException e) {
            // 异常时删除无效PDF文件
            if (pdfFile != null && pdfFile.exists()) {
                pdfFile.delete();
            }
            log.error("多张图片合并PDF失败:" + e.getMessage(), e);
            return null;
        } finally {
            // 关闭所有资源
            try {
                if (imageStream != null) {
                    imageStream.close();
                }
            } catch (IOException e) {
                log.error("关闭图片流失败", e);
            }
            try {
                if (doc != null) {
                    doc.close();
                }
            } catch (Exception e) {
                log.error("关闭PDF文档失败", e);
            }
        }
    }

    /**
     * 多张图片转PDF(支持合并/分开生成)
     * @param imageFiles 图片文件数组
     * @param isOverride true=合并为一个PDF,false=每张图片生成一个PDF
     * @return PDF文件数组
     */
    public static File[] mergeImagesToPdf(File[] imageFiles, Boolean isOverride) {
        if (isOverride) {
            // 合并为单个PDF
            File mergedPdf = mergeImagesToPdf(imageFiles);
            return mergedPdf != null ? new File[]{mergedPdf} : new File[0];
        }
        // 每张图片生成一个PDF
        File[] pdfFiles = new File[imageFiles.length];
        for (int i = 0; i < imageFiles.length; i++) {
            pdfFiles[i] = imgToPdfFile(imageFiles[i]);
        }
        return pdfFiles;
    }

    /**
     * 多张网络图片(URL字符串)转PDF
     * @param imageFiles 图片URL列表
     * @param isOverride true=合并为一个PDF,false=每张图片生成一个PDF
     * @return PDF文件数组
     */
    public static File[] mergeImagesToPdf(List<String> imageFiles, Boolean isOverride) {
        try {
            File[] files = new File[imageFiles.size()];
            for (int i = 0; i < imageFiles.size(); i++) {
                URL url = new URL(imageFiles.get(i));
                // 校验URL协议
                if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
                    throw new RuntimeException("url地址必须是http/https类型地址:" + imageFiles.get(i));
                }
                // 下载网络图片到临时文件
                try (InputStream inputStream = url.openStream();) {
                    File tempFile = FileUtil.createTempFile();
                    FileUtil.writeFromStream(inputStream, tempFile);
                    files[i] = tempFile;
                } catch (Exception e) {
                    throw new RuntimeException("下载图片失败:" + imageFiles.get(i), e);
                }
            }
            return mergeImagesToPdf(files, isOverride);
        } catch (MalformedURLException e) {
            throw new RuntimeException("URL格式错误", e);
        }
    }

    /**
     * 多张网络图片合并为单个PDF
     * @param imageFiles 图片URL列表
     * @return 合并后的PDF文件
     */
    public static File[] mergeImagesToPdf(List<String> imageFiles, Boolean isOverride) {
    //转化为File文件
    File tempFile = null;
    try {
        File[] files = new File[imageFiles.size()];
        for (int i = 0; i < imageFiles.size(); i++) {
            URL url = null;
            url = new URL(imageFiles.get(i));
            if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
                throw new RuntimeException("url地址必须是http类型地址");
            }
            try (InputStream inputStream = url.openStream();) {
                tempFile = FileUtil.createTempFile();
                FileUtil.writeFromStream(inputStream, tempFile);
                files[i] = tempFile;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return mergeImagesToPdf(files, isOverride);
    } catch (MalformedURLException e) {
        throw new RuntimeException(e);
    } finally {
        if (tempFile.exists()) {
            tempFile.delete();
        }
    }
}

2. 关键功能说明

方法 功能 核心逻辑
imgToPdfFile(File file) 本地图片转 PDF 创建临时 PDF 文件 → 初始化 PDF 文档 → 添加图片到 PDF 页面 → 保存文件
imgToPdfFile(String urlPath) URL 图片转 PDF 解析 URL → 下载图片到临时文件 → 调用本地图片转 PDF 方法
mergeImagesToPdf(File[] imageFiles) 多张图片合并 PDF 遍历图片 → 为每张图片创建 PDF 页面 → 合并保存为单个 PDF
encodeBase64String(File file) PDF 转 Base64 读取 PDF 文件字节流 → 进行 Base64 编码

四、使用示例

1. 单张本地图片转 PDF

public static void main(String[] args) {
    // 单张本地图片转PDF
    File imgFile = new File("D:/test/photo.jpg");
    File pdfFile = PdfUtils.imgToPdfFile(imgFile);
    System.out.println("生成的PDF路径:" + pdfFile.getAbsolutePath());
    
    // PDF转Base64
    String base64 = PdfUtils.encodeBase64String(pdfFile);
    System.out.println("PDF Base64编码:" + base64);
}

2. 多张网络图片合并为单个 PDF

public static void main(String[] args) {
    // 多张网络图片URL
    List<String> imgUrls = new ArrayList<>();
    imgUrls.add("https://example.com/img1.jpg");
    imgUrls.add("https://example.com/img2.png");
    
    // 合并为单个PDF
    File mergedPdf = PdfUtils.mergeImagesToPdf(imgUrls);
    System.out.println("合并后的PDF路径:" + mergedPdf.getAbsolutePath());
}

3. 多张图片(部分合并、部分分开)

public static void main(String[] args) {
    // 本地图片数组
    File[] imgFiles = new File[]{
        new File("D:/test/1.jpg"),
        new File("D:/test/2.png")
    };
    
    // false=每张图片生成一个PDF
    File[] pdfFiles = PdfUtils.mergeImagesToPdf(imgFiles, false);
    for (File pdf : pdfFiles) {
        System.out.println("生成的PDF路径:" + pdf.getAbsolutePath());
    }
}

五、注意事项

  1. 资源关闭:图片流、PDF 文档必须在 finally 块中关闭,避免内存泄漏和文件句柄占用。
  2. Aspose.PDF 授权:Aspose.PDF 免费版会有水印,生产环境需购买授权或替换为开源方案(如 iText、PDFBox)。
  3. URL 校验:网络图片 URL 需校验协议(仅支持 http/https),避免非法 URL 导致异常。
  4. 异常处理:代码中对 IO 异常、URL 格式异常做了基础处理,可根据业务需求扩展(如重试机制、告警通知)。

六、总结

本文基于 Aspose.PDF 实现了一套完整的图片转 PDF 解决方案,支持本地 / 网络图片、单张 / 多张合并,兼容 JDK 8 及以上版本。核心亮点:

  1. 封装性强:所有功能集中在PdfUtils工具类,调用简单;
  2. 资源安全:完善的流和文档关闭逻辑,避免资源泄漏;
  3. 灵活性高:支持合并 / 分开生成 PDF,满足不同业务场景;
  4. 兼容性好:支持 http/https 协议的网络图片,适配常见图片格式。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐