java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java批量生成pdf并压缩导出

Java实现批量pdf生成并批量压缩导出的方法

作者:张哇噻!

在Java开发中生成PDF文件是一项常见需求,尤其在报表生成、文档导出等场景中,这篇文章主要介绍了Java实现批量pdf生成并批量压缩导出的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

1.压缩使用的是 jdk 自带的压缩方法,生成 pdf 使用的是 itext工具需要引入 jar 包。

2.引入 itext 工具的 jar 包

<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>itext7-core</artifactId>
			<version>7.2.6</version> <!-- 确认最新稳定版 -->
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>itext-asian</artifactId>
			<version>5.2.0</version>
		</dependency>

3.实现过程,循环生成 pdf ,再进行压缩。下面是生成单张 pdf 方法

package com.example.springdemo;

import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.SolidBorder;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.HorizontalAlignment;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;
import com.itextpdf.kernel.colors.DeviceGray; // 导入颜色类

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class PdfTableExporter {

    public byte[] exportToPdf(InvoiceInfoRecordDto invoiceInfoRecordDto) throws IOException {
        ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
        // 1. 创建PDF写入器和文档(统一边距,避免内容偏移)
        PdfWriter writer = new PdfWriter(pdfBaos);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document doc = new Document(pdfDoc, PageSize.A4);
        try {
            doc.setMargins(20, 20, 20, 20); // 设置页面边距(上、右、下、左)
            doc.setHorizontalAlignment(HorizontalAlignment.CENTER);

            // 2. 加载中文字体
            PdfFont font = PdfFontFactory.createFont(
                    "STSong-Light",
                    "UniGB-UCS2-H",
                    PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED,
                    false
            );

            // -------------------------- 关键:用一个主表格整合所有内容 --------------------------
            // 主表格:1列(所有内容都放在这个表格里,避免“一块一块”)
            Table mainTable = new Table(1);
            mainTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占满页面宽度
            mainTable.setBorder(new SolidBorder(DeviceGray.BLACK, 1)); // 主表格外边框(黑色1px)
            mainTable.setSpacingRatio(0); // 消除单元格间距


            // 3. 标题栏(作为主表格的第一个单元格,无单独表格)
            Cell titleCell1 = new Cell()
                    .add(new Paragraph("XXXX有限公司"))
                    .setFont(font)
                    .setFontSize(14) // 标题字体放大
                    .setTextAlignment(TextAlignment.CENTER)
                    .setBold()
                    .setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)) // 单元格内边框
                    .setPadding(8); // 单元格内边距(避免文字贴边)
            mainTable.addCell(titleCell1);

            Cell titleCell2 = new Cell()
                    .add(new Paragraph("新能源公司增值税专用发票开具审批表"))
                    .setFont(font)
                    .setFontSize(16)
                    .setTextAlignment(TextAlignment.CENTER)
                    .setBold()
                    .setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f))
                    .setPadding(8);
            mainTable.addCell(titleCell2);


            // 4. 客户信息表格(作为主表格的一个单元格,消除独立表格分割)
            // 客户信息子表格:6列(按原比例)
            float[] clientColWidths = {2, 2, 1, 1, 1, 1};
            Table clientTable = new Table(clientColWidths);
            clientTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占满页面宽度
            clientTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
            clientTable.setSpacingRatio(0);

            // 客户信息表头
            clientTable.addCell(createBoldCell("客户名称", font));
            clientTable.addCell(createBoldCell("商品名称", font));
            clientTable.addCell(createBoldCell("规格/型号", font));
            clientTable.addCell(createBoldCell("数量", font));
            clientTable.addCell(createBoldCell("单价(含税)", font));
            clientTable.addCell(createBoldCell("金额(含税)", font));

            // 客户信息内容
            clientTable.addCell(createNormalCell("XXXX有限公司\n纳税人识别号:XXXX", font));
            clientTable.addCell(createNormalCell("液化天然气 (LNG)", font));
            clientTable.addCell(createNormalCell("kg", font));
            clientTable.addCell(createNormalCell("XXXX", font));
            clientTable.addCell(createNormalCell("XXXX", font));
            clientTable.addCell(createNormalCell("XXXX", font));

            // 总计行(跨5列)
            Cell totalCell = new Cell(1, 5)
                    .add(new Paragraph("总计"))
                    .setFont(font)
                    .setBold()
                    .setTextAlignment(TextAlignment.CENTER)
                    .setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f))
                    .setPadding(5);
            clientTable.addCell(totalCell);
            clientTable.addCell(createNormalCell("456.74", font));

            // 将客户信息子表格加入主表格
            mainTable.addCell(new Cell().add(clientTable).setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)).setPadding(0));


            // 5. 审核流程表格(同样作为主表格的单元格)
            float[] auditColWidths = {2, 4};
            Table auditTable = new Table(auditColWidths);
            auditTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占满页面宽度
            auditTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
            auditTable.setSpacingRatio(0);

            // 审核流程内容(循环添加,简化代码)
            String[][] auditData = {
                    {"站长审核", "XXXX\n"},
                    {"经营管理部审核", "审核人: XXXX\n"},
                    {"经营管理部门负责人审核", "审核人: XXXX\n"},
                    {"财务审核", "审核人: XXXX\n"},
                    {"财务分管领导审核", "审核人: XX\n"},
                    {"总经理审核", "审核人: XX\n"}
            };
            for (String[] data : auditData) {
                auditTable.addCell(createBoldCell(data[0], font));
                auditTable.addCell(createNormalCell(data[1], font));
            }

            // 将审核流程表格加入主表格
            mainTable.addCell(new Cell().add(auditTable).setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)).setPadding(0));


            // 6. 订单信息表格
            float[] orderColWidths = {2, 2, 1, 1, 1, 1};
            Table orderTable = new Table(orderColWidths);
            orderTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占满页面宽度
            orderTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
            orderTable.setSpacingRatio(0);

            // 订单信息表头
            orderTable.addCell(createBoldCell("订单号", font));
            orderTable.addCell(createBoldCell("加注站点", font));
            orderTable.addCell(createBoldCell("加注时间", font));
            orderTable.addCell(createBoldCell("加注量", font));
            orderTable.addCell(createBoldCell("支付金额", font));
            orderTable.addCell(createBoldCell("支付方式", font));

            // 订单信息内容
            orderTable.addCell(createNormalCell("XXXX", font));
            orderTable.addCell(createNormalCell("XXXX", font));
            orderTable.addCell(createNormalCell("2025-09-27 07:49:51", font));
            orderTable.addCell(createNormalCell("97.18", font));
            orderTable.addCell(createNormalCell("456.74", font));
            orderTable.addCell(createNormalCell("微信", font));

            // 将订单信息表格加入主表格
            mainTable.addCell(new Cell().add(orderTable).setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)).setPadding(0));


            // 7. 会员信息表格
            float[] memberColWidths = {2, 2, 2};
            Table memberTable = new Table(memberColWidths);
            memberTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占满页面宽度
            memberTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
            memberTable.setSpacingRatio(0);

            // 会员信息表头
            memberTable.addCell(createBoldCell("会员名称", font));
            memberTable.addCell(createBoldCell("支付时间", font));
            memberTable.addCell(createBoldCell("开票时间", font));

            // 会员信息内容
            memberTable.addCell(createNormalCell("XXXX", font));
            memberTable.addCell(createNormalCell("2025-09-27 07:50:28", font));
            memberTable.addCell(createNormalCell("2025-09-27 16:35:24", font));

            // 将会员信息表格加入主表格
            mainTable.addCell(new Cell().add(memberTable).setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)).setPadding(0));


            // 8. 将主表格加入文档(所有内容都在主表格里,视觉上是一个整体)
            doc.add(mainTable);
            doc.close();
            return pdfBaos.toByteArray();
        }finally {
            // 关闭流(不变)
            if (doc != null) doc.close();
            if (pdfDoc != null) pdfDoc.close();
            if (writer != null) writer.close();
            pdfBaos.close();
        }
    }

    // -------------------------- 工具方法:简化单元格创建(避免重复代码) --------------------------
    /** 创建加粗的单元格(表头用) */
    private Cell createBoldCell(String content, PdfFont font) {
        return new Cell()
                .add(new Paragraph(content))
                .setFont(font)
                .setBold()
                .setTextAlignment(TextAlignment.CENTER)
                .setVerticalAlignment(VerticalAlignment.MIDDLE)
                .setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f))
                .setPadding(5);
    }

    /** 创建普通单元格(内容用) */
    private Cell createNormalCell(String content, PdfFont font) {
        return new Cell()
                .add(new Paragraph(content))
                .setFont(font)
                .setTextAlignment(TextAlignment.LEFT)
                .setVerticalAlignment(VerticalAlignment.MIDDLE)
                .setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f))
                .setPadding(5);
    }

    public static void main(String[] args) throws IOException {
//        PdfTableExporter exporter = new PdfTableExporter();
//        System.out.println(exporter.exportToPdf("发票审批表.pdf"));
        //System.out.println("PDF 导出完成!文件路径:" + new java.io.File("发票审批表.pdf").getAbsolutePath());
        ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
        // 1. 创建PDF写入器和文档(统一边距,避免内容偏移)
        PdfWriter writer = new PdfWriter(pdfBaos);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document doc = new Document(pdfDoc, PageSize.A4);
        doc.close();
        // 关闭流(不变)
        if (doc != null) doc.close();
        if (pdfDoc != null) pdfDoc.close();
        if (writer != null) writer.close();
        pdfBaos.close();
    }
}

