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


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

相关推荐
万少4 分钟前
万少用9个AI工具,帮朋友完成了一个"不可能"的项目
前端
星辰_mya4 分钟前
码头调度主任——Kubernetes
后端·云原生·容器·面试·kubernetes
小小小小宇6 分钟前
Vue `import` 为什么可以异步加载
前端
WMYeah11 分钟前
【无标题】
前端·rust·抽奖程序·跨平台抽奖程序
Unbelievabletobe12 分钟前
免费外汇api的响应时间在不同时段下的波动分析
大数据·开发语言·前端·python
大哥,带带弟弟21 分钟前
Grafana 前端嵌入与 JWT 鉴权实战
前端·grafana
阿苟21 分钟前
数据库重点难点
redis·后端·mysql
小小小小宇22 分钟前
前端 V8 引擎垃圾回收机制与内存问题排查
前端
momom32 分钟前
分布式缓存集群高可用架构与一致性哈希优化实践
分布式·后端·架构
前端老石人33 分钟前
CSS 值定义语法
前端·css