Spring Boot 实现PDF水印的完整指南
引言
PDF(Portable Document Format)作为一种跨平台的文档格式,在企业应用中广泛使用。在实际业务场景中,我们经常需要为PDF文件添加水印,以保护版权、标识文档状态或增强品牌识别度。本文将详细介绍如何使用Spring Boot实现PDF水印功能,涵盖五种主流实现方案。
方案一:使用Apache PDFBox
Apache PDFBox是一个开源的Java库,提供了完善的PDF处理功能,完全免费使用。
1. 添加依赖
xml
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.27</version>
</dependency>
2. Spring Boot实现
java
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@RestController
@RequestMapping("/api/pdf")
public class PDFBoxWatermarkController {
@PostMapping("/watermark/pdfbox")
public ResponseEntity<byte[]> addWatermark(@RequestParam("file") MultipartFile file) throws IOException {
// 加载PDF文档
try (PDDocument document = PDDocument.load(file.getInputStream())) {
// 遍历每一页
for (int i = 0; i < document.getNumberOfPages(); i++) {
PDPage page = document.getPage(i);
// 创建内容流,追加模式
PDPageContentStream contentStream = new PDPageContentStream(
document, page,
PDPageContentStream.AppendMode.APPEND,
true, true
);
// 设置字体和大小
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 48);
// 设置浅灰色,半透明效果
contentStream.setNonStrokingColor(200, 200, 200);
// 获取页面尺寸
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
// 添加水印文本(居中显示)
contentStream.beginText();
contentStream.newLineAtOffset(pageWidth / 2 - 150, pageHeight / 2);
contentStream.showText("CONFIDENTIAL");
contentStream.endText();
contentStream.close();
}
// 保存到输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
}
}
3. PDFBox的优缺点
优点:
- 完全开源免费,Apache 2.0许可证
- API设计简洁,易于上手
- 无第三方依赖
缺点:
- 功能相对基础,复杂水印效果需要较多代码
- 性能在处理大文件时略逊于商业库
方案二:使用iText
iText是业界知名的PDF处理库,功能强大,但需要注意其商业许可。
1. 添加依赖
xml
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.5</version>
</dependency>
2. Spring Boot实现
java
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@RestController
@RequestMapping("/api/pdf")
public class ITextWatermarkController {
@PostMapping("/watermark/itext")
public ResponseEntity<byte[]> addWatermark(@RequestParam("file") MultipartFile file)
throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 创建PDF读取器
PdfReader reader = new PdfReader(file.getInputStream());
// 创建PDF印章
PdfStamper stamper = new PdfStamper(reader, outputStream);
// 获取总页数
int pageCount = reader.getNumberOfPages();
// 创建字体
BaseFont baseFont = BaseFont.createFont(
BaseFont.HELVETICA_BOLD,
BaseFont.WINANSI,
BaseFont.EMBEDDED
);
// 遍历每一页
for (int i = 1; i <= pageCount; i++) {
// 获取页面内容(覆盖层,水印在内容上方)
PdfContentByte contentByte = stamper.getOverContent(i);
// 开始文本处理
contentByte.beginText();
// 设置字体和大小
contentByte.setFontAndSize(baseFont, 60);
// 设置颜色和透明度
contentByte.setColorFill(BaseColor.LIGHT_GRAY);
contentByte.setGState(new PdfGState() {{
setFillOpacity(0.3f);
}});
// 获取页面尺寸
Rectangle pageSize = reader.getPageSize(i);
float x = pageSize.getWidth() / 2;
float y = pageSize.getHeight() / 2;
// 添加旋转水印
contentByte.showTextAligned(
Element.ALIGN_CENTER,
"DRAFT",
x, y, 45
);
contentByte.endText();
}
stamper.close();
reader.close();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
}
3. iText的优缺点
优点:
- 功能全面,支持复杂的PDF操作
- 性能优秀,文档完善
- 支持数字签名、表单处理等高级功能
缺点:
- 商业使用需购买许可证(AGPL协议)
- 社区版功能受限
方案三:使用Ghostscript命令行
Ghostscript是一个强大的PDF处理工具,通过Java调用命令行实现水印添加。
1. 安装Ghostscript
根据操作系统选择安装方式:
- Windows:下载安装包 https://ghostscript.com/releases/gsdnld.html
- macOS :
brew install ghostscript - Linux :
apt-get install ghostscript或yum install ghostscript
2. Spring Boot实现
java
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@RestController
@RequestMapping("/api/pdf")
public class GhostscriptWatermarkController {
private static final String GHOSTSCRIPT_CMD = "gs"; // 或完整路径
@PostMapping("/watermark/ghostscript")
public ResponseEntity<byte[]> addWatermark(@RequestParam("file") MultipartFile file)
throws IOException, InterruptedException {
// 创建临时文件
Path inputFile = Files.createTempFile("input_", ".pdf");
Path outputFile = Files.createTempFile("output_", ".pdf");
try {
// 保存上传文件
Files.copy(file.getInputStream(), inputFile, StandardCopyOption.REPLACE_EXISTING);
// 构建Ghostscript命令
ProcessBuilder pb = new ProcessBuilder(
GHOSTSCRIPT_CMD,
"-dBATCH",
"-dNOPAUSE",
"-sDEVICE=pdfwrite",
"-sOutputFile=" + outputFile.toString(),
"-c",
"newpath /Helvetica-Bold findfont 48 scalefont setfont " +
"0.7 setgray " +
"200 200 moveto (CONFIDENTIAL) show " +
"300 500 moveto (CONFIDENTIAL) show " +
"showpage",
inputFile.toString()
);
// 执行命令
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Ghostscript处理失败,退出码:" + exitCode);
}
// 读取结果
byte[] result = Files.readAllBytes(outputFile);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(result);
} finally {
// 清理临时文件
Files.deleteIfExists(inputFile);
Files.deleteIfExists(outputFile);
}
}
}
3. 进阶用法:添加多个水印
java
private String buildWatermarkCommand(String text, float opacity, int pageWidth, int pageHeight) {
StringBuilder sb = new StringBuilder();
sb.append("newpath /Helvetica-Bold findfont 36 scalefont setfont ");
sb.append(opacity).append(" setgray ");
// 添加多个水印位置
sb.append("100 100 moveto (").append(text).append(") show ");
sb.append("400 400 moveto (").append(text).append(") show ");
sb.append("700 700 moveto (").append(text).append(") show ");
return sb.toString();
}
方案四:使用Free Spire.PDF for Java
Free Spire.PDF是一个免费的Java PDF库,API设计简洁易用。
1. 添加依赖
xml
<repositories>
<repository>
<id>e-iceblue</id>
<url>https://repo.e-iceblue.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>free-spire-pdf-for-java</artifactId>
<version>1.9.6</version>
</dependency>
2. Spring Boot实现
java
import com.spire.pdf.*;
import com.spire.pdf.graphics.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.awt.*;
import java.io.*;
@RestController
@RequestMapping("/api/pdf")
public class SpireWatermarkController {
@PostMapping("/watermark/spire/text")
public ResponseEntity<byte[]> addTextWatermark(@RequestParam("file") MultipartFile file)
throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 加载PDF文档
PdfDocument pdf = new PdfDocument();
pdf.loadFromStream(file.getInputStream());
// 创建文本水印
PdfWatermark watermark = new PdfWatermark("INTERNAL USE ONLY");
watermark.setFont(new PdfFont(PdfFontFamily.Helvetica, 36));
watermark.setOpacity(0.3f);
watermark.setRotation(45);
// 应用到所有页面
for (int i = 0; i < pdf.getPages().getCount(); i++) {
PdfPageBase page = pdf.getPages().get(i);
page.getWatermarks().add(watermark);
}
// 保存
pdf.saveToStream(outputStream);
pdf.close();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
@PostMapping("/watermark/spire/image")
public ResponseEntity<byte[]> addImageWatermark(@RequestParam("file") MultipartFile file)
throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 加载PDF文档
PdfDocument pdf = new PdfDocument();
pdf.loadFromStream(file.getInputStream());
// 创建图片水印
PdfWatermark watermark = new PdfWatermark("watermark.png");
watermark.setOpacity(0.3f);
watermark.setRotation(45);
// 应用到所有页面
for (int i = 0; i < pdf.getPages().getCount(); i++) {
PdfPageBase page = pdf.getPages().get(i);
page.getWatermarks().add(watermark);
}
// 保存
pdf.saveToStream(outputStream);
pdf.close();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
}
方案五:使用Aspose.PDF for Java(商业版)
Aspose.PDF是一款功能强大的商业PDF处理库,提供了最全面的功能。
1. 添加依赖
xml
<repositories>
<repository>
<id>aspose</id>
<url>https://repository.aspose.com/repo/</url>
</repository>
</repositories>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-pdf</artifactId>
<version>23.5</version>
</dependency>
2. Spring Boot实现
java
import com.aspose.pdf.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@RestController
@RequestMapping("/api/pdf")
public class AsposeWatermarkController {
@PostMapping("/watermark/aspose/text")
public ResponseEntity<byte[]> addTextWatermark(@RequestParam("file") MultipartFile file)
throws IOException {
// 加载PDF文档
Document pdfDocument = new Document(file.getInputStream());
// 创建文本印章
TextStamp textStamp = new TextStamp("CONFIDENTIAL");
textStamp.setWordWrap(true);
textStamp.setVerticalAlignment(VerticalAlignment.Center);
textStamp.setHorizontalAlignment(HorizontalAlignment.Center);
textStamp.setRotateAngle(45);
textStamp.setOpacity(0.3);
// 设置文本属性
textStamp.getTextState().setFont(FontRepository.findFont("Helvetica-Bold"));
textStamp.getTextState().setFontSize(48);
textStamp.getTextState().setForegroundColor(Color.getLightGray());
// 添加到所有页面
for (int i = 1; i <= pdfDocument.getPages().size(); i++) {
pdfDocument.getPages().get_Item(i).addStamp(textStamp);
}
// 保存
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pdfDocument.save(outputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
@PostMapping("/watermark/aspose/image")
public ResponseEntity<byte[]> addImageWatermark(@RequestParam("file") MultipartFile file)
throws IOException {
// 加载PDF文档
Document pdfDocument = new Document(file.getInputStream());
// 创建图片印章
ImageStamp imageStamp = new ImageStamp("watermark.png");
imageStamp.setWidth(200);
imageStamp.setHeight(100);
imageStamp.setVerticalAlignment(VerticalAlignment.Center);
imageStamp.setHorizontalAlignment(HorizontalAlignment.Center);
imageStamp.setRotateAngle(45);
imageStamp.setOpacity(0.3);
// 添加到所有页面
for (int i = 1; i <= pdfDocument.getPages().size(); i++) {
pdfDocument.getPages().get_Item(i).addStamp(imageStamp);
}
// 保存
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pdfDocument.save(outputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(outputStream.toByteArray());
}
}
方案对比与选择建议
| 方案 | 开源/免费 | 功能完整性 | 性能 | 学习曲线 | 适用场景 |
|---|---|---|---|---|---|
| PDFBox | ✅ 完全开源 | 中等 | 中等 | 平缓 | 个人项目、中小型企业 |
| iText | ⚠️ AGPL协议 | 优秀 | 优秀 | 中等 | 商业项目需购买许可 |
| Ghostscript | ✅ 完全开源 | 中等 | 优秀 | 较陡 | 服务端批处理 |
| Free Spire | ✅ 完全免费 | 良好 | 良好 | 平缓 | 快速开发、中小企业 |
| Aspose | ❌ 商业付费 | 卓越 | 优秀 | 平缓 | 大型企业、关键业务 |
选择建议
- 个人学习或小型项目:推荐使用PDFBox或Free Spire.PDF
- 企业级应用:如有预算,推荐Aspose.PDF;如无预算,可考虑PDFBox
- 高性能批处理:Ghostscript方案适合大批量处理
- 复杂PDF操作:iText或Aspose功能最全面
完整Spring Boot应用示例
1. 配置文件 application.yml
yaml
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
pdf:
watermark:
text: "CONFIDENTIAL"
opacity: 0.3
font-size: 48
rotation: 45
2. 统一的服务接口
java
public interface PdfWatermarkService {
byte[] addWatermark(InputStream pdfInputStream, WatermarkConfig config) throws Exception;
}
@Data
public class WatermarkConfig {
private String text = "CONFIDENTIAL";
private float opacity = 0.3f;
private int fontSize = 48;
private int rotation = 45;
private String imagePath;
private boolean isImageWatermark = false;
}
3. 通用的控制器
java
@RestController
@RequestMapping("/api/pdf/watermark")
public class WatermarkController {
@Autowired
private PdfWatermarkService watermarkService;
@PostMapping
public ResponseEntity<byte[]> addWatermark(
@RequestParam("file") MultipartFile file,
@RequestParam(required = false) String text,
@RequestParam(required = false) Float opacity,
@RequestParam(required = false) Integer fontSize) throws Exception {
WatermarkConfig config = new WatermarkConfig();
if (text != null) config.setText(text);
if (opacity != null) config.setOpacity(opacity);
if (fontSize != null) config.setFontSize(fontSize);
byte[] result = watermarkService.addWatermark(file.getInputStream(), config);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(result);
}
}
测试方法
使用Postman进行API测试:
- 创建POST请求:
http://localhost:8080/api/pdf/watermark - 设置Headers:
Content-Type: multipart/form-data - Body中选择form-data,添加file字段并选择PDF文件
- 可选的参数:text, opacity, fontSize
- 发送请求,接收带水印的PDF文件
总结
本文详细介绍了五种在Spring Boot中实现PDF水印的方案,每种方案都有其特点和适用场景。开发者可以根据项目需求、预算和技术栈选择最合适的方案。无论选择哪种方案,都需要注意:
- 性能优化:处理大文件时考虑流式处理
- 内存管理:及时关闭资源,防止内存泄漏
- 异常处理:完善的异常处理机制
- 安全考虑:防止文件上传漏洞
通过合理选择和使用这些方案,可以轻松地在Spring Boot应用中实现PDF水印功能。