JAVA使用easyexcel框架导出excel添加水印

 

easyexcel的版本引入的poi版本有点低 ,所以这里做了排查和新增,只要保证项目的poi版本支撑就行 根据自己的来 也可以先把代码添加进去,看哪些报错来决定改哪个依赖

 

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.7</version>
            <exclusions>
                <exclusion>
                    <artifactId>poi-ooxml</artifactId>
                    <groupId>org.apache.poi</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>poi</artifactId>
                    <groupId>org.apache.poi</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>poi-ooxml-schemas</artifactId>
                    <groupId>org.apache.poi</groupId>
                </exclusion>
            </exclusions>
        </dependency>

单独引入版本poi

<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.3</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>

 

还引入了hutool工具类 ,如果不想引入可以自己改下代码

 

工具类

WaterMarkHandler.java

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WaterMarkHandler implements SheetWriteHandler {
    private final static Logger LOGGER = LoggerFactory.getLogger(WaterMarkHandler.class);

    private boolean hasLineBreak;

    private String[] paramArray;

    public WaterMarkHandler(boolean hasLineBreak, String... paramArray) {
        super();
        this.hasLineBreak = hasLineBreak;
        this.paramArray = paramArray;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        try {
            WaterMarkUtil.insertWaterMarkTextToXlsx(writeWorkbookHolder.getWorkbook(),
                    writeSheetHolder.getSheet(), this.hasLineBreak, this.paramArray);
        } catch (Exception e) {
            LOGGER.error("添加水印时出错啦!", e);
        }
    }
}

 

WaterMarkUtil.java

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
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.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;


@Slf4j
public class WaterMarkUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(WaterMarkUtil.class);

    /**
     * 描述:Excel 导出添加水印
     *
     */
    public static void insertWaterMarkTextToXlsx(Workbook workbook, Sheet sheet,
                                                 boolean hasLineBreak, String... paramArray) throws IOException {
        String split = " ";
        if (hasLineBreak) {
            split = System.lineSeparator();
        }

        String waterMarkText = "";
        StringBuilder waterMarkTextBuilder = new StringBuilder();
        for (int i = 0; i < paramArray.length; i++) {
            String param = paramArray[i];
            if (StrUtil.isNotBlank(param)) {
                if (i == 0) {
                    waterMarkTextBuilder.append(param);
                } else {
                    waterMarkTextBuilder.append(split).append(param);
                }
            }
        }
        waterMarkText = waterMarkTextBuilder.toString();

        if (workbook instanceof SXSSFWorkbook) {
            insertWaterMarkTextToXlsx((SXSSFWorkbook) workbook, (SXSSFSheet) sheet, waterMarkText, hasLineBreak);
        } else if (workbook instanceof XSSFWorkbook) {
            insertWaterMarkTextToXlsx((XSSFWorkbook) workbook, (XSSFSheet) sheet, waterMarkText, hasLineBreak);
        }
    }


    /**
     * 描述:给 SXSSFWorkbook 添加水印
     */
    public static void insertWaterMarkTextToXlsx(SXSSFWorkbook workbook, SXSSFSheet sheet,
                                                 String waterMarkText, boolean hasLineBreak) throws IOException {
        BufferedImage image = createWatermarkImage(waterMarkText, hasLineBreak);
        ByteArrayOutputStream imageOs = new ByteArrayOutputStream();
        ImageIO.write(image, "png", imageOs);
        int pictureIdx = workbook.addPicture(imageOs.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG);
        XSSFPictureData pictureData = (XSSFPictureData) workbook.getAllPictures().get(pictureIdx);

        // 这里由于 SXSSFSheet 没有 getCTWorksheet() 方法,通过反射取出 _sh 属性
        XSSFSheet shReflect = (XSSFSheet) ReflectUtil.getFieldValue(sheet, "_sh");
        PackagePartName ppn = pictureData.getPackagePart().getPartName();
        String relType = XSSFRelation.IMAGES.getRelation();
        PackageRelationship pr = shReflect.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
        shReflect.getCTWorksheet().addNewPicture().setId(pr.getId());
    }

    /**
     * 描述:给 XSSFWorkbook 添加水印
     *
     */
    public static void insertWaterMarkTextToXlsx(XSSFWorkbook workbook, XSSFSheet sheet,
                                                 String waterMarkText, boolean hasLineBreak) throws IOException {
        BufferedImage image = createWatermarkImage(waterMarkText, hasLineBreak);
        ByteArrayOutputStream imageOs = new ByteArrayOutputStream();
        ImageIO.write(image, "png", imageOs);
        int pictureIdx = workbook.addPicture(imageOs.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG);
        XSSFPictureData pictureData = workbook.getAllPictures().get(pictureIdx);

        PackagePartName ppn = pictureData.getPackagePart().getPartName();
        String relType = XSSFRelation.IMAGES.getRelation();
        PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
        sheet.getCTWorksheet().addNewPicture().setId(pr.getId());
    }

    /**
     * 描述:创建水印图片
     */
    public static BufferedImage createWatermarkImage(String waterMark, boolean hasLineBreak) {

        //加载外部字体文件
        Font font = null;
        try {
            InputStream awardFontFile = Thread.currentThread().getContextClassLoader().getResourceAsStream("font/msyh.ttf");
            font = Font.createFont(Font.TRUETYPE_FONT, awardFontFile).deriveFont(Font.BOLD);
            //设置font字体大小
            font = font.deriveFont(20f);
        } catch (Exception e) {
            log.warn("加载外部字体文件失败", e);
        }

        // 创建一个模版 Graphics2D 上下文来获取文本的尺寸
        BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        Graphics2D tempGraphics = tempImage.createGraphics();
        tempGraphics.setFont(font);
        FontMetrics tempMetrics = tempGraphics.getFontMetrics();

        int textWidthMax = 0;
        int textHeightSum = 0;
        if (hasLineBreak) {
            // 换行水印,分割并绘制文本
            String[] lines = waterMark.split(System.lineSeparator());
            for (String line : lines) {
                // 计算文本宽度和高度
                int textWidth = tempMetrics.stringWidth(line);
                int textHeight = tempMetrics.getHeight();
                if (textWidth > textWidthMax) {
                    textWidthMax = textWidth;
                }
                textHeightSum += textHeight;
            }
        } else {
            // 计算文本宽度和高度
            textWidthMax = tempMetrics.stringWidth(waterMark);
            textHeightSum = tempMetrics.getHeight();
        }
        // 清除模版 Graphics2D
        tempGraphics.dispose();

        // 因为后面会把文字旋转 45 %,所以上面算出的 textWidthMax 和 textHeightSum 实际上是斜边
        textWidthMax = (int) (textWidthMax / Math.sqrt(2));
        textHeightSum = (int) (textHeightSum / Math.sqrt(2));

        int imageEdgeLength = textWidthMax + textHeightSum;
        imageEdgeLength = Math.max(imageEdgeLength, 200) + 100;

        // 创建一个大小恰好适合文本的图像
        BufferedImage image = new BufferedImage(imageEdgeLength, imageEdgeLength, BufferedImage.TYPE_INT_ARGB);
        // 背景透明 开始
        Graphics2D graphics = image.createGraphics();
        // 设定画笔颜色
        graphics.setColor(new Color(0, 0, 0, 40));
        // 设置字体
        graphics.setStroke(new BasicStroke(1));
        // 设置画笔字体
        graphics.setFont(font);
        // 设置倾斜度
        graphics.rotate(0 - (Math.PI / 4), (double) image.getWidth() / 2, (double) image.getHeight() / 2);

        // 设置字体平滑
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 获取FontMetrics用于测量文本宽度
        FontMetrics metrics = graphics.getFontMetrics();

        double baseY = (imageEdgeLength - textHeightSum) / 2;
        if (hasLineBreak) {
            // 换行水印,分割并绘制文本
            String[] lines = waterMark.split(System.lineSeparator());
            for (String line : lines) {
                byte[] bytes = line.getBytes(StandardCharsets.UTF_8);
                line = new String(bytes, StandardCharsets.UTF_8);
//                LOGGER.info("当前要添加的这行水印是:{}", line);
                // 计算文本宽度
                int textWidth = metrics.stringWidth(line);

                // 计算图像的中心点
                int centerX = imageEdgeLength / 2;

                // 计算文本起始位置(使文本居中)
                int x = centerX - textWidth / 2;

                // 绘制文本
                graphics.drawString(line, x, (int) baseY);

                // 计算下一行的y坐标
                baseY += metrics.getHeight();
            }
        } else {
            byte[] bytes = waterMark.getBytes(StandardCharsets.UTF_8);
            waterMark = new String(bytes, StandardCharsets.UTF_8);
//            LOGGER.info("当前要添加的这行水印是:{}", waterMark);
            // 计算文本宽度和高度
            int textWidth = metrics.stringWidth(waterMark);

            // 计算图像的中心点
            int centerX = imageEdgeLength / 2;

            // 计算文本起始位置(使文本居中)
            int x = centerX - textWidth / 2;

            // 绘制文本
            graphics.drawString(waterMark, x, (int) baseY);
        }

        // 释放画笔
        graphics.dispose();

        return image;
    }
}

 

 

font/msyh.ttf:水印字体说明参考:https://www.cnblogs.com/pxblog/p/18320150

 

 

使用伪代码

        // 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment; filename=日志.xlsx");

        // 创建ExcelWriter,注册水印处理器
        ExcelWriter writer = EasyExcel.write(response.getOutputStream())
                .inMemory(true)
                .registerWriteHandler(new WaterMarkHandler(true, "第一个水印内容", "第二个水印内容"))
                .build();

        try {
            // 写入Excel文件
            writer.write(respVOList, EasyExcel.writerSheet("数据列表").head(RespVO.class).build());
        } finally {
            // 关闭ExcelWriter,释放资源
            writer.finish();
        }

 

这个注意:

  • .inMemory(true):将 Excel 文件的写入过程完全在内存中进行处理。这意味着,Excel 文件会先被完全生成并保存在内存中,然后一次性输出到响应流(例如 response.getOutputStream())。这种方式适用于数据量较小的情况,因为它会消耗更多的内存。

 

posted @ 2025-04-15 14:48  yvioo  阅读(701)  评论(0)    收藏  举报