Java实现PDF添加水印的完整方案(支持灵活配置、平铺、多页策略)
前言
在企业级应用中,PDF水印是文档安全管理的必备功能。无论是版权保护、版本标识,还是敏感信息管控,水印都扮演着重要角色。
本文将提供一个生产级的PDF水印解决方案,基于Apache PDFBox实现,具备以下特点:
- ✅ 高度可配置:支持文字、字体、颜色、旋转角度、透明度等全方位参数配置
- ✅ 智能页面控制:支持全部页面、奇偶页、指定页码等灵活策略
- ✅ 平铺模式:支持水印平铺布局,避免单一水印被截断
- ✅ 多水印组合:支持在同一文档中添加多个不同的水印
- ✅ 建造者模式:链式调用,代码简洁优雅
一、技术选型
为什么选择Apache PDFBox?
| 对比项 | Apache PDFBox | iText | OpenPDF |
|---|---|---|---|
| 开源协议 | Apache 2.0(免费) | AGPL(商业收费) | LGPL(较宽松) |
| 功能完整性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 社区活跃度 | 高 | 中等 | 中等 |
| 学习曲线 | 平缓 | 陡峭 | 较平缓 |
| 推荐指数 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
结论:对于企业项目,PDFBox是最优选择——开源免费、功能完整、社区活跃。
Maven依赖
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>pdfboxartifactId>
<version>2.0.29version>
dependency>
二、核心代码实现
2.1 水印配置类(WatermarkConfig)
采用建造者模式设计,支持链式调用,所有参数都可灵活配置。
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.PDFont;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
/**
* PDF水印配置类
* 使用建造者模式构建,支持链式调用
*/
public class WatermarkConfig {
// ==================== 基础配置 ====================
private String watermarkText; // 水印文字(必填)
private PDFont font; // 字体
private float fontSize; // 字体大小
private Color color; // 颜色(支持透明度)
// ==================== 旋转配置 ====================
private int rotationAngle; // 旋转角度(度),支持正负值
// ==================== 位置配置 ====================
private WatermarkPosition position; // 水印位置枚举
private Float customX; // 自定义X坐标(position=CUSTOM时使用)
private Float customY; // 自定义Y坐标(position=CUSTOM时使用)
// ==================== 平铺配置 ====================
private boolean tilingEnabled; // 是否启用平铺模式
private int tilingDensityX; // X轴平铺密度(每页重复次数)
private int tilingDensityY; // Y轴平铺密度(每页重复次数)
private float tilingOffsetX; // X轴偏移量(百分比 0-1)
private float tilingOffsetY; // Y轴偏移量(百分比 0-1)
// ==================== 透明度配置 ====================
private float opacity; // 透明度 0-1(0完全透明,1完全不透明)
// ==================== 页面过滤配置 ====================
private List<Integer> targetPageNumbers; // 目标页码列表(从0开始)
private PageFilterStrategy pageFilterStrategy; // 页面过滤策略
// ==================== 多水印配置 ====================
private List<WatermarkConfig> multiWatermarks; // 多水印配置列表
/**
* 水印位置枚举
*/
public enum WatermarkPosition {
CENTER, // 居中
TOP_LEFT, // 左上角
TOP_RIGHT, // 右上角
BOTTOM_LEFT, // 左下角
BOTTOM_RIGHT, // 右下角
TOP_CENTER, // 上中
BOTTOM_CENTER, // 下中
CUSTOM // 自定义坐标
}
/**
* 页面过滤策略
*/
public enum PageFilterStrategy {
ALL_PAGES, // 所有页面(默认)
EVEN_PAGES, // 仅偶数页
ODD_PAGES, // 仅奇数页
SPECIFIC_PAGES // 指定页码
}
/**
* 私有构造方法,使用Builder构建
*/
private WatermarkConfig() {
// 设置默认值
this.font = PDType1Font.HELVETICA_BOLD;
this.fontSize = 36f;
this.color = new Color(200, 200, 200, 128); // 默认灰色半透明
this.rotationAngle = 45;
this.position = WatermarkPosition.CENTER;
this.tilingEnabled = false;
this.tilingDensityX = 3;
this.tilingDensityY = 3;
this.tilingOffsetX = 0.25f;
this.tilingOffsetY = 0.25f;
this.opacity = 0.5f;
this.pageFilterStrategy = PageFilterStrategy.ALL_PAGES;
this.targetPageNumbers = new ArrayList<>();
}
/**
* Builder建造者类
*/
public static class Builder {
private WatermarkConfig config;
public Builder() {
this.config = new WatermarkConfig();
}
public Builder text(String text) {
config.watermarkText = text;
return this;
}
public Builder font(PDFont font) {
config.font = font;
return this;
}
public Builder fontSize(float fontSize) {
config.fontSize = fontSize;
return this;
}
public Builder color(Color color) {
config.color = color;
return this;
}
public Builder rotation(int angle) {
config.rotationAngle = angle;
return this;
}
public Builder position(WatermarkPosition position) {
config.position = position;
return this;
}
public Builder customPosition(float x, float y) {
config.position = WatermarkPosition.CUSTOM;
config.customX = x;
config.customY = y;
return this;
}
public Builder enableTiling(int densityX, int densityY) {
config.tilingEnabled = true;
config.tilingDensityX = densityX;
config.tilingDensityY = densityY;
return this;
}
public Builder enableTiling(int densityX, int densityY, float offsetX, float offsetY) {
config.tilingEnabled = true;
config.tilingDensityX = densityX;
config.tilingDensityY = densityY;
config.tilingOffsetX = offsetX;
config.tilingOffsetY = offsetY;
return this;
}
public Builder opacity(float opacity) {
config.opacity = opacity;
return this;
}
public Builder targetPages(PageFilterStrategy strategy) {
config.pageFilterStrategy = strategy;
return this;
}
public Builder targetPages(Integer... pageNumbers) {
config.pageFilterStrategy = PageFilterStrategy.SPECIFIC_PAGES;
for (Integer num : pageNumbers) {
config.targetPageNumbers.add(num);
}
return this;
}
public Builder addMultiWatermark(WatermarkConfig watermarkConfig) {
if (config.multiWatermarks == null) {
config.multiWatermarks = new ArrayList<>();
}
config.multiWatermarks.add(watermarkConfig);
return this;
}
public WatermarkConfig build() {
if (config.watermarkText == null || config.watermarkText.trim().isEmpty()) {
throw new IllegalArgumentException("水印文字不能为空");
}
return config;
}
}
// ==================== Getter方法 ====================
public String getWatermarkText() { return watermarkText; }
public PDFont getFont() { return font; }
public float getFontSize() { return fontSize; }
public Color getColor() { return color; }
public int getRotationAngle() { return rotationAngle; }
public WatermarkPosition getPosition() { return position; }
public Float getCustomX() { return customX; }
public Float getCustomY() { return customY; }
public boolean isTilingEnabled() { return tilingEnabled; }
public int getTilingDensityX() { return tilingDensityX; }
public int getTilingDensityY() { return tilingDensityY; }
public float getTilingOffsetX() { return tilingOffsetX; }
public float getTilingOffsetY() { return tilingOffsetY; }
public float getOpacity() { return opacity; }
public List<Integer> getTargetPageNumbers() { return targetPageNumbers; }
public PageFilterStrategy getPageFilterStrategy() { return pageFilterStrategy; }
public List<WatermarkConfig> getMultiWatermarks() { return multiWatermarks; }
}
2.2 水印执行工具类(EnhancedPdfWatermarkUtil)
核心执行逻辑,支持流式和文件两种模式。
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.util.Matrix;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 增强版PDF水印工具
* 支持灵活配置、页面过滤、平铺模式、多水印组合
*/
public class EnhancedPdfWatermarkUtil {
/**
* 添加水印(流模式)- 适合Web场景
*
* @param inputStream 输入PDF流
* @param outputStream 输出PDF流
* @param config 水印配置
* @throws IOException IO异常
*/
public static void addWatermark(InputStream inputStream, OutputStream outputStream,
WatermarkConfig config) throws IOException {
try (PDDocument document = PDDocument.load(inputStream)) {
addWatermarkToDocument(document, config);
document.save(outputStream);
}
}
/**
* 添加水印(文件模式)- 适合批量处理
*
* @param sourceFile 源文件
* @param targetFile 目标文件
* @param config 水印配置
* @throws IOException IO异常
*/
public static void addWatermark(File sourceFile, File targetFile,
WatermarkConfig config) throws IOException {
try (PDDocument document = PDDocument.load(sourceFile)) {
addWatermarkToDocument(document, config);
document.save(targetFile);
}
}
/**
* 向文档添加水印(支持多水印配置)
*
* @param document PDF文档对象
* @param config 水印配置
* @throws IOException IO异常
*/
private static void addWatermarkToDocument(PDDocument document, WatermarkConfig config) throws IOException {
int pageCount = document.getNumberOfPages();
// 处理主水印
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
if (shouldAddWatermarkToPage(pageIndex, config)) {
PDPage page = document.getPage(pageIndex);
addWatermarkToPage(document, page, config);
}
}
// 处理多水印配置(递归处理子水印)
if (config.getMultiWatermarks() != null) {
for (WatermarkConfig subConfig : config.getMultiWatermarks()) {
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
if (shouldAddWatermarkToPage(pageIndex, subConfig)) {
PDPage page = document.getPage(pageIndex);
addWatermarkToPage(document, page, subConfig);
}
}
}
}
}
/**
* 判断页面是否需要添加水印
*
* @param pageIndex 页码索引(从0开始)
* @param config 水印配置
* @return true-需要添加,false-跳过
*/
private static boolean shouldAddWatermarkToPage(int pageIndex, WatermarkConfig config) {
switch (config.getPageFilterStrategy()) {
case ALL_PAGES:
return true;
case EVEN_PAGES:
return (pageIndex + 1) % 2 == 0;
case ODD_PAGES:
return (pageIndex + 1) % 2 != 0;
case SPECIFIC_PAGES:
return config.getTargetPageNumbers().contains(pageIndex);
default:
return true;
}
}
/**
* 向单页添加水印
*
* @param document PDF文档对象(必须传入)
* @param page PDF页面对象
* @param config 水印配置
* @throws IOException IO异常
*/
private static void addWatermarkToPage(PDDocument document, PDPage page, WatermarkConfig config) throws IOException {
PDRectangle pageSize = page.getMediaBox();
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
try (PDPageContentStream contentStream = new PDPageContentStream(
document,
page,
PDPageContentStream.AppendMode.APPEND,
true,
true)) {
// 设置字体和颜色
contentStream.setFont(config.getFont(), config.getFontSize());
contentStream.setNonStrokingColor(config.getColor());
// 根据配置选择绘制模式
if (config.isTilingEnabled()) {
// 平铺模式
addTiledWatermark(contentStream, config, pageWidth, pageHeight);
} else {
// 单点模式
addSingleWatermark(contentStream, config, pageWidth, pageHeight);
}
}
}
/**
* 添加平铺水印(网格布局)
*
* @param contentStream 内容流
* @param config 水印配置
* @param pageWidth 页面宽度
* @param pageHeight 页面高度
* @throws IOException IO异常
*/
private static void addTiledWatermark(PDPageContentStream contentStream,
WatermarkConfig config,
float pageWidth, float pageHeight) throws IOException {
// 计算旋转角度(弧度)
float angle = (float) Math.toRadians(config.getRotationAngle());
// 计算网格间距
float stepX = pageWidth / config.getTilingDensityX();
float stepY = pageHeight / config.getTilingDensityY();
// 遍历网格绘制水印
for (int i = 0; i < config.getTilingDensityX(); i++) {
for (int j = 0; j < config.getTilingDensityY(); j++) {
float x = stepX * i + stepX * config.getTilingOffsetX();
float y = stepY * j + stepY * config.getTilingOffsetY();
drawRotatedText(contentStream, config, x, y, angle);
}
}
}
/**
* 添加单点水印(固定位置 - 已修复真正居中)
*
* @param contentStream 内容流
* @param config 水印配置
* @param pageWidth 页面宽度
* @param pageHeight 页面高度
* @throws IOException IO异常
*/
private static void addSingleWatermark(PDPageContentStream contentStream,
WatermarkConfig config,
float pageWidth, float pageHeight) throws IOException {
float x, y;
float angle = (float) Math.toRadians(config.getRotationAngle());
// ✅ 计算文字宽度,实现真正的左右居中
float textWidth = config.getFont().getStringWidth(config.getWatermarkText())
/ 1000 * config.getFontSize();
// 根据位置枚举计算坐标
switch (config.getPosition()) {
case CENTER:
// ✅ 页面中心 - 文字宽度的一半 = 文字居中
x = pageWidth / 2 - textWidth / 2;
y = pageHeight / 2;
break;
case TOP_LEFT:
x = 100;
y = pageHeight - 100;
break;
case TOP_RIGHT:
// ✅ 右边缘 - 文字宽度 - 边距
x = pageWidth - textWidth - 100;
y = pageHeight - 100;
break;
case BOTTOM_LEFT:
x = 100;
y = 100;
break;
case BOTTOM_RIGHT:
// ✅ 右边缘 - 文字宽度 - 边距
x = pageWidth - textWidth - 100;
y = 100;
break;
case TOP_CENTER:
// ✅ 页面水平中心 - 文字宽度的一半
x = pageWidth / 2 - textWidth / 2;
y = pageHeight - 100;
break;
case BOTTOM_CENTER:
// ✅ 页面水平中心 - 文字宽度的一半
x = pageWidth / 2 - textWidth / 2;
y = 100;
break;
case CUSTOM:
x = config.getCustomX();
y = config.getCustomY();
break;
default:
x = pageWidth / 2 - textWidth / 2;
y = pageHeight / 2;
}
drawRotatedText(contentStream, config, x, y, angle);
}
/**
* 绘制旋转文字(核心方法 - 已修复方法调用顺序)
*
* @param contentStream 内容流
* @param config 水印配置
* @param x X坐标
* @param y Y坐标
* @param angle 旋转角度(弧度)
* @throws IOException IO异常
*/
private static void drawRotatedText(PDPageContentStream contentStream,
WatermarkConfig config,
float x, float y, float angle) throws IOException {
contentStream.saveGraphicsState();
// ✅ 正确顺序:先 beginText,再 setTextMatrix
contentStream.beginText();
contentStream.setTextMatrix(Matrix.getRotateInstance(angle, x, y));
contentStream.newLineAtOffset(0, 0);
contentStream.showText(config.getWatermarkText());
contentStream.endText();
contentStream.restoreGraphicsState();
}
}
三、使用示例
示例1:所有页面居中水印(最简单场景)
import java.io.File;
import java.awt.Color;
public class Example1_SimpleWatermark {
public static void main(String[] args) {
try {
File sourceFile = new File("原始.pdf");
File targetFile = new File("带水印.pdf");
// 构建配置
WatermarkConfig config = new WatermarkConfig.Builder()
.text("机密文档")
.fontSize(48f)
.color(new Color(200, 0, 0, 128)) // 半透明红色
.rotation(30)
.position(WatermarkConfig.WatermarkPosition.CENTER)
.opacity(0.6f)
.build();
// 执行添加水印
EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, config);
System.out.println("水印添加成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果:所有页面中央出现"机密文档"水印,红色半透明,旋转30度。
示例2:奇偶页不同水印 + 平铺效果
import java.io.File;
import java.awt.Color;
public class Example2_OddEvenWatermark {
public static void main(String[] args) {
try {
File sourceFile = new File("原始.pdf");
File targetFile = new File("奇偶水印.pdf");
// 奇数页配置:平铺灰色水印
WatermarkConfig oddPageConfig = new WatermarkConfig.Builder()
.text("内部专用")
.fontSize(32f)
.color(Color.GRAY)
.rotation(45)
.enableTiling(3, 3) // 3x3平铺
.targetPages(WatermarkConfig.PageFilterStrategy.ODD_PAGES)
.build();
// 偶数页配置:居中红色大水印
WatermarkConfig evenPageConfig = new WatermarkConfig.Builder()
.text("CONFIDENTIAL")
.fontSize(56f)
.color(new Color(255, 0, 0, 150))
.rotation(-30)
.position(WatermarkConfig.WatermarkPosition.CENTER)
.targetPages(WatermarkConfig.PageFilterStrategy.EVEN_PAGES)
.build();
// 组合多水印配置
WatermarkConfig combinedConfig = new WatermarkConfig.Builder()
.text("主水印")
.addMultiWatermark(oddPageConfig)
.addMultiWatermark(evenPageConfig)
.build();
EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, combinedConfig);
System.out.println("奇偶页水印添加成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果:
- 奇数页:灰色"内部专用"水印,3x3平铺
- 偶数页:红色"CONFIDENTIAL"水印,居中大字号
示例3:指定页码 + 自定义位置
import java.io.File;
import java.awt.Color;
public class Example3_SpecificPages {
public static void main(String[] args) {
try {
File sourceFile = new File("原始.pdf");
File targetFile = new File("指定页水印.pdf");
// 配置:只在第1、3、5页添加水印
WatermarkConfig config = new WatermarkConfig.Builder()
.text("草稿版本")
.fontSize(24f)
.color(Color.BLUE)
.rotation(0) // 不旋转
.customPosition(200f, 300f) // 自定义坐标
.targetPages(0, 2, 4) // 页码从0开始,即第1、3、5页
.build();
EnhancedPdfWatermarkUtil.addWatermark(sourceFile, targetFile, config);
System.out.println("指定页水印添加成功!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果:第1、3、5页的坐标(200, 300)处出现蓝色"草稿版本"水印,不旋转。
示例4:Web流式输出 + 高密度平铺
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import java.awt.Color;
import java.io.InputStream;
import java.io.OutputStream;
public class Example4_WebStream {
public void downloadWatermarkedPdf(InputStream pdfInput,
HttpServletResponse response) throws Exception {
// 配置:高密度平铺水印
WatermarkConfig config = new WatermarkConfig.Builder()
.text("禁止外传")
.font(PDType1Font.HELVETICA_BOLD)
.fontSize(28f)
.color(new Color(150, 150, 150, 100))
.rotation(45)
.enableTiling(5, 4, 0.2f, 0.3f) // 5x4平铺,带偏移
.opacity(0.4f)
.targetPages(WatermarkConfig.PageFilterStrategy.ALL_PAGES)
.build();
// 设置响应头
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=watermarked.pdf");
// 流式输出
try (OutputStream output = response.getOutputStream()) {
EnhancedPdfWatermarkUtil.addWatermark(pdfInput, output, config);
}
}
}
效果:用户下载的PDF文件中,所有页面都布满5x4网格的"禁止外传"水印。
四、核心功能详解
4.1 页面过滤策略
| 策略 | 说明 | 使用场景 |
|---|---|---|
ALL_PAGES | 所有页面 | 默认场景,确保每页都有水印 |
EVEN_PAGES | 仅偶数页 | 奇偶页不同水印的需求 |
ODD_PAGES | 仅奇数页 | 奇偶页不同水印的需求 |
SPECIFIC_PAGES | 指定页码 | 仅对特定页面(如封面、结尾页)加水印 |
代码示例:
// 偶数页加水印
.targetPages(WatermarkConfig.PageFilterStrategy.EVEN_PAGES)
// 指定第1、3、5页加水印(页码从0开始)
.targetPages(0, 2, 4)
4.2 水印位置配置
支持8种预设位置 + 自定义坐标:
| 位置枚举 | 说明 |
|---|---|
CENTER | 页面居中 |
TOP_LEFT | 左上角 |
TOP_RIGHT | 右上角 |
BOTTOM_LEFT | 左下角 |
BOTTOM_RIGHT | 右下角 |
TOP_CENTER | 上中 |
BOTTOM_CENTER | 下中 |
CUSTOM | 自定义坐标 |
代码示例:
// 使用预设位置
.position(WatermarkConfig.WatermarkPosition.TOP_RIGHT)
// 使用自定义坐标
.customPosition(500f, 200f)
4.3 平铺模式详解
平铺模式通过网格布局,让水印覆盖整个页面,避免单一水印被截断的问题。
核心参数:
densityX:X轴平铺密度(每页重复次数)densityY:Y轴平铺密度(每页重复次数)offsetX:X轴偏移量(百分比 0-1)offsetY:Y轴偏移量(百分比 0-1)
可视化示意:
3x3平铺(offsetX=0.25, offsetY=0.25):
┌─────────────────────────────┐
│ ✓ ✓ ✓ │
│ ✓ ✓ ✓ │
│ ✓ ✓ ✓│
└─────────────────────────────┘
5x4平铺(offsetX=0.2, offsetY=0.3):
┌─────────────────────────────┐
│ ✓ ✓ ✓ ✓ ✓ │
│ ✓ ✓ ✓ ✓ ✓ │
│ ✓ ✓ ✓ ✓ ✓ │
│✓ ✓ ✓ ✓ ✓ │
└─────────────────────────────┘
代码示例:
// 3x3平铺,默认偏移
.enableTiling(3, 3)
// 5x4平铺,自定义偏移
.enableTiling(5, 4, 0.2f, 0.3f)
4.4 多水印组合
支持在同一文档中添加多个不同的水印,满足复杂业务场景。
典型应用场景:
- 主水印 + 副水印(如"机密" + “禁止外传”)
- 不同页面不同水印(奇偶页策略)
- 不同位置不同样式的水印组合
代码示例:
WatermarkConfig config = new WatermarkConfig.Builder()
.text("主水印")
.addMultiWatermark(watermarkConfig1)
.addMultiWatermark(watermarkConfig2)
.addMultiWatermark(watermarkConfig3)
.build();
五、常见问题与解决方案
Q1:水印显示乱码或字体不正确?
原因:PDFBox默认字体不支持中文。
解决方案:加载外部中文字体文件。
// 加载中文字体
PDFont chineseFont = PDType0Font.load(document,
new File("C:/Windows/Fonts/simhei.ttf"));
WatermarkConfig config = new WatermarkConfig.Builder()
.font(chineseFont)
.text("机密文档")
.build();
Q2:水印在原有内容下方,被遮挡了?
原因:PDFBox默认将新内容添加在页面内容流末尾。
解决方案:调整内容流添加顺序(需要修改PDFBox底层处理逻辑,较复杂)。
替代方案:提高水印透明度,使其即使被遮挡也能隐约可见。
.opacity(0.8f) // 提高不透明度
Q3:水印文字过长,超出页面边界?
原因:字体大小或位置设置不当。
解决方案:
- 调小字体大小
- 使用平铺模式,让水印分布在页面多个位置
- 调整旋转角度,减少水平空间占用
.fontSize(24f) // 调小字体
.rotation(60) // 增大旋转角度
Q4:如何添加图片水印?
当前方案限制:本文仅提供文字水印实现。
扩展方案:使用PDFBox的PDImageXObject加载图片,通过drawImage方法绘制。
// 示例代码(需自行扩展WatermarkConfig)
PDImageXObject image = PDImageXObject.createFromFile("watermark.png", document);
contentStream.drawImage(image, x, y, width, height);
六、性能优化建议
6.1 批量处理优化
对于大批量PDF文件处理,建议:
- 使用线程池:并行处理多个PDF文件
- 复用字体对象:避免重复加载字体文件
- 流式处理:大文件使用流模式,避免内存溢出
示例代码:
// 使用线程池批量处理
ExecutorService executor = Executors.newFixedThreadPool(4);
List<File> files = Arrays.asList(new File("dir").listFiles());
for (File file : files) {
executor.submit(() -> {
// 处理逻辑
EnhancedPdfWatermarkUtil.addWatermark(source, target, config);
});
}
executor.shutdown();
6.2 内存优化
对于超大PDF文件(几百MB以上):
- 使用流模式:避免一次性加载整个文件到内存
- 分页处理:逐页处理,及时释放内存
- 增加JVM堆内存:
-Xmx2g或更大
示例代码:
// 流模式处理大文件
try (InputStream input = new FileInputStream("large.pdf");
OutputStream output = new FileOutputStream("output.pdf")) {
EnhancedPdfWatermarkUtil.addWatermark(input, output, config);
}
七、总结
本文提供了一个生产级的PDF水印解决方案,具备以下核心优势:
- ✅ 高度可配置:支持文字、字体、颜色、旋转角度、透明度等全方位参数配置
- ✅ 智能页面控制:支持全部页面、奇偶页、指定页码等灵活策略
- ✅ 平铺模式:支持水印平铺布局,避免单一水印被截断
- ✅ 多水印组合:支持在同一文档中添加多个不同的水印
- ✅ 建造者模式:链式调用,代码简洁优雅
- ✅ 双模式支持:既支持文件操作(批量处理),也支持流操作(Web响应场景)
适用场景:
- 企业文档管理系统
- 合同、报告等敏感文档处理
- Web下载文件自动添加水印
- 批量PDF文件处理任务







