Java实现PDF合并、PDF拆分、PDF加水印等PDF文档处理的技术方案与实践

Java实现PDF批量处理:合并、拆分、加水印的技术方案与实践

在日常开发中,PDF文档处理是一个高频需求。无论是合同管理系统需要合并多份PDF,还是报表系统需要按页拆分,亦或是给机密文档添加水印,都需要一套可靠的PDF处理方案。本文将分享基于Java技术栈实现PDF批量处理的核心思路与代码实践。

一、技术选型

Java生态中处理PDF的库主要有以下几个:

| 库名 | 特点 | 适用场景 |

|------|------|----------|

| Apache PDFBox | 开源免费,功能全面 | 合并、拆分、水印、文本提取 |

| iText | 功能强大,商用需授权 | 复杂PDF生成、表单处理 |

| OpenPDF | iText的开源分支 | 轻量级PDF生成 |

综合考虑开源协议和功能覆盖度,Apache PDFBox 是大多数项目的首选。

二、Maven依赖配置

xml 复制代码
<dependency>

<groupId>org.apache.pdfbox</groupId>

<artifactId>pdfbox</artifactId>

<version>2.0.27</version>

</dependency>

三、PDF合并实现

3.1 核心思路

PDF合并的本质是将多个PDF文档的页面按顺序追加到一个新文档中。PDFBox提供了PDFMergerUtility工具类来简化这个过程。

3.2 代码实现

java 复制代码
import org.apache.pdfbox.multipdf.PDFMergerUtility;

import java.io.*;

import java.util.List;

  


public class PdfMergeService {

  


/**

* 合并多个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);

}

  


// 执行合并,MemoryUsageSetting可控制内存使用

merger.mergeDocuments(

org.apache.pdfbox.io.MemoryUsageSetting.setupMainMemoryOnly()

);

}

}

3.3 注意事项

  • 内存管理 :处理大文件时建议使用setupTempFileOnly()将临时数据写入磁盘,避免OOM

  • 文件顺序 :合并顺序取决于addSource的调用顺序,前端可通过拖拽排序来控制

  • 书签处理 :合并后原有书签可能丢失,如需保留需额外处理PDDocumentOutline

四、PDF拆分实现

4.1 按页码范围拆分

java 复制代码
import org.apache.pdfbox.multipdf.Splitter;

import org.apache.pdfbox.pdmodel.PDDocument;

  


public class PdfSplitService {

  


/**

* 按页码范围拆分PDF

* @param inputStream 源PDF输入流

* @param startPage 起始页(从1开始)

* @param endPage 结束页

*/

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();

// 参数校验

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 性能优化建议

对于页数很多的PDF(如上千页),逐页操作可能较慢。可以考虑:

  • 使用Splitter类的setSplitAtPage()方法按固定页数批量拆分

  • 对于仅需提取少量页面的场景,直接操作页面树比使用Splitter更高效

五、PDF加水印实现

5.1 文字水印核心代码

java 复制代码
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

);

  


// 设置透明度

PDExtendedGraphicsState gs = new PDExtendedGraphicsState();

gs.setNonStrokingAlphaConstant(opacity);

cs.setGraphicsStateParameters(gs);

  


// 设置字体和颜色

cs.setFont(PDType1Font.HELVETICA_BOLD, fontSize);

cs.setNonStrokingColor(200, 200, 200); // 浅灰色

  


// 计算页面中心位置

float pageWidth = page.getMediaBox().getWidth();

float pageHeight = page.getMediaBox().getHeight();

float cx = pageWidth / 2;

float cy = pageHeight / 2;

  


// 旋转变换

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默认的PDType1Font不支持中文字符,直接使用会导致乱码或报错。解决方案:

java 复制代码
// 加载系统中文字体

PDType0Font chineseFont = PDType0Font.load(document,

new FileInputStream("/usr/share/fonts/wqy-microhei/wqy-microhei.ttc"), 0);

cs.setFont(chineseFont, fontSize);

服务器部署时需确保安装了中文字体

bash 复制代码
# CentOS/RHEL

sudo yum install -y wqy-microhei-fonts

  


# Ubuntu/Debian

sudo apt-get install -y fonts-wqy-microhei

六、封装为REST API

在Spring Boot项目中,可以将上述功能封装为统一的REST接口:

java 复制代码
@RestController

@RequestMapping("/api/document/pdf")

public class PdfController {

  


@PostMapping("/merge")

public ResponseEntity<?> merge(

@RequestParam("files") MultipartFile[] files) {

// 调用合并服务

}

  


@PostMapping("/split")

public ResponseEntity<?> split(

@RequestParam("file") MultipartFile file,

@RequestParam("startPage") int startPage,

@RequestParam("endPage") int endPage) {

// 调用拆分服务

}

  


@PostMapping("/watermark")

public ResponseEntity<?> watermark(

@RequestParam("file") MultipartFile file,

@RequestParam("text") String text,

@RequestParam(defaultValue = "0.5") float opacity) {

// 调用水印服务

}

}

七、实际效果

如果你不想自己搭建服务,也可以直接使用现成的在线工具来处理PDF。比如 轻语API开放平台 提供了免费的 PDF在线处理工具,支持合并、拆分、加水印、转Word、转图片等功能,底层就是基于上述技术方案实现的。

对于需要批量处理的场景,也可以通过API接口集成到自己的系统中,省去重复造轮子的成本。

八、总结

| 功能 | 核心类/方法 | 难点 |

|------|------------|------|

| PDF合并 | PDFMergerUtility | 大文件内存管理 |

| PDF拆分 | PDDocument.getPage() | 页码边界校验 |

| PDF水印 | PDPageContentStream | 中文字体支持 |

PDF处理看似简单,但在生产环境中需要关注内存控制、字体兼容、并发处理等问题。希望本文的实践经验能帮助你少踩一些坑。


如果觉得有帮助,欢迎点赞收藏。有问题可以在评论区交流。

相关推荐
mCell10 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell11 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭11 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清11 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木11 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_6070766011 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声11 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易11 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得012 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
青云计划12 小时前
知光项目知文发布模块
java·后端·spring·mybatis