4.pdf 生成完调用 java 自带的 zipUtil 工具来进行压缩

package com.example.springdemo;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * ZIP 压缩工具类:批量将文件添加到压缩包
 */
public class ZipUtils {

    /**
     * 批量压缩文件到指定 ZIP 包
     * @param srcFilePaths 要压缩的文件路径数组(如多个 PDF 路径)
     * @param zipFilePath 生成的 ZIP 包路径(如 "发票审批表批量压缩.zip")
     * @throws IOException 压缩过程中的 IO 异常
     */
    /**
     * 最终修复:去掉 BufferedOutputStream,直接用 ZipOutputStream 写入,避免流嵌套冲突
     */
    public static byte[] compressPdfBytesToZipStream(java.util.List<PdfBytesDto> pdfBytesList) throws IOException {
        // 内存流:存储 ZIP 内容(不落地)
        ByteArrayOutputStream zipBaos = new ByteArrayOutputStream();
        try (ZipOutputStream zipOut = new ZipOutputStream(zipBaos)) {
            zipOut.setLevel(6); // 压缩级别(可选)

            // 遍历所有 PDF 字节流,写入 ZIP
            for (PdfBytesDto pdfBytes : pdfBytesList) {
                // 1. 创建 ZIP 条目(文件名)
                ZipEntry zipEntry = new ZipEntry(pdfBytes.getPdfFileName());
                zipOut.putNextEntry(zipEntry);

                // 2. 写入 PDF 字节流
                zipOut.write(pdfBytes.getPdfBytes());

                // 3. 关闭当前条目(避免内容混淆)
                zipOut.closeEntry();
            }

            // 4. 刷新流(确保所有内容写入内存)
            zipOut.flush();
            // 5. 返回 ZIP 字节流(toByteArray() 会复制流内容,关闭流不影响)
            return zipBaos.toByteArray();
        } finally {
            // 关闭内存流(不影响已返回的字节数组)
            zipBaos.close();
        }
    }

    /**
     * 简化方法:压缩单个文件到 ZIP 包
     */
    public static void compressSingleFileToZip(String srcFilePath, String zipFilePath) throws IOException {
        //compressFilesToZip(new String[]{srcFilePath}, zipFilePath);
    }


    // 在 PdfTableExporter 的 main 方法中(批量生成 PDF 后压缩)
    public static void main(String[] args) throws IOException, InterruptedException {
        // 1. 批量生成 3 个 PDF(确保每个 PDF 流已关闭)
        int batchCount = 3;

        PdfTableExporter exporter = new PdfTableExporter();

        List<PdfBytesDto> pdfBytesList = new ArrayList<>();

        for (int i = 0; i < batchCount; i++) {
            PdfBytesDto pdfBytesDto = new PdfBytesDto();
            String pdfPath = "发票审批表_" + (i + 1) + ".pdf";
            byte[] bytes = exporter.exportToPdf(null);// 生成单个 PDF(exportToPdf 中需确保 doc.close() 已调用)
            pdfBytesDto.setPdfFileName(pdfPath);
            pdfBytesDto.setPdfBytes(bytes);
            pdfBytesList.add(pdfBytesDto);
            System.out.println("单个 PDF 生成完成:" + pdfPath);
        }

        // 2. 所有 PDF 生成完成后,调用修复后的 ZipUtils 压缩
        String zipPath = "发票审批表批量压缩.zip";
        // 2. 调用改造后的压缩方法,生成 ZIP 字节流
        byte[] zipBytes = ZipUtils.compressPdfBytesToZipStream(pdfBytesList);

        // 3. 配置响应头,触发前端下载
        HttpHeaders headers = new HttpHeaders();
        // 处理中文文件名乱码(HTTP 响应头标准编码:ISO-8859-1)
        String zipFileName = "发票审批表批量压缩.zip";
        headers.setContentDispositionFormData("attachment", zipFileName); // 关键:触发下载弹窗
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 声明二进制流类型
        headers.setContentLength(zipBytes.length); // 告诉前端文件大小
        System.out.println(new ResponseEntity<>(zipBytes, headers, HttpStatus.OK));
        // 4. 返回 ZIP 字节流给前端
        //System.out.println("ZIP 压缩完成!路径:" + new File(zipPath).getAbsolutePath());

    }

}

5.注意事项:

(1)如果要和前端对接的话,要转成流直接响应给浏览器直接可以下载

(2)如果下载出来的文件损坏就是生成的 pdf 有问题,无法下载

(3)如果生成出来的 pdf 打开是乱码,有两种可能,第一种是字符编码规则不对,第二个是生成 pdf 选的字符串编码存在问题,或者服务器没有那种字体文件,也会出现下载乱码问题。

总结

到此这篇关于Java实现批量pdf生成并批量压缩导出的文章就介绍到这了,更多相关Java批量生成pdf并压缩导出内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文