首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java 实现现有 Excel 文件添加水印(先删除旧水印,再添加新水印)

Java 实现现有 Excel 文件添加水印(先删除旧水印,再添加新水印)

作者头像
崔认知
发布2026-03-16 21:18:11
发布2026-03-16 21:18:11
1070
举报
文章被收录于专栏:nobodynobody

在企业办公自动化、数据报表分发或文档安全管控等场景中,我们常常需要对 Excel 文件添加水印,以标识文档状态(如“机密”、“草稿”、“内部使用”等)或防止信息被非法复制传播。然而,现实中的需求往往更为复杂:目标 Excel 文件可能已经包含水印,直接叠加新水印会导致视觉混乱、格式错乱,甚至影响阅读体验。

因此,一个健壮的水印处理方案必须具备两个核心能力:

  1. 识别并清除已有水印
  2. 精准添加新的水印

本文将基于 Apache POI 库,详细讲解如何使用 Java 实现这一完整流程,适用于 .xlsx 格式的 Excel 文件,并提供可运行的代码示例与实用建议。

一、Excel 水印的本质

首先需要明确:Excel 并不像 Word 或 PDF 那样原生支持“水印”功能。所谓“Excel 水印”,通常是通过以下方式模拟实现的:

  • 在工作表绘图层(Drawing Layer)插入半透明图片
  • 使用文本框(TextBox)绘制旋转文字
  • 设置工作表背景图(但无法打印,且全表覆盖)
  • 利用页眉/页脚(仅在打印时可见)

其中,在绘图层插入 PNG 图片 是最常用、最灵活的方式,也是本文采用的技术路线。该方式的优点是:

  • 可控制位置、大小、透明度
  • 支持任意文字或图形
  • 在屏幕显示和打印时均可见(除非用户手动隐藏对象)

二、技术选型:Apache POI

Apache POI 是 Java 处理 Microsoft Office 文档的事实标准库。对于 .xlsx 文件,我们使用其 XSSF 模块(基于 Open XML 标准)。

三、代码实现详解

3.1 Maven 依赖

代码语言:javascript
复制
<dependencies>
    <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
    <!-- 可选:增强图像处理兼容性 -->
    <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-core</artifactId>
        <version>3.9.4</version>
    </dependency>
</dependencies>

3.2 生成水印图片工具类

代码语言:javascript
复制
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

publicclass WaterMarkHandler {

    public static ByteArrayOutputStream createWaterMark(String content) throws IOException {
        int width = 500;
        int height = 300;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 获取bufferedImage对象
        String fontType = "微软雅黑";
        int fontStyle = Font.BOLD;
        int fontSize = 20;
        Font font = new Font(fontType, fontStyle, fontSize);
        Graphics2D g2d = image.createGraphics(); // 获取Graphics2d对象
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        g2d.setColor(new Color(0, 0, 0, 20)); //设置字体颜色和透明度,最后一个参数为透明度
        g2d.setStroke(new BasicStroke(1)); // 设置字体
        g2d.setFont(font); // 设置字体类型  加粗 大小
        g2d.rotate(-0.5, (double) image.getWidth() / 2, (double) image.getHeight() / 2);//设置倾斜度
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(content, (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        return os;
    }

3.3 主处理逻辑:清除旧水印 + 添加新水印

代码语言:javascript
复制
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;


publicfinalclass ExcelWatermarkUtil {



    /**
     * 给 Excel 添加水印(内存到内存)
     *
     * @param excelData     原始 Excel 文件的 byte[]
     * @param watermarkText 水印文字(如 "CONFIDENTIAL")
     * @return 添加水印后的 Excel byte[]
     * @throws IOException
     */
    publicstaticbyte[] addWatermarkToExcel(byte[] excelData, String watermarkText) throws IOException {
        ByteArrayOutputStream waterMark = WaterMarkHandler.createWaterMark(watermarkText);

        try (ByteArrayInputStream bis = new ByteArrayInputStream(excelData);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            XSSFWorkbook workbook = new XSSFWorkbook(bis);


            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                XSSFSheet sheet = workbook.getSheetAt(i);
                try {
                    sheet.getCTWorksheet().unsetPicture();
                }catch (Exception e){
                   e.printStackTrace();
                }

            }


            int pictureIdx = workbook.addPicture(waterMark.toByteArray(), Workbook.PICTURE_TYPE_PNG);

            POIXMLDocumentPart poixmlDocumentPart = workbook.getAllPictures().get(pictureIdx);


            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                XSSFSheet sheet = workbook.getSheetAt(i);
                PackagePartName ppn = poixmlDocumentPart.getPackagePart().getPartName();
                String relType = XSSFRelation.IMAGES.getRelation();
                PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
                sheet.getCTWorksheet().addNewPicture().setId(pr.getId());
            }

            workbook.write(bos);

            return bos.toByteArray();
        }
    }
}

四、示例

代码语言:javascript
复制

 public void addWatermarkToExcel() throws IOException {
        byte[] fileBytes = Files.readAllBytes(Paths.get("/Users/me/logs/test.xlsx"));
        byte[] addWatermarkToExcel = ExcelWatermarkUtil.addWatermarkToExcel(fileBytes, "王叔叔 12122121212 2025-12-05 09:17:35");

        Files.write(Paths.get("/Users/me/logs/test111.xlsx"), addWatermarkToExcel);
    }

原水印:

替换后的水印:

五、关键问题与优化建议

1. 如何精准识别“水印”而非普通图片?

当前代码删除了所有图片,这在仅有水印的报表中可行。但在含 Logo、图表的复杂文件中会误删。建议:

  • 添加水印时记录元信息:例如将水印图片命名为 "WATERMARK_CONFIDENTIAL",后续通过 picture.getPictureData().getFileName() 判断。
  • 基于位置/尺寸过滤:水印通常居中、大尺寸、低透明度。
  • 使用自定义属性:通过 POI 的 CTPicture 底层 API 添加标记(高级用法)。

2. 水印位置适配不同分辨率

上述代码使用固定行列范围。若需全表居中覆盖,可动态计算:

代码语言:javascript
复制
int lastRow = sheet.getLastRowNum();
int lastCol = sheet.getRow(0).getLastCellNum();
anchor.setCol1(0); anchor.setRow1(0);
anchor.setCol2(lastCol); anchor.setRow2(lastRow);

3. 性能优化

  • 水印图片可缓存(相同文字无需重复生成)
  • 对于只读场景,可使用 ReadOnlySharedStringsTable 提升大文件读取速度

六、总结

本文完整展示了如何使用 Java 和 Apache POI 实现 “删除旧水印 → 添加新水印” 的 Excel 处理流程。虽然 Excel 本身不支持原生水印,但通过绘图层插入半透明图片的方式,可以高效、灵活地满足业务需求。

最佳实践:在系统设计初期就规范水印的添加方式(如统一命名、位置、尺寸),可极大简化后续的识别与替换逻辑。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 认知科技技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Excel 水印的本质
  • 二、技术选型:Apache POI
  • 三、代码实现详解
    • 3.1 Maven 依赖
    • 3.2 生成水印图片工具类
    • 3.3 主处理逻辑:清除旧水印 + 添加新水印
  • 四、示例
  • 原水印:
  • 替换后的水印:
  • 五、关键问题与优化建议
    • 1. 如何精准识别“水印”而非普通图片?
    • 2. 水印位置适配不同分辨率
    • 3. 性能优化
  • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档