Java實(shí)現(xiàn)PDF批量處理之合并、拆分、加水印的技術(shù)方案與實(shí)踐
一、技術(shù)選型
Java生態(tài)中處理PDF的庫主要有以下幾個(gè):
| 庫名 | 特點(diǎn) | 適用場(chǎng)景 |
|------|------|----------|
| Apache PDFBox | 開源免費(fèi),功能全面 | 合并、拆分、水印、文本提取 |
| iText | 功能強(qiáng)大,商用需授權(quán) | 復(fù)雜PDF生成、表單處理 |
| OpenPDF | iText的開源分支 | 輕量級(jí)PDF生成 |
綜合考慮開源協(xié)議和功能覆蓋度,Apache PDFBox 是大多數(shù)項(xiàng)目的首選。
二、Maven依賴配置
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.27</version> </dependency>
三、PDF合并實(shí)現(xiàn)
3.1 核心思路
PDF合并的本質(zhì)是將多個(gè)PDF文檔的頁面按順序追加到一個(gè)新文檔中。PDFBox提供了PDFMergerUtility工具類來簡(jiǎn)化這個(gè)過程。
3.2 代碼實(shí)現(xiàn)
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import java.io.*;
import java.util.List;
public class PdfMergeService {
/**
* 合并多個(gè)PDF文件
* @param inputStreams PDF文件輸入流列表
* @param outputPath 輸出文件路徑
*/
public void mergePdf(List<InputStream> inputStreams, String outputPath)
throws IOException {
PDFMergerUtility merger = new PDFMergerUtility();
merger.setDestinationFileName(outputPath);
for (InputStream is : inputStreams) {
merger.addSource(is);
}
// 執(zhí)行合并,MemoryUsageSetting可控制內(nèi)存使用
merger.mergeDocuments(
org.apache.pdfbox.io.MemoryUsageSetting.setupMainMemoryOnly()
);
}
}
3.3 注意事項(xiàng)
- 內(nèi)存管理:處理大文件時(shí)建議使用
setupTempFileOnly()將臨時(shí)數(shù)據(jù)寫入磁盤,避免OOM - 文件順序:合并順序取決于
addSource的調(diào)用順序,前端可通過拖拽排序來控制 - 書簽處理:合并后原有書簽可能丟失,如需保留需額外處理
PDDocumentOutline
四、PDF拆分實(shí)現(xiàn)
4.1 按頁碼范圍拆分
import org.apache.pdfbox.multipdf.Splitter;
import org.apache.pdfbox.pdmodel.PDDocument;
public class PdfSplitService {
/**
* 按頁碼范圍拆分PDF
* @param inputStream 源PDF輸入流
* @param startPage 起始頁(從1開始)
* @param endPage 結(jié)束頁
*/
public byte[] splitByPageRange(InputStream inputStream,
int startPage, int endPage) throws IOException {
try (PDDocument document = PDDocument.load(inputStream);
PDDocument newDoc = new PDDocument()) {
int totalPages = document.getNumberOfPages();
// 參數(shù)校驗(yàn)
startPage = Math.max(1, startPage);
endPage = Math.min(totalPages, endPage);
for (int i = startPage - 1; i < endPage; i++) {
newDoc.addPage(document.getPage(i));
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
newDoc.save(baos);
return baos.toByteArray();
}
}
}
4.2 性能優(yōu)化建議
對(duì)于頁數(shù)很多的PDF(如上千頁),逐頁操作可能較慢??梢钥紤]:
- 使用
Splitter類的setSplitAtPage()方法按固定頁數(shù)批量拆分 - 對(duì)于僅需提取少量頁面的場(chǎng)景,直接操作頁面樹比使用Splitter更高效
五、PDF加水印實(shí)現(xiàn)
5.1 文字水印核心代碼
import org.apache.pdfbox.pdmodel.*;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
public class PdfWatermarkService {
public byte[] addTextWatermark(InputStream input, String text,
float opacity, float rotation, float fontSize) throws IOException {
try (PDDocument document = PDDocument.load(input)) {
// 遍歷每一頁添加水印
for (PDPage page : document.getPages()) {
PDPageContentStream cs = new PDPageContentStream(
document, page,
PDPageContentStream.AppendMode.APPEND, true, true
);
// 設(shè)置透明度
PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
gs.setNonStrokingAlphaConstant(opacity);
cs.setGraphicsStateParameters(gs);
// 設(shè)置字體和顏色
cs.setFont(PDType1Font.HELVETICA_BOLD, fontSize);
cs.setNonStrokingColor(200, 200, 200); // 淺灰色
// 計(jì)算頁面中心位置
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
float cx = pageWidth / 2;
float cy = pageHeight / 2;
// 旋轉(zhuǎn)變換
cs.beginText();
Matrix matrix = Matrix.getRotateInstance(
Math.toRadians(rotation), cx, cy
);
cs.setTextMatrix(matrix);
cs.showText(text);
cs.endText();
cs.close();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
return baos.toByteArray();
}
}
}
5.2 中文水印的坑
PDFBox默認(rèn)的PDType1Font不支持中文字符,直接使用會(huì)導(dǎo)致亂碼或報(bào)錯(cuò)。解決方案:
// 加載系統(tǒng)中文字體
PDType0Font chineseFont = PDType0Font.load(document,
new FileInputStream("/usr/share/fonts/wqy-microhei/wqy-microhei.ttc"), 0);
cs.setFont(chineseFont, fontSize);
服務(wù)器部署時(shí)需確保安裝了中文字體:
# CentOS/RHEL sudo yum install -y wqy-microhei-fonts # Ubuntu/Debian sudo apt-get install -y fonts-wqy-microhei
六、封裝為REST API
在Spring Boot項(xiàng)目中,可以將上述功能封裝為統(tǒng)一的REST接口:
@RestController
@RequestMapping("/api/document/pdf")
public class PdfController {
@PostMapping("/merge")
public ResponseEntity<?> merge(
@RequestParam("files") MultipartFile[] files) {
// 調(diào)用合并服務(wù)
}
@PostMapping("/split")
public ResponseEntity<?> split(
@RequestParam("file") MultipartFile file,
@RequestParam("startPage") int startPage,
@RequestParam("endPage") int endPage) {
// 調(diào)用拆分服務(wù)
}
@PostMapping("/watermark")
public ResponseEntity<?> watermark(
@RequestParam("file") MultipartFile file,
@RequestParam("text") String text,
@RequestParam(defaultValue = "0.5") float opacity) {
// 調(diào)用水印服務(wù)
}
}
七、實(shí)際效果
如果你不想自己搭建服務(wù),也可以直接使用現(xiàn)成的在線工具來處理PDF。比如 輕語API開放平臺(tái) 提供了免費(fèi)的 PDF在線處理工具,支持合并、拆分、加水印、轉(zhuǎn)Word、轉(zhuǎn)圖片等功能,底層就是基于上述技術(shù)方案實(shí)現(xiàn)的。
對(duì)于需要批量處理的場(chǎng)景,也可以通過API接口集成到自己的系統(tǒng)中,省去重復(fù)造輪子的成本。
八、總結(jié)
| 功能 | 核心類/方法 | 難點(diǎn) |
|------|------------|------|
| PDF合并 | PDFMergerUtility | 大文件內(nèi)存管理 |
| PDF拆分 | PDDocument.getPage() | 頁碼邊界校驗(yàn) |
| PDF水印 | PDPageContentStream | 中文字體支持 |
PDF處理看似簡(jiǎn)單,但在生產(chǎn)環(huán)境中需要關(guān)注內(nèi)存控制、字體兼容、并發(fā)處理等問題。希望本文的實(shí)踐經(jīng)驗(yàn)?zāi)軒椭闵俨纫恍┛印?/p>
以上就是Java實(shí)現(xiàn)PDF批量處理之合并、拆分、加水印的技術(shù)方案與實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Java批量處理PDF文檔的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中動(dòng)態(tài)規(guī)則的實(shí)現(xiàn)方式示例詳解
這篇文章主要介紹了Java中動(dòng)態(tài)規(guī)則的實(shí)現(xiàn)方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
springboot整合webservice使用簡(jiǎn)單案例總結(jié)
WebService是一個(gè)SOA(面向服務(wù)的編程)的架構(gòu),它是不依賴于語言,平臺(tái)等,可以實(shí)現(xiàn)不同的語言間的相互調(diào)用,下面這篇文章主要給大家介紹了關(guān)于springboot整合webservice使用的相關(guān)資料,需要的朋友可以參考下2024-07-07
Java中Timer的schedule()方法參數(shù)詳解
今天小編就為大家分享一篇關(guān)于Java中Timer的schedule()方法參數(shù)詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
SpringBoot設(shè)置默認(rèn)主頁的方法步驟
這篇文章主要介紹了SpringBoot設(shè)置默認(rèn)主頁的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
SpringBoot Redis 啟動(dòng)失敗深度剖析與標(biāo)準(zhǔn)化解決方案(快速定位問題)
本文基于SpringBoot 2.x/3.x(含Jakarta EE適配)與Redis 6.x/7.x生態(tài),系統(tǒng)梳理12類常見錯(cuò)誤,涵蓋錯(cuò)誤現(xiàn)象、深層原因、解決方案及最佳實(shí)踐,助力開發(fā)者快速定位問題,感興趣的朋友跟隨小編一起看看吧2025-11-11
Springboot實(shí)現(xiàn)發(fā)送郵件及注冊(cè)激活步驟
為了方便郵件發(fā)送功能的使用,我們用郵件發(fā)送功能實(shí)現(xiàn)用戶注冊(cè),實(shí)現(xiàn)步驟大概就是進(jìn)行用戶注冊(cè)同時(shí)發(fā)送一封激活郵件,郵件里附帶激活鏈接,關(guān)于Springboot發(fā)送郵件注冊(cè)激活功能的實(shí)現(xiàn)參考下本文吧2021-06-06
Java 多線程學(xué)習(xí)詳細(xì)總結(jié)
本文主要介紹 Java 多線程的知識(shí)資料,這里整理了詳細(xì)的多線程內(nèi)容,及簡(jiǎn)單實(shí)現(xiàn)代碼,有需要的朋友可以參考下2016-09-09

