Spring Boot 实现PDF水印的完整指南

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

根据操作系统选择安装方式:

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 ❌ 商业付费 卓越 优秀 平缓 大型企业、关键业务

选择建议

  1. 个人学习或小型项目:推荐使用PDFBox或Free Spire.PDF
  2. 企业级应用:如有预算,推荐Aspose.PDF;如无预算,可考虑PDFBox
  3. 高性能批处理:Ghostscript方案适合大批量处理
  4. 复杂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测试:

  1. 创建POST请求:http://localhost:8080/api/pdf/watermark
  2. 设置Headers:Content-Type: multipart/form-data
  3. Body中选择form-data,添加file字段并选择PDF文件
  4. 可选的参数:text, opacity, fontSize
  5. 发送请求,接收带水印的PDF文件

总结

本文详细介绍了五种在Spring Boot中实现PDF水印的方案,每种方案都有其特点和适用场景。开发者可以根据项目需求、预算和技术栈选择最合适的方案。无论选择哪种方案,都需要注意:

  1. 性能优化:处理大文件时考虑流式处理
  2. 内存管理:及时关闭资源,防止内存泄漏
  3. 异常处理:完善的异常处理机制
  4. 安全考虑:防止文件上传漏洞

通过合理选择和使用这些方案,可以轻松地在Spring Boot应用中实现PDF水印功能。

相关推荐
SuniaWang2 小时前
Spring Boot + Spring AI + Vue 3 + TypeScript + Milvus 项目实战
java·人工智能·spring boot·spring·typescript·框架·前端开发
摇滚侠2 小时前
Spring SpringMVC SpringBoot SpringCloud SpringAI 分别是做什么的
spring boot·spring·spring cloud
青柠代码录2 小时前
【JWT】整合 SpringBoot 实现认证和鉴权
spring boot
桂花很香,旭很美2 小时前
[7天实战入门Go语言后端] Day 1:Go 基础入门——环境、语法、错误处理与并发
开发语言·后端·golang
Coder_Boy_2 小时前
从单体并发工具类到分布式并发:思想演进与最佳实践
java·spring boot·分布式·微服务
❀͜͡傀儡师2 小时前
SpringBoot渗透扫描Scan工具
java·spring boot·后端
G探险者9 小时前
聊一聊 CLI:为什么真正的工程能力,都藏在命令行里?
后端
hzc098765432110 小时前
Spring Integration + MQTT
java·后端·spring
是梦终空10 小时前
计算机毕业设计266—基于Springboot+Vue3的共享单车管理系统(源代码+数据库)
数据库·spring boot·vue·课程设计·计算机毕业设计·源代码·共享单车系统