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

 更新时间:2026年02月11日 09:36:07   作者:用户3891775626958  
在日常开发中,PDF文档处理是一个高频需求,无论是合同管理系统需要合并多份PDF,还是报表系统需要按页拆分,亦或是给机密文档添加水印,都需要一套可靠的PDF处理方案,本文将分享基于Java技术栈实现PDF批量处理的核心思路与代码实践,需要的朋友可以参考下

一、技术选型

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

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

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

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

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

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

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

二、Maven依赖配置

<dependency>

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

<artifactId>pdfbox</artifactId>

<version>2.0.27</version>

</dependency>

三、PDF合并实现

3.1 核心思路

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

3.2 代码实现

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 按页码范围拆分

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 文字水印核心代码

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

// 加载系统中文字体

PDType0Font chineseFont = PDType0Font.load(document,

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

cs.setFont(chineseFont, fontSize);

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

# CentOS/RHEL

sudo yum install -y wqy-microhei-fonts

# Ubuntu/Debian

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

六、封装为REST API

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

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

以上就是Java实现PDF批量处理之合并、拆分、加水印的技术方案与实践的详细内容,更多关于Java批量处理PDF文档的资料请关注脚本之家其它相关文章!

相关文章

  • Java中动态规则的实现方式示例详解

    Java中动态规则的实现方式示例详解

    这篇文章主要介绍了Java中动态规则的实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • 如何发布jar包到maven中央仓库

    如何发布jar包到maven中央仓库

    这篇文章主要介绍了发布jar包到maven中央仓库的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-12-12
  • springboot整合webservice使用简单案例总结

    springboot整合webservice使用简单案例总结

    WebService是一个SOA(面向服务的编程)的架构,它是不依赖于语言,平台等,可以实现不同的语言间的相互调用,下面这篇文章主要给大家介绍了关于springboot整合webservice使用的相关资料,需要的朋友可以参考下
    2024-07-07
  • Java中Timer的schedule()方法参数详解

    Java中Timer的schedule()方法参数详解

    今天小编就为大家分享一篇关于Java中Timer的schedule()方法参数详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • java反转链表的多种解决方法举例详解

    java反转链表的多种解决方法举例详解

    这篇文章主要介绍了java反转链表的多种解决方法,分别是使用栈、双指针和递归,每种方法都有其实现原理和代码示例,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04
  • SpringBoot设置默认主页的方法步骤

    SpringBoot设置默认主页的方法步骤

    这篇文章主要介绍了SpringBoot设置默认主页的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • SpringBoot Redis 启动失败深度剖析与标准化解决方案(快速定位问题)

    SpringBoot Redis 启动失败深度剖析与标准化解决方案(快速定位问题)

    本文基于SpringBoot 2.x/3.x(含Jakarta EE适配)与Redis 6.x/7.x生态,系统梳理12类常见错误,涵盖错误现象、深层原因、解决方案及最佳实践,助力开发者快速定位问题,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • Springboot实现发送邮件及注册激活步骤

    Springboot实现发送邮件及注册激活步骤

    为了方便邮件发送功能的使用,我们用邮件发送功能实现用户注册,实现步骤大概就是进行用户注册同时发送一封激活邮件,邮件里附带激活链接,关于Springboot发送邮件注册激活功能的实现参考下本文吧
    2021-06-06
  • Java的二叉树排序以及遍历文件展示文本格式的文件树

    Java的二叉树排序以及遍历文件展示文本格式的文件树

    这篇文章主要介绍了Java的二叉树排序以及遍历文件展示文本格式的文件树,是对二叉树结构学习的两个很好的实践,需要的朋友可以参考下
    2015-11-11
  • Java 多线程学习详细总结

    Java 多线程学习详细总结

    本文主要介绍 Java 多线程的知识资料,这里整理了详细的多线程内容,及简单实现代码,有需要的朋友可以参考下
    2016-09-09

最新评论