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())。这种方式适用于数据量较小的情况,因为它会消耗更多的内存。
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网如果有地址失效了,可以私信我,后续看到会补充)

浙公网安备 33010602011771号