最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實現(xiàn)PDF文件添加文字圖片和圖片水印的實戰(zhàn)指南

 更新時間:2026年02月04日 09:40:46   作者:qq_29757467  
在日常開發(fā)中,PDF 加水印是非常常見的需求,比如文件脫敏、版權(quán)標(biāo)識等場景,本文將基于 iTextPDF 庫實現(xiàn) PDF 文件的文字水印和圖片水印添加,需要的朋友可以參考下

在日常開發(fā)中,PDF 加水印是非常常見的需求,比如文件脫敏、版權(quán)標(biāo)識等場景。本文將基于 iTextPDF 庫實現(xiàn) PDF 文件的文字水印和圖片水印添加,并結(jié)合 MinIO 文件下載場景,實現(xiàn)下載 PDF 時自動添加水印的實戰(zhàn)功能。

一、技術(shù)選型與依賴配置

1. 核心依賴

本文使用iTextPDF作為 PDF 處理核心庫,該庫是 Java 生態(tài)中處理 PDF 的經(jīng)典工具,支持 PDF 的創(chuàng)建、修改、水印添加等幾乎所有操作。

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 版本為經(jīng)典穩(wěn)定版,兼容大部分項目;如果使用 6.x + 版本,包名和部分 API 會有調(diào)整,需注意適配。

二、核心工具類實現(xiàn)(PDFUtil)

封裝通用的 PDF 水印添加工具類,支持文字水印圖片水印兩種方式,適配 Windows/Linux 系統(tǒng)的字體差異。

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. 創(chuàng)建PDF Stamper(用于修改PDF)
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPath));

        // 3. 加載水印圖片
        Image image = Image.getInstance(imagePath);

        // 4. 設(shè)置水印透明度
        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++) {
            // 獲取當(dāng)前頁面尺寸
            float pageWidth = reader.getPageSize(i + 1).getWidth();
            float pageHeight = reader.getPageSize(i + 1).getHeight();
            
            // 獲取頁面的上層內(nèi)容(水印需放在上層)
            content = stamper.getOverContent(i + 1);
            content.setGState(gs); // 應(yīng)用透明度
            
            // 循環(huán)添加水?。宽?列7行,均勻分布)
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 7; k++) {
                    // 設(shè)置圖片水印位置
                    image.setAbsolutePosition(pageWidth / 3 * j - 30, pageHeight / 7 * k - 20);
                    content.addImage(image);
                }
            }
        }

        // 6. 關(guān)閉資源
        stamper.close();
        reader.close();
    }

    /**
     * PDF添加文字水印
     * @param srcPath 源PDF文件路徑
     * @param destPath 加水印后PDF輸出路徑
     * @param waterMarkText 水印文字內(nèi)容
     * @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系統(tǒng)字體(解決中文亂碼)
        String fontPath = null;
        String osName = System.getProperties().getProperty("os.name");
        if (osName.startsWith("win") || osName.startsWith("Win")) {
            // Windows系統(tǒng)宋體
            fontPath = "C:\\Windows\\Fonts\\SIMSUN.TTC,1";
        } else {
            // Linux系統(tǒng)中文字體(需提前安裝中文字體)
            fontPath = "/usr/share/fonts/chinese/TrueType/uming.ttf";
        }

        // 3. 創(chuàng)建基礎(chǔ)字體(支持中文)
        BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        // 4. 設(shè)置水印透明度
        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); // 設(shè)置字體和字號
            
            // 循環(huán)添加水?。宽?列7行,旋轉(zhuǎn)25度,避免遮擋內(nèi)容)
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 7; k++) {
                    // 參數(shù)說明:對齊方式、文字內(nèi)容、X坐標(biāo)、Y坐標(biāo)、旋轉(zhuǎn)角度
                    content.showTextAligned(Element.ALIGN_CENTER, waterMarkText, 
                                           pageWidth / 3 * j + 90, pageHeight / 7 * k, 25);
                }
            }
            content.endText();
        }

        // 6. 關(guān)閉資源
        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();
                }
            }
        }
    }
}

關(guān)鍵代碼說明

  1. 字體適配:區(qū)分 Windows 和 Linux 系統(tǒng)的中文字體路徑,解決中文水印亂碼問題;
  2. 透明度設(shè)置:通過PdfGState的setFillOpacity設(shè)置水印透明度,0.2 為最佳視覺效果;
  3. 水印分布:按頁面尺寸均分,每頁 3 列 7 行添加水印,保證全屏覆蓋且不密集;
  4. 文字旋轉(zhuǎn):設(shè)置 25 度旋轉(zhuǎn)角,避免水印與正文文字平行,提升視覺體驗。

三、實戰(zhàn)場景:MinIO 下載 PDF 自動加水印

在實際項目中,經(jīng)常需要從 MinIO 下載文件時,對 PDF 文件自動添加水印后再返回給前端。以下是完整的接口實現(xiàn):

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;

    // 臨時文件夾(需提前創(chuàng)建,建議配置在application.yml)
    private String destFolder = "/tmp/pdf_watermark";
    // 水印文字常量
    private static final String WATER_MARK = "天津港項目有限公司";

    /**
     * 通用附件下載
     * @param fileId 文件ID
     * @param response 響應(yīng)對象
     * @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. 設(shè)置響應(yīng)頭(文件下載)
        response.setContentType(statObject.contentType());
        response.setHeader("Content-Disposition", 
                "attachment;filename=" + URLEncoder.encode(uploadFilePo.getRealName(), "UTF-8"));

        // 5. 區(qū)分PDF和非PDF文件處理
        if ("pdf".equals(uploadFilePo.getFileType())) {
            // 5.1 從MinIO獲取PDF文件輸入流
            InputStream inputStream = minioTemplate.getObject(
                    bucketName, uploadFilePo.getFilePath(), uploadFilePo.getVersionId());

            // 5.2 創(chuàng)建臨時文件(避免并發(fā)沖突,使用雪花ID命名)
            String tempDir = destFolder + File.separator + IdUtils.snowflakeNewId();
            File tempDirFile = new File(tempDir);
            if (!tempDirFile.exists()) {
                tempDirFile.mkdirs(); // 創(chuàng)建多級目錄
            }
            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寫入響應(yīng)流
            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());
        }
    }
}

實戰(zhàn)要點

  1. 臨時文件處理:使用雪花 ID 創(chuàng)建唯一臨時目錄,避免多線程下載時文件沖突;
  2. 資源釋放:下載完成后強制刪除臨時文件,防止磁盤空間泄露;
  3. 響應(yīng)頭設(shè)置:通過URLEncoder.encode處理文件名,解決中文文件名亂碼;
  4. 異常處理:實際項目中需增加 try-catch,保證臨時文件無論是否異常都能刪除。

四、常見問題與解決方案

Linux 系統(tǒng)中文亂碼:
原因:Linux 默認(rèn)無中文字體;
解決:安裝fonts-wqy-microhei或fonts-chinese字體包,修改工具類中的字體路徑。

水印覆蓋 PDF 內(nèi)容:
原因:使用getOverContent(上層內(nèi)容),如果 PDF 有表單 / 批注可能被遮擋;
解決:改用getUnderContent(下層內(nèi)容),水印放在文字下方。

iTextPDF 版本沖突:
原因:項目中其他依賴引入了低版本 iTextPDF;
解決:在 pom.xml 中排除沖突依賴,指定固定版本。

臨時文件刪除失敗:
原因:文件流未關(guān)閉;
解決:在forceDelete前關(guān)閉所有流,或使用FileDeleteStrategy.FORCE強制刪除。

五、總結(jié)

本文基于 iTextPDF 實現(xiàn)了 PDF 文字 / 圖片水印的通用工具類,并結(jié)合 MinIO 文件下載場景完成了實戰(zhàn)落地,核心要點如下:

  1. 依賴配置:核心引入 iTextPDF 和中文支持包,解決基礎(chǔ)依賴;
  2. 工具類封裝:適配多系統(tǒng)字體、控制水印透明度和分布,保證通用性;
  3. 實戰(zhàn)落地:下載 PDF 時通過臨時文件中轉(zhuǎn),加水印后返回并清理臨時文件;
  4. 問題兜底:針對亂碼、資源泄露、并發(fā)沖突等問題提供解決方案。

該方案已在實際項目中驗證,可直接復(fù)用,適用于企業(yè)級 PDF 文件版權(quán)保護、脫敏等場景。

以上就是Java實現(xiàn)PDF文件添加文字圖片和圖片水印的實戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于Java PDF添加文字和圖片水印的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用Java和ffmpeg把音頻和視頻合成視頻的操作方法

    使用Java和ffmpeg把音頻和視頻合成視頻的操作方法

    這篇文章主要介紹了使用Java和ffmpeg把音頻和視頻合成視頻,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的工作或?qū)W習(xí)具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • IDEA中thymeleaf語法沒有提示的問題及解決

    IDEA中thymeleaf語法沒有提示的問題及解決

    這篇文章主要介紹了IDEA中thymeleaf語法沒有提示的問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Springboot使用redisson實現(xiàn)分布式鎖的代碼示例

    Springboot使用redisson實現(xiàn)分布式鎖的代碼示例

    在實際項目中,某些場景下可能需要使用到分布式鎖功能,那么實現(xiàn)分布式鎖有多種方式,常見的如mysql分布式鎖、zookeeper分布式鎖、redis分布式鎖,本文介紹springboot如何使用redisson實現(xiàn)分布式鎖,需要的朋友可以參考下
    2023-06-06
  • Java數(shù)據(jù)存儲的“雙子星”對決(Map和Set的區(qū)別)

    Java數(shù)據(jù)存儲的“雙子星”對決(Map和Set的區(qū)別)

    文章主要介紹了Java中Map和Set兩種數(shù)據(jù)結(jié)構(gòu)的定義、實現(xiàn)、方法及應(yīng)用場景,Map用于存儲鍵值對,鍵唯一,值可重復(fù);Set用于存儲唯一元素,無序,兩者都提供了豐富的操作方法,如添加、刪除、查找等,感興趣的朋友一起看看吧
    2025-02-02
  • 使用Jackson 處理 null 或者 空字符串

    使用Jackson 處理 null 或者 空字符串

    這篇文章主要介紹了使用Jackson 處理 null 或者 空字符串,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 2020最新版SSM框架整合教程

    2020最新版SSM框架整合教程

    這篇文章主要介紹了2020最新版SSM框架整合教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Springmvc的運行流程圖文詳解

    Springmvc的運行流程圖文詳解

    今天小編就為大家分享一篇關(guān)于Springmvc的運行流程圖文詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • java線程的run()沒有返回值怎么辦?

    java線程的run()沒有返回值怎么辦?

    java線程的run()沒有返回值怎么辦?本文給出了java線程的run()返回值為空的解決辦法,感興趣的小伙伴們可以參考一下
    2016-01-01
  • JVM垃圾回收器選型與調(diào)優(yōu)方式

    JVM垃圾回收器選型與調(diào)優(yōu)方式

    本文介紹了JVM垃圾回收器選型與調(diào)優(yōu)的關(guān)鍵原理和實戰(zhàn)技巧,包括基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)、API使用、常見問題及解決方案、性能優(yōu)化建議等,并提供了實際項目中的最佳實踐,通過理解這些知識,可以有效提升代碼質(zhì)量
    2026-05-05
  • Java實現(xiàn)郵件發(fā)送的過程及代碼詳解

    Java實現(xiàn)郵件發(fā)送的過程及代碼詳解

    這篇文章主要介紹了Java實現(xiàn)郵件發(fā)送的過程及代碼詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07

最新評論

呼和浩特市| 白河县| 漳浦县| 营口市| 镶黄旗| 双峰县| 米林县| 盖州市| 京山县| 大厂| 张北县| 洪雅县| 贵南县| 伊春市| 垫江县| 无棣县| 重庆市| 石狮市| 新民市| 永平县| 和平区| 教育| 辽中县| 大足县| 如皋市| 积石山| 临泽县| 池州市| 彭阳县| 永寿县| 肇庆市| 哈尔滨市| 突泉县| 泾川县| 扎赉特旗| 广昌县| 五指山市| 射阳县| 临颍县| 苍南县| 忻城县|