一、技术选型
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文档的资料请关注代码网其它相关文章!
发表评论