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

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

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

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

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

1. 核心依賴

本文使用iTextPDF作為 PDF 處理核心庫(kù),該庫(kù)是 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下載場(chǎng)景) -->
<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)定版,兼容大部分項(xiàng)目;如果使用 6.x + 版本,包名和部分 API 會(huì)有調(diào)整,需注意適配。

二、核心工具類實(shí)現(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所有頁(yè)面添加水印
        int totalPages = reader.getNumberOfPages();
        for (int i = 0; i < totalPages; i++) {
            // 獲取當(dāng)前頁(yè)面尺寸
            float pageWidth = reader.getPageSize(i + 1).getWidth();
            float pageHeight = reader.getPageSize(i + 1).getHeight();
            
            // 獲取頁(yè)面的上層內(nèi)容(水印需放在上層)
            content = stamper.getOverContent(i + 1);
            content.setGState(gs); // 應(yīng)用透明度
            
            // 循環(huán)添加水?。宽?yè)3列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. 遍歷所有頁(yè)面添加水印
        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è)置字體和字號(hào)
            
            // 循環(huán)添加水?。宽?yè)3列7行,旋轉(zhuǎn)25度,避免遮擋內(nèi)容)
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 7; k++) {
                    // 參數(shù)說(shuō)明:對(duì)齊方式、文字內(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();
    }

    // 測(cè)試方法
    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, "天津港項(xiàng)目有限公司");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

關(guān)鍵代碼說(shuō)明

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

三、實(shí)戰(zhàn)場(chǎng)景:MinIO 下載 PDF 自動(dòng)加水印

在實(shí)際項(xiàng)目中,經(jīng)常需要從 MinIO 下載文件時(shí),對(duì) PDF 文件自動(dòng)添加水印后再返回給前端。以下是完整的接口實(shí)現(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自動(dòng)加水?。?
 */
@RestController
@RequestMapping("/file")
public class FileDownloadController {

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

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

    /**
     * 通用附件下載
     * @param fileId 文件ID
     * @param response 響應(yīng)對(duì)象
     * @throws Exception 處理異常
     */
    @ApiOperation("通用附件下載")
    @GetMapping("/download")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileId", value = "文件ID", required = true)
    })
    public void download(String fileId, HttpServletResponse response) throws Exception {
        // 1. 校驗(yàn)MinIO桶是否存在
        String bucketName = minioConfig.getBucketName();
        boolean bucketExists = minioTemplate.bucketExists(bucketName);
        Assert.isTrue(bucketExists, "文件存儲(chǔ)桶不存在!");

        // 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)建臨時(shí)文件(避免并發(fā)沖突,使用雪花ID命名)
            String tempDir = destFolder + File.separator + IdUtils.snowflakeNewId();
            File tempDirFile = new File(tempDir);
            if (!tempDirFile.exists()) {
                tempDirFile.mkdirs(); // 創(chuàng)建多級(jí)目錄
            }
            String tempSrcPdf = tempDir + File.separator + uploadFilePo.getFileName();
            String tempDestPdf = tempDir + File.separator + uploadFilePo.getRealName();

            // 5.3 將MinIO的PDF寫入臨時(shí)文件
            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 刪除臨時(shí)文件(避免磁盤占用)
            org.apache.commons.io.FileUtils.forceDelete(tempDirFile);
        } else {
            // 非PDF文件直接下載
            InputStream inputStream = minioTemplate.getObject(
                    bucketName, uploadFilePo.getFilePath(), uploadFilePo.getVersionId());
            IOUtils.copy(inputStream, response.getOutputStream());
        }
    }
}

實(shí)戰(zhàn)要點(diǎn)

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

四、常見(jiàn)問(wèn)題與解決方案

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

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

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

臨時(shí)文件刪除失?。?/strong>
原因:文件流未關(guān)閉;
解決:在forceDelete前關(guān)閉所有流,或使用FileDeleteStrategy.FORCE強(qiáng)制刪除。

五、總結(jié)

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

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

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

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

相關(guān)文章

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

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

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

    IDEA中thymeleaf語(yǔ)法沒(méi)有提示的問(wèn)題及解決

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

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

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

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

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

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

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

    2020最新版SSM框架整合教程

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

    Springmvc的運(yùn)行流程圖文詳解

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

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

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

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

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

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

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

最新評(píng)論

肇源县| 化州市| 青铜峡市| 历史| 延津县| 沁阳市| 奎屯市| 金华市| 西城区| 陆川县| 永仁县| 沙雅县| 永寿县| 甘德县| 巴彦淖尔市| 南开区| 敦化市| 抚州市| 景谷| 汝南县| 新宾| 即墨市| 安吉县| 哈尔滨市| 浙江省| 登封市| 德保县| 开江县| 大丰市| 米林县| 新邵县| 浠水县| 石河子市| 洪洞县| 邵阳县| 蒙城县| 宿州市| 瑞丽市| 河池市| 中江县| 廊坊市|