Java实现PDF文件添加文字图片和图片水印的实战指南

 更新时间:2026年02月04日 09:40:46   作者:qq_29757467  
在日常开发中,PDF 加水印是非常常见的需求,比如文件脱敏、版权标识等场景,本文将基于 iTextPDF 库实现 PDF 文件的文字水印和图片水印添加,需要的朋友可以参考下

在日常开发中,PDF 加水印是非常常见的需求,比如文件脱敏、版权标识等场景。本文将基于 iTextPDF 库实现 PDF 文件的文字水印和图片水印添加,并结合 MinIO 文件下载场景,实现下载 PDF 时自动添加水印的实战功能。

一、技术选型与依赖配置

1. 核心依赖

本文使用iTextPDF作为 PDF 处理核心库,该库是 Java 生态中处理 PDF 的经典工具,支持 PDF 的创建、修改、水印添加等几乎所有操作。

Maven 依赖配置

<!-- iTextPDF 核心依赖 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.3</version>
</dependency>

<!-- 中文字体支持 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

<!-- MinIO客户端依赖(如果用到MinIO下载场景) -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.7</version>
</dependency>

<!-- Apache Commons IO 工具类 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>cy>

Gradle 依赖配置

// iTextPDF 核心
implementation 'com.itextpdf:itextpdf:5.5.13.3'
// 中文支持
implementation 'com.itextpdf:itext-asian:5.2.0'
// MinIO
implementation 'io.minio:minio:8.5.7'
// Commons IO
implementation 'commons-io:commons-io:2.15.1'

注意:iTextPDF 5.x 版本为经典稳定版,兼容大部分项目;如果使用 6.x + 版本,包名和部分 API 会有调整,需注意适配。

二、核心工具类实现(PDFUtil)

封装通用的 PDF 水印添加工具类,支持文字水印图片水印两种方式,适配 Windows/Linux 系统的字体差异。

package com.tydt.util;

import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;

import java.io.File;
import java.io.FileOutputStream;

/**
 * PDF添加水印工具类
 * 支持文字水印、图片水印两种方式
 */
public class PDFUtil {

    /**
     * PDF添加图片水印
     * @param srcPath 源PDF文件路径
     * @param destPath 加水印后PDF输出路径
     * @param imagePath 水印图片路径
     * @throws Exception 处理异常
     */
    public static void addPDFImageWaterMark(String srcPath, String destPath, String imagePath) throws Exception {
        // 1. 读取源PDF文件
        PdfReader reader = new PdfReader(srcPath);
        // 2. 创建PDF Stamper(用于修改PDF)
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPath));

        // 3. 加载水印图片
        Image image = Image.getInstance(imagePath);

        // 4. 设置水印透明度
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(0.2f); // 透明度0.2(0-1,值越小越淡)
        PdfContentByte content = null;

        // 5. 遍历PDF所有页面添加水印
        int totalPages = reader.getNumberOfPages();
        for (int i = 0; i < totalPages; i++) {
            // 获取当前页面尺寸
            float pageWidth = reader.getPageSize(i + 1).getWidth();
            float pageHeight = reader.getPageSize(i + 1).getHeight();
            
            // 获取页面的上层内容(水印需放在上层)
            content = stamper.getOverContent(i + 1);
            content.setGState(gs); // 应用透明度
            
            // 循环添加水印(每页3列7行,均匀分布)
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 7; k++) {
                    // 设置图片水印位置
                    image.setAbsolutePosition(pageWidth / 3 * j - 30, pageHeight / 7 * k - 20);
                    content.addImage(image);
                }
            }
        }

        // 6. 关闭资源
        stamper.close();
        reader.close();
    }

    /**
     * PDF添加文字水印
     * @param srcPath 源PDF文件路径
     * @param destPath 加水印后PDF输出路径
     * @param waterMarkText 水印文字内容
     * @throws Exception 处理异常
     */
    public static void addPDFWaterMark(String srcPath, String destPath, String waterMarkText) throws Exception {
        // 1. 读取源PDF文件
        PdfReader reader = new PdfReader(srcPath);
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPath));

        // 2. 适配Windows/Linux系统字体(解决中文乱码)
        String fontPath = null;
        String osName = System.getProperties().getProperty("os.name");
        if (osName.startsWith("win") || osName.startsWith("Win")) {
            // Windows系统宋体
            fontPath = "C:\\Windows\\Fonts\\SIMSUN.TTC,1";
        } else {
            // Linux系统中文字体(需提前安装中文字体)
            fontPath = "/usr/share/fonts/chinese/TrueType/uming.ttf";
        }

        // 3. 创建基础字体(支持中文)
        BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        // 4. 设置水印透明度
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(0.2f); // 文字透明度
        PdfContentByte content = null;

        // 5. 遍历所有页面添加水印
        int totalPages = reader.getNumberOfPages();
        for (int i = 0; i < totalPages; i++) {
            float pageWidth = reader.getPageSize(i + 1).getWidth();
            float pageHeight = reader.getPageSize(i + 1).getHeight();
            
            content = stamper.getOverContent(i + 1);
            content.setGState(gs);
            content.beginText();
            content.setFontAndSize(baseFont, 20); // 设置字体和字号
            
            // 循环添加水印(每页3列7行,旋转25度,避免遮挡内容)
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 7; k++) {
                    // 参数说明:对齐方式、文字内容、X坐标、Y坐标、旋转角度
                    content.showTextAligned(Element.ALIGN_CENTER, waterMarkText, 
                                           pageWidth / 3 * j + 90, pageHeight / 7 * k, 25);
                }
            }
            content.endText();
        }

        // 6. 关闭资源
        stamper.close();
        reader.close();
    }

    // 测试方法
    public static void main(String[] args) {
        // 批量给指定目录的PDF添加文字水印
        File pdfDir = new File("D:\\wps");
        for (File pdfFile : pdfDir.listFiles()) {
            if (pdfFile.getName().endsWith(".pdf")) {
                try {
                    System.out.println("正在处理:" + pdfFile.getName());
                    String srcPath = "D:\\wps\\" + pdfFile.getName();
                    String destPath = "D:\\wps\\waterMark\\" + pdfFile.getName();
                    // 添加文字水印
                    addPDFWaterMark(srcPath, destPath, "天津港项目有限公司");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

关键代码说明

  1. 字体适配:区分 Windows 和 Linux 系统的中文字体路径,解决中文水印乱码问题;
  2. 透明度设置:通过PdfGState的setFillOpacity设置水印透明度,0.2 为最佳视觉效果;
  3. 水印分布:按页面尺寸均分,每页 3 列 7 行添加水印,保证全屏覆盖且不密集;
  4. 文字旋转:设置 25 度旋转角,避免水印与正文文字平行,提升视觉体验。

三、实战场景:MinIO 下载 PDF 自动加水印

在实际项目中,经常需要从 MinIO 下载文件时,对 PDF 文件自动添加水印后再返回给前端。以下是完整的接口实现:

import io.minio.StatObjectResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.net.URLEncoder;

/**
 * 通用文件下载接口(PDF自动加水印)
 */
@RestController
@RequestMapping("/file")
public class FileDownloadController {

    @Autowired
    private MinioTemplate minioTemplate;
    @Autowired
    private MinioConfig minioConfig;
    @Autowired
    private FileService fileService;

    // 临时文件夹(需提前创建,建议配置在application.yml)
    private String destFolder = "/tmp/pdf_watermark";
    // 水印文字常量
    private static final String WATER_MARK = "天津港项目有限公司";

    /**
     * 通用附件下载
     * @param fileId 文件ID
     * @param response 响应对象
     * @throws Exception 处理异常
     */
    @ApiOperation("通用附件下载")
    @GetMapping("/download")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileId", value = "文件ID", required = true)
    })
    public void download(String fileId, HttpServletResponse response) throws Exception {
        // 1. 校验MinIO桶是否存在
        String bucketName = minioConfig.getBucketName();
        boolean bucketExists = minioTemplate.bucketExists(bucketName);
        Assert.isTrue(bucketExists, "文件存储桶不存在!");

        // 2. 查询文件信息
        UploadFilePO uploadFilePo = fileService.queryFileDetail(fileId);
        Assert.notNull(uploadFilePo, "文件不存在或已被删除!");

        // 3. 获取MinIO文件元信息
        StatObjectResponse statObject = minioTemplate.getStatObjectResponse(
                bucketName, uploadFilePo.getFilePath(), uploadFilePo.getVersionId());

        // 4. 设置响应头(文件下载)
        response.setContentType(statObject.contentType());
        response.setHeader("Content-Disposition", 
                "attachment;filename=" + URLEncoder.encode(uploadFilePo.getRealName(), "UTF-8"));

        // 5. 区分PDF和非PDF文件处理
        if ("pdf".equals(uploadFilePo.getFileType())) {
            // 5.1 从MinIO获取PDF文件输入流
            InputStream inputStream = minioTemplate.getObject(
                    bucketName, uploadFilePo.getFilePath(), uploadFilePo.getVersionId());

            // 5.2 创建临时文件(避免并发冲突,使用雪花ID命名)
            String tempDir = destFolder + File.separator + IdUtils.snowflakeNewId();
            File tempDirFile = new File(tempDir);
            if (!tempDirFile.exists()) {
                tempDirFile.mkdirs(); // 创建多级目录
            }
            String tempSrcPdf = tempDir + File.separator + uploadFilePo.getFileName();
            String tempDestPdf = tempDir + File.separator + uploadFilePo.getRealName();

            // 5.3 将MinIO的PDF写入临时文件
            org.apache.commons.io.FileUtils.copyToFile(inputStream, new File(tempSrcPdf));

            // 5.4 给PDF添加水印
            PDFUtil.addPDFWaterMark(tempSrcPdf, tempDestPdf, WATER_MARK);

            // 5.5 将加水印后的PDF写入响应流
            FileUtils.writeBytes(tempDestPdf, response.getOutputStream());

            // 5.6 删除临时文件(避免磁盘占用)
            org.apache.commons.io.FileUtils.forceDelete(tempDirFile);
        } else {
            // 非PDF文件直接下载
            InputStream inputStream = minioTemplate.getObject(
                    bucketName, uploadFilePo.getFilePath(), uploadFilePo.getVersionId());
            IOUtils.copy(inputStream, response.getOutputStream());
        }
    }
}

实战要点

  1. 临时文件处理:使用雪花 ID 创建唯一临时目录,避免多线程下载时文件冲突;
  2. 资源释放:下载完成后强制删除临时文件,防止磁盘空间泄露;
  3. 响应头设置:通过URLEncoder.encode处理文件名,解决中文文件名乱码;
  4. 异常处理:实际项目中需增加 try-catch,保证临时文件无论是否异常都能删除。

四、常见问题与解决方案

Linux 系统中文乱码:
原因:Linux 默认无中文字体;
解决:安装fonts-wqy-microhei或fonts-chinese字体包,修改工具类中的字体路径。

水印覆盖 PDF 内容:
原因:使用getOverContent(上层内容),如果 PDF 有表单 / 批注可能被遮挡;
解决:改用getUnderContent(下层内容),水印放在文字下方。

iTextPDF 版本冲突:
原因:项目中其他依赖引入了低版本 iTextPDF;
解决:在 pom.xml 中排除冲突依赖,指定固定版本。

临时文件删除失败:
原因:文件流未关闭;
解决:在forceDelete前关闭所有流,或使用FileDeleteStrategy.FORCE强制删除。

五、总结

本文基于 iTextPDF 实现了 PDF 文字 / 图片水印的通用工具类,并结合 MinIO 文件下载场景完成了实战落地,核心要点如下:

  1. 依赖配置:核心引入 iTextPDF 和中文支持包,解决基础依赖;
  2. 工具类封装:适配多系统字体、控制水印透明度和分布,保证通用性;
  3. 实战落地:下载 PDF 时通过临时文件中转,加水印后返回并清理临时文件;
  4. 问题兜底:针对乱码、资源泄露、并发冲突等问题提供解决方案。

该方案已在实际项目中验证,可直接复用,适用于企业级 PDF 文件版权保护、脱敏等场景。

以上就是Java实现PDF文件添加文字图片和图片水印的实战指南的详细内容,更多关于Java PDF添加文字和图片水印的资料请关注脚本之家其它相关文章!

相关文章

  • 解决mybatisplus MetaObjectHandler 失效的问题

    解决mybatisplus MetaObjectHandler 失效的问题

    本文主要介绍了解决mybatisplus MetaObjectHandler 失效的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Java的@Transactional、@Aysnc、事务同步问题详解

    Java的@Transactional、@Aysnc、事务同步问题详解

    这篇文章主要介绍了Java的@Transactional、@Aysnc、事务同步问题详解,现在我们需要在一个业务方法中插入一个用户,这个业务方法我们需要加上事务,然后插入用户后,我们要异步的方式打印出数据库中所有存在的用户,需要的朋友可以参考下
    2023-11-11
  • IDEA实用好用插件推荐及使用方法教程详解(必看)

    IDEA实用好用插件推荐及使用方法教程详解(必看)

    这篇文章主要介绍了IDEA实用好用插件推荐及使用方法教程,本文通过实例截图相结合给大家介绍的非常详细,对大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • java实现简单的推箱子小游戏

    java实现简单的推箱子小游戏

    这篇文章主要为大家详细介绍了java实现简单的推箱子小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • 解决weblogic部署springboot项目步骤及可能会出现的问题

    解决weblogic部署springboot项目步骤及可能会出现的问题

    这篇文章主要介绍了解决weblogic部署springboot项目步骤及可能会出现的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • SpringBoot如何集成Netty

    SpringBoot如何集成Netty

    这篇文章主要介绍了SpringBoot如何集成Netty问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • JavaEE账号注册模拟网站邮箱激活

    JavaEE账号注册模拟网站邮箱激活

    这篇文章主要为大家详细介绍了JavaEE账号注册模拟网站邮箱激活,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Java项目打包发布到maven私仓常见的几种方式

    Java项目打包发布到maven私仓常见的几种方式

    这篇文章主要介绍了项目打包发布到maven私仓常见的几种方式,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下
    2021-03-03
  • 使用Java通过OAuth协议验证发送微博的教程

    使用Java通过OAuth协议验证发送微博的教程

    这篇文章主要介绍了使用Java通过OAuth协议验证发送微博的教程,使用到了新浪微博为Java开放的API weibo4j,需要的朋友可以参考下
    2016-02-02
  • springdoc-openapi用户界面实现将请求设置为HTTPS

    springdoc-openapi用户界面实现将请求设置为HTTPS

    文章介绍了在使用Swagger进行API测试时遇到HTTPS请求问题的解决方案,包括将服务地址添加到配置文件和在SpringDoc配置类中设置服务列表,从而使得Swagger页面能够正确发起HTTPS请求
    2026-01-01

最新评论