Java實(shí)現(xiàn)批量pdf生成并批量壓縮導(dǎo)出的方法
1.壓縮使用的是 jdk 自帶的壓縮方法,生成 pdf 使用的是 itext工具需要引入 jar 包。
2.引入 itext 工具的 jar 包
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.6</version> <!-- 確認(rèn)最新穩(wěn)定版 --> <type>pom</type> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency>
3.實(shí)現(xiàn)過(guò)程,循環(huán)生成 pdf ,再進(jìn)行壓縮。下面是生成單張 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; // 導(dǎo)入顏色類(lèi)
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class PdfTableExporter {
public byte[] exportToPdf(InvoiceInfoRecordDto invoiceInfoRecordDto) throws IOException {
ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
// 1. 創(chuàng)建PDF寫(xiě)入器和文檔(統(tǒng)一邊距,避免內(nèi)容偏移)
PdfWriter writer = new PdfWriter(pdfBaos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc, PageSize.A4);
try {
doc.setMargins(20, 20, 20, 20); // 設(shè)置頁(yè)面邊距(上、右、下、左)
doc.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 2. 加載中文字體
PdfFont font = PdfFontFactory.createFont(
"STSong-Light",
"UniGB-UCS2-H",
PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED,
false
);
// -------------------------- 關(guān)鍵:用一個(gè)主表格整合所有內(nèi)容 --------------------------
// 主表格:1列(所有內(nèi)容都放在這個(gè)表格里,避免“一塊一塊”)
Table mainTable = new Table(1);
mainTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占滿頁(yè)面寬度
mainTable.setBorder(new SolidBorder(DeviceGray.BLACK, 1)); // 主表格外邊框(黑色1px)
mainTable.setSpacingRatio(0); // 消除單元格間距
// 3. 標(biāo)題欄(作為主表格的第一個(gè)單元格,無(wú)單獨(dú)表格)
Cell titleCell1 = new Cell()
.add(new Paragraph("XXXX有限公司"))
.setFont(font)
.setFontSize(14) // 標(biāo)題字體放大
.setTextAlignment(TextAlignment.CENTER)
.setBold()
.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)) // 單元格內(nèi)邊框
.setPadding(8); // 單元格內(nèi)邊距(避免文字貼邊)
mainTable.addCell(titleCell1);
Cell titleCell2 = new Cell()
.add(new Paragraph("新能源公司增值稅專(zhuān)用發(fā)票開(kāi)具審批表"))
.setFont(font)
.setFontSize(16)
.setTextAlignment(TextAlignment.CENTER)
.setBold()
.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f))
.setPadding(8);
mainTable.addCell(titleCell2);
// 4. 客戶信息表格(作為主表格的一個(gè)單元格,消除獨(dú)立表格分割)
// 客戶信息子表格:6列(按原比例)
float[] clientColWidths = {2, 2, 1, 1, 1, 1};
Table clientTable = new Table(clientColWidths);
clientTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占滿頁(yè)面寬度
clientTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
clientTable.setSpacingRatio(0);
// 客戶信息表頭
clientTable.addCell(createBoldCell("客戶名稱", font));
clientTable.addCell(createBoldCell("商品名稱", font));
clientTable.addCell(createBoldCell("規(guī)格/型號(hào)", font));
clientTable.addCell(createBoldCell("數(shù)量", font));
clientTable.addCell(createBoldCell("單價(jià)(含稅)", font));
clientTable.addCell(createBoldCell("金額(含稅)", font));
// 客戶信息內(nèi)容
clientTable.addCell(createNormalCell("XXXX有限公司\n納稅人識(shí)別號(hào):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));
// 總計(jì)行(跨5列)
Cell totalCell = new Cell(1, 5)
.add(new Paragraph("總計(jì)"))
.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()); // 占滿頁(yè)面寬度
auditTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
auditTable.setSpacingRatio(0);
// 審核流程內(nèi)容(循環(huán)添加,簡(jiǎn)化代碼)
String[][] auditData = {
{"站長(zhǎng)審核", "XXXX\n"},
{"經(jīng)營(yíng)管理部審核", "審核人: XXXX\n"},
{"經(jīng)營(yíng)管理部門(mén)負(fù)責(zé)人審核", "審核人: XXXX\n"},
{"財(cái)務(wù)審核", "審核人: XXXX\n"},
{"財(cái)務(wù)分管領(lǐng)導(dǎo)審核", "審核人: XX\n"},
{"總經(jīng)理審核", "審核人: 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()); // 占滿頁(yè)面寬度
orderTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
orderTable.setSpacingRatio(0);
// 訂單信息表頭
orderTable.addCell(createBoldCell("訂單號(hào)", font));
orderTable.addCell(createBoldCell("加注站點(diǎn)", font));
orderTable.addCell(createBoldCell("加注時(shí)間", font));
orderTable.addCell(createBoldCell("加注量", font));
orderTable.addCell(createBoldCell("支付金額", font));
orderTable.addCell(createBoldCell("支付方式", font));
// 訂單信息內(nèi)容
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. 會(huì)員信息表格
float[] memberColWidths = {2, 2, 2};
Table memberTable = new Table(memberColWidths);
memberTable.setWidth(PageSize.A4.getWidth() - doc.getLeftMargin() - doc.getRightMargin()); // 占滿頁(yè)面寬度
memberTable.setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f));
memberTable.setSpacingRatio(0);
// 會(huì)員信息表頭
memberTable.addCell(createBoldCell("會(huì)員名稱", font));
memberTable.addCell(createBoldCell("支付時(shí)間", font));
memberTable.addCell(createBoldCell("開(kāi)票時(shí)間", font));
// 會(huì)員信息內(nèi)容
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));
// 將會(huì)員信息表格加入主表格
mainTable.addCell(new Cell().add(memberTable).setBorder(new SolidBorder(DeviceGray.BLACK, 0.5f)).setPadding(0));
// 8. 將主表格加入文檔(所有內(nèi)容都在主表格里,視覺(jué)上是一個(gè)整體)
doc.add(mainTable);
doc.close();
return pdfBaos.toByteArray();
}finally {
// 關(guān)閉流(不變)
if (doc != null) doc.close();
if (pdfDoc != null) pdfDoc.close();
if (writer != null) writer.close();
pdfBaos.close();
}
}
// -------------------------- 工具方法:簡(jiǎn)化單元格創(chuàng)建(避免重復(fù)代碼) --------------------------
/** 創(chuàng)建加粗的單元格(表頭用) */
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);
}
/** 創(chuàng)建普通單元格(內(nèi)容用) */
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("發(fā)票審批表.pdf"));
//System.out.println("PDF 導(dǎo)出完成!文件路徑:" + new java.io.File("發(fā)票審批表.pdf").getAbsolutePath());
ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
// 1. 創(chuàng)建PDF寫(xiě)入器和文檔(統(tǒng)一邊距,避免內(nèi)容偏移)
PdfWriter writer = new PdfWriter(pdfBaos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc, PageSize.A4);
doc.close();
// 關(guān)閉流(不變)
if (doc != null) doc.close();
if (pdfDoc != null) pdfDoc.close();
if (writer != null) writer.close();
pdfBaos.close();
}
}4.pdf 生成完調(diào)用 java 自帶的 zipUtil 工具來(lái)進(jìn)行壓縮
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 壓縮工具類(lèi):批量將文件添加到壓縮包
*/
public class ZipUtils {
/**
* 批量壓縮文件到指定 ZIP 包
* @param srcFilePaths 要壓縮的文件路徑數(shù)組(如多個(gè) PDF 路徑)
* @param zipFilePath 生成的 ZIP 包路徑(如 "發(fā)票審批表批量壓縮.zip")
* @throws IOException 壓縮過(guò)程中的 IO 異常
*/
/**
* 最終修復(fù):去掉 BufferedOutputStream,直接用 ZipOutputStream 寫(xiě)入,避免流嵌套沖突
*/
public static byte[] compressPdfBytesToZipStream(java.util.List<PdfBytesDto> pdfBytesList) throws IOException {
// 內(nèi)存流:存儲(chǔ) ZIP 內(nèi)容(不落地)
ByteArrayOutputStream zipBaos = new ByteArrayOutputStream();
try (ZipOutputStream zipOut = new ZipOutputStream(zipBaos)) {
zipOut.setLevel(6); // 壓縮級(jí)別(可選)
// 遍歷所有 PDF 字節(jié)流,寫(xiě)入 ZIP
for (PdfBytesDto pdfBytes : pdfBytesList) {
// 1. 創(chuàng)建 ZIP 條目(文件名)
ZipEntry zipEntry = new ZipEntry(pdfBytes.getPdfFileName());
zipOut.putNextEntry(zipEntry);
// 2. 寫(xiě)入 PDF 字節(jié)流
zipOut.write(pdfBytes.getPdfBytes());
// 3. 關(guān)閉當(dāng)前條目(避免內(nèi)容混淆)
zipOut.closeEntry();
}
// 4. 刷新流(確保所有內(nèi)容寫(xiě)入內(nèi)存)
zipOut.flush();
// 5. 返回 ZIP 字節(jié)流(toByteArray() 會(huì)復(fù)制流內(nèi)容,關(guān)閉流不影響)
return zipBaos.toByteArray();
} finally {
// 關(guān)閉內(nèi)存流(不影響已返回的字節(jié)數(shù)組)
zipBaos.close();
}
}
/**
* 簡(jiǎn)化方法:壓縮單個(gè)文件到 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 個(gè) PDF(確保每個(gè) PDF 流已關(guān)閉)
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 = "發(fā)票審批表_" + (i + 1) + ".pdf";
byte[] bytes = exporter.exportToPdf(null);// 生成單個(gè) PDF(exportToPdf 中需確保 doc.close() 已調(diào)用)
pdfBytesDto.setPdfFileName(pdfPath);
pdfBytesDto.setPdfBytes(bytes);
pdfBytesList.add(pdfBytesDto);
System.out.println("單個(gè) PDF 生成完成:" + pdfPath);
}
// 2. 所有 PDF 生成完成后,調(diào)用修復(fù)后的 ZipUtils 壓縮
String zipPath = "發(fā)票審批表批量壓縮.zip";
// 2. 調(diào)用改造后的壓縮方法,生成 ZIP 字節(jié)流
byte[] zipBytes = ZipUtils.compressPdfBytesToZipStream(pdfBytesList);
// 3. 配置響應(yīng)頭,觸發(fā)前端下載
HttpHeaders headers = new HttpHeaders();
// 處理中文文件名亂碼(HTTP 響應(yīng)頭標(biāo)準(zhǔn)編碼:ISO-8859-1)
String zipFileName = "發(fā)票審批表批量壓縮.zip";
headers.setContentDispositionFormData("attachment", zipFileName); // 關(guān)鍵:觸發(fā)下載彈窗
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 聲明二進(jìn)制流類(lèi)型
headers.setContentLength(zipBytes.length); // 告訴前端文件大小
System.out.println(new ResponseEntity<>(zipBytes, headers, HttpStatus.OK));
// 4. 返回 ZIP 字節(jié)流給前端
//System.out.println("ZIP 壓縮完成!路徑:" + new File(zipPath).getAbsolutePath());
}
}5.注意事項(xiàng):
(1)如果要和前端對(duì)接的話,要轉(zhuǎn)成流直接響應(yīng)給瀏覽器直接可以下載
(2)如果下載出來(lái)的文件損壞就是生成的 pdf 有問(wèn)題,無(wú)法下載
(3)如果生成出來(lái)的 pdf 打開(kāi)是亂碼,有兩種可能,第一種是字符編碼規(guī)則不對(duì),第二個(gè)是生成 pdf 選的字符串編碼存在問(wèn)題,或者服務(wù)器沒(méi)有那種字體文件,也會(huì)出現(xiàn)下載亂碼問(wèn)題。
總結(jié)
到此這篇關(guān)于Java實(shí)現(xiàn)批量pdf生成并批量壓縮導(dǎo)出的文章就介紹到這了,更多相關(guān)Java批量生成pdf并壓縮導(dǎo)出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(23)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07
java RocketMQ快速入門(mén)基礎(chǔ)知識(shí)
這篇文章主要介紹了java RocketMQ快速入門(mén)基礎(chǔ)知識(shí),所以RocketMQ是站在巨人的肩膀上(kafka),又對(duì)其進(jìn)行了優(yōu)化讓其更滿足互聯(lián)網(wǎng)公司的特點(diǎn)。它是純Java開(kāi)發(fā),具有高吞吐量、高可用性、適合大規(guī)模分布式系統(tǒng)應(yīng)用的特點(diǎn)。,需要的朋友可以參考下2019-06-06
Java 并發(fā)編程創(chuàng)建線程的四種方式示例詳解
本文對(duì)比Java創(chuàng)建線程的四種方式:繼承Thread類(lèi)(單繼承限制)、實(shí)現(xiàn)Runnable接口(靈活推薦)、實(shí)現(xiàn)Callable接口(支持返回值)、使用線程池(最佳實(shí)踐,資源管理高效),推薦優(yōu)先使用線程池提升系統(tǒng)穩(wěn)定性與資源利用率,感興趣的朋友一起看看吧2025-09-09
springboot整合flowable框架入門(mén)步驟
最近工作中有用到工作流的開(kāi)發(fā),引入了flowable工作流框架,在此記錄一下springboot整合flowable工作流框架的過(guò)程,感興趣的朋友一起看看吧2022-04-04
SpringBoot集成quartz實(shí)現(xiàn)定時(shí)任務(wù)詳解
最為常用定時(shí)任務(wù)框架是Quartz,并且Spring也集成了Quartz的框架,Quartz不僅支持單實(shí)例方式還支持分布式方式。本文主要介紹Quartz,基礎(chǔ)的Quartz的集成案例本,以及實(shí)現(xiàn)基于數(shù)據(jù)庫(kù)的分布式任務(wù)管理和控制job生命周期2022-08-08
Spring?Cloud?Gateway?整合?knife4j?聚合接口文檔功能
這篇文章主要介紹了Spring?Cloud?Gateway?整合?knife4j?聚合接口文檔的相關(guān)知識(shí),我們可以基于?Spring?Cloud?Gateway?網(wǎng)關(guān)?+?nacos?+?knife4j?對(duì)所有微服務(wù)項(xiàng)目的接口文檔進(jìn)行聚合,從而實(shí)現(xiàn)我們想要的文檔管理功能,需要的朋友可以參考下2022-02-02
Java實(shí)現(xiàn)簡(jiǎn)單文字驗(yàn)證碼以及人機(jī)驗(yàn)證
人機(jī)驗(yàn)證技術(shù)的發(fā)展也在不斷進(jìn)化,從最初的簡(jiǎn)單驗(yàn)證碼到現(xiàn)在的人工智能驅(qū)動(dòng)的高級(jí)驗(yàn)證系統(tǒng),下面這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)單文字驗(yàn)證碼以及人機(jī)驗(yàn)證的相關(guān)資料,需要的朋友可以參考下2025-04-04
如何解決springboot數(shù)據(jù)庫(kù)查詢時(shí)出現(xiàn)的時(shí)區(qū)差異問(wèn)題
這篇文章主要介紹了如何解決springboot數(shù)據(jù)庫(kù)查詢時(shí)出現(xiàn)的時(shí)區(qū)差異問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
Maven項(xiàng)目web多圖片上傳及格式驗(yàn)證的實(shí)現(xiàn)
本文主要介紹了Maven項(xiàng)目web多圖片上傳及格式驗(yàn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10

