easyExcel - 带图片导出

目录

  • 前言
  • 一、情景介绍
  • 二、问题分析
  • 三、代码实现
    • 1. 单图片导出
    • 2. 多图片导出
    • 3. 多图片导出(优化)


前言

Java-easyExcel入门教程:https://blog.csdn.net/xhmico/article/details/134714025

之前有介绍过如何使用 easyExcel,以及写了两个入门的 demo ,这两个 demo 能应付在开发中大多数的导入和导出需求,不过有时候面对一些复杂的表格,就会有点不够用,该篇讲述的是如何实现带图片导出


一、情景介绍

在实际的开发过程中可能会遇到需要带图片导出的表格,比如以下案例:

在这里插入图片描述

如果有多张图片要放在一个单元格中,并且单元格随着图片数量自动扩宽


二、问题分析

关于如何实现带图片导出的功能,在官方文档中有一个简单的说明:

官方文档:图片导出

在这里插入图片描述

从官方文档中给的代码示例中可以看出,带图片导出有 6 种方式

@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageDemoData {private File file;private InputStream inputStream;/*** 如果string类型 必须指定转换器,string默认转换成string*/@ExcelProperty(converter = StringImageConverter.class)private String string;private byte[] byteArray;/*** 根据url导出** @since 2.1.1*/private URL url;/*** 根据文件导出 并设置导出的位置。** @since 3.0.0-beta1*/private WriteCellData<Void> writeCellDataFile;
}

我在 D://picture 下存放了一张图片 1.png

在这里插入图片描述

D:\\excel-files 下创建了一个 excel 文件 demo01.xlsx

在这里插入图片描述

拷贝了下官方给的代码示例,改一改:

/*** 带图片导出:官方案例*/@Testpublic void exportWithPicture01() throws Exception {// 输出文件路径String fileName = "D:\\excel-files\\demo01.xlsx";// 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1:// 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接// 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片String imagePath = "D:\\picture\\1.png";try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) {List<ImageDemoData> list = ListUtils.newArrayList();ImageDemoData imageDemoData = new ImageDemoData();list.add(imageDemoData);// 放入五种类型的图片 实际使用只要选一种即可imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));imageDemoData.setFile(new File(imagePath));imageDemoData.setString(imagePath);imageDemoData.setInputStream(inputStream);imageDemoData.setUrl(new URL("https://img-blog.csdnimg.cn/direct/c11088e1790049a5b84a0fda21a271b1.png"));// 这里演示// 需要额外放入文字// 而且需要放入2个图片// 第一个图片靠左// 第二个靠右 而且要额外的占用他后面的单元格WriteCellData<Void> writeCellData = new WriteCellData<>();imageDemoData.setWriteCellDataFile(writeCellData);// 这里可以设置为 EMPTY 则代表不需要其他数据了writeCellData.setType(CellDataTypeEnum.EMPTY);writeCellData.setStringValue("额外的放一些文字");// 可以放入多个图片List<ImageData> imageDataList = new ArrayList<>();ImageData imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);// 放入2进制图片imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));// 图片类型imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);// 上 右 下 左 需要留空// 这个类似于 css 的 margin// 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。imageData.setTop(5);imageData.setRight(40);imageData.setBottom(5);imageData.setLeft(5);// 放入第二个图片imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);imageData.setTop(5);imageData.setRight(5);imageData.setBottom(5);imageData.setLeft(50);// 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格// 起点相对于当前单元格为0 当然可以不写imageData.setRelativeFirstRowIndex(0);imageData.setRelativeFirstColumnIndex(0);imageData.setRelativeLastRowIndex(0);// 前面3个可以不写  下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格// 也就是说 这个图片会覆盖当前单元格和 后面的那一格imageData.setRelativeLastColumnIndex(1);// 写入数据EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list);}}

导出结果:

在这里插入图片描述


三、代码实现


1. 单图片导出

如果每个单元格只需要存放一张图片,使用官方给的方案就绰绰有余了,通常情况下使用 URL 的方式会比较多,例如:

输出对象类:UserInfoEntity.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.BorderStyleEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.net.URL;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
// 头背景设置
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
//标题高度
@HeadRowHeight(20)
//内容高度
@ContentRowHeight(40)
//内容居中,左、上、右、下的边框显示
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
public class UserInfoEntity {@ExcelProperty(value = "名称")@ColumnWidth(10)private String name;@ExcelProperty(value = "照片")@ColumnWidth(10)private URL image;@ExcelProperty(value = "年龄")@ColumnWidth(10)private Integer age;}

代码示例:

    /*** 带图片导出:单元格只需要存放单张图片*/@Testpublic void exportWithPicture02() {// 输出文件路径String fileName = "D:\\excel-files\\demo01.xlsx";try {// 需要导出的数据List<UserInfoEntity> data = new ArrayList<>();data.add(UserInfoEntity.builder().name("米大傻").image(new URL("https://img-blog.csdnimg.cn/direct/c11088e1790049a5b84a0fda21a271b1.png")).age(18).build());data.add(UserInfoEntity.builder().name("曹大力").image(new URL("https://img-blog.csdnimg.cn/direct/bef2fdeffa644fb4aa6231d485ddaaac.png")).age(17).build());data.add(UserInfoEntity.builder().name("张大仙").image(new URL("https://img-blog.csdnimg.cn/direct/e264c110314d4ec49a7c79c51732f5f7.png")).age(18).build());// 写入数据EasyExcel.write(fileName, UserInfoEntity.class).sheet().doWrite(data);} catch (Exception e) {System.out.println("导出异常");}}

导出结果

在这里插入图片描述


2. 多图片导出

但是如果要实现情景介绍案例中每个单元格需要存放多张图片就不能仅使用官方提供的方案去解决了,通常情况下需要自己写一个拦截器,对单元格中的图片进行处理

这里我借鉴了 木木子薇夏:EasyExcel导出多张图片(URL图片)的数据(图片放到一个单元格) 的实现方式,但做了以下几个优化:

  • ① 图片宽度可自设置,单位为 px
  • ② 添加像素转换因子,默认为 32 ,如果导入的图片超出或未占满表格,可调整该参数
  • ③ 解决图片遮挡单元格的上边框和右边框的问题

转换器:ImageUrlConverter.java

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.IoUtils;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.io.InputStream;
import java.net.URL;
import java.util.List;@Slf4j
public class ImageUrlConverter implements Converter<List<URL>> {@Overridepublic Class<?> supportJavaTypeKey() {return List.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.EMPTY;}@Overridepublic List<URL> convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {return null;}@Overridepublic WriteCellData<?> convertToExcelData(List<URL> value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {// 这里进行对数据实体类URL集合处理List<ImageData> data = ListUtils.newArrayList();ImageData imageData;// for 循环一次读取for (URL url : value) {try (InputStream inputStream = url.openStream();) {byte[] bytes = IoUtils.toByteArray(inputStream);imageData = new ImageData();imageData.setImage(bytes);data.add(imageData);} catch (Exception e) {log.error("导出临时记录图片异常:", e);}}WriteCellData<?> cellData = new WriteCellData<>();if (!CollectionUtils.isEmpty(data)) {// 图片返回图片列表cellData.setImageDataList(data);cellData.setType(CellDataTypeEnum.EMPTY);} else {// 没有图片使用汉字表示cellData.setStringValue("无图");cellData.setType(CellDataTypeEnum.STRING);}return cellData;}
}

单元格拦截器:CustomImageModifyStrategy.java

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ImageData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.Units;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;/*** Excel导出单元格中有图片,图片会进行压缩,缩略,插入单元格* 注意:*      - 该策略是复制原表格中的图片进行缩放,原图片并没有删除掉,而是将尺寸设置为 0 看不到而已,但是依旧占用空间*      - 且在多 sheet 的环境下,除第一个 sheet,其余的 sheet 图片会被置 0* 目前上述问题并没有得到解决,如果在导出数据较多或者存在多个 sheet 的情况下不建议使用*/
public class CustomImageModifyStrategy implements CellWriteHandler {/*** 已经处理的Cell*/private final CopyOnWriteArrayList<String> REPEATS = new CopyOnWriteArrayList<>();/*** 单元格的图片最大张数(每列的单元格图片张数不确定,单元格宽度需按照张数最多的长度来设置)*/private final AtomicReference<Integer> MAX_IMAGE_SIZE = new AtomicReference<>(0);/*** 标记手动添加的图片,用于排除EasyExcel自动添加的图片*/private final CopyOnWriteArrayList<Integer> CREATE_PIC_INDEX = new CopyOnWriteArrayList<>();/*** 默认图片宽度(单位像素):60*/private final static int DEFAULT_IMAGE_WIDTH = 60;/*** 默认像素转换因子:32*/private final static int DEFAULT_PIXEL_CONVERSION_FACTOR = 32;/*** 图片宽度,单位像素*/private final int imageWidth;/*** 像素转换因子*/private final int pixelConversionFactor;public CustomImageModifyStrategy() {this.imageWidth = DEFAULT_IMAGE_WIDTH;this.pixelConversionFactor = DEFAULT_PIXEL_CONVERSION_FACTOR;}public CustomImageModifyStrategy(int imageWidth, int pixelConversionFactor) {this.imageWidth = imageWidth;this.pixelConversionFactor = pixelConversionFactor;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {//  在数据转换成功后 不是头就把类型设置成空if (isHead) {return;}//将要插入图片的单元格的type设置为空,下面再填充图片if (!CollectionUtils.isEmpty(cellData.getImageDataList())) {cellData.setType(CellDataTypeEnum.EMPTY);}}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {//  在 单元格写入完毕后 ,自己填充图片if (isHead || CollectionUtils.isEmpty(cellDataList)) {return;}boolean listFlag = false;Sheet sheet = cell.getSheet();List<ImageData> imageDataList = cellDataList.get(0).getImageDataList();if (!CollectionUtils.isEmpty(imageDataList)) {listFlag = true;}if (!listFlag && imageDataList == null) {return;}String key = cell.getRowIndex() + "_" + cell.getColumnIndex();if (REPEATS.contains(key)) {return;}REPEATS.add(key);if (imageDataList.size() > MAX_IMAGE_SIZE.get()) {MAX_IMAGE_SIZE.set(imageDataList.size());}int widthValue =  imageWidth * pixelConversionFactor;sheet.setColumnWidth(cell.getColumnIndex(), listFlag ? widthValue * MAX_IMAGE_SIZE.get() + pixelConversionFactor : widthValue);if (listFlag) {for (int i = 0; i < imageDataList.size(); i++) {ImageData imageData = imageDataList.get(i);if (imageData == null) {continue;}byte[] image = imageData.getImage();int index = this.insertImage(sheet, cell, image, i);CREATE_PIC_INDEX.add(index);}} else {this.insertImage(sheet, cell, imageDataList.get(0).getImage(), 0);}// 清除EasyExcel自动添加的没有格式的图片XSSFDrawing drawingPatriarch = (XSSFDrawing) sheet.getDrawingPatriarch();List<XSSFShape> shapes = drawingPatriarch.getShapes();for (int i = 0; i < shapes.size(); i++) {XSSFShape shape = shapes.get(i);if (shape instanceof XSSFPicture && !CREATE_PIC_INDEX.contains(i)) {CREATE_PIC_INDEX.add(i);XSSFPicture picture = (XSSFPicture) shape;// 这里只是将图片的大小设置为 0,所以表格依旧会存放该图片picture.resize(0);}}}/*** 重新插入一个图片** @param sheet       Excel页面* @param cell        表格元素* @param pictureData 图片数据* @param i           图片顺序*/public int insertImage(Sheet sheet, Cell cell, byte[] pictureData, int i) {int picWidth = Units.pixelToEMU(imageWidth);int index = sheet.getWorkbook().addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG);Drawing<?> drawing = sheet.getDrawingPatriarch();if (drawing == null) {drawing = sheet.createDrawingPatriarch();}CreationHelper helper = sheet.getWorkbook().getCreationHelper();ClientAnchor anchor = helper.createClientAnchor();/** 设置图片坐标* 为了不让图片遮挡单元格的上边框和右边框,故 x1、x2、y1 这几个坐标点均向后移动了一个像素点*/anchor.setDx1(Units.pixelToEMU(1) + picWidth * i);anchor.setDx2(Units.pixelToEMU(1) + picWidth + picWidth * i);anchor.setDy1(Units.pixelToEMU(1));anchor.setDy2(0);//设置图片位置int columnIndex = cell.getColumnIndex();anchor.setCol1(columnIndex);anchor.setCol2(columnIndex);int rowIndex = cell.getRowIndex();anchor.setRow1(rowIndex);anchor.setRow2(rowIndex + 1);anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);drawing.createPicture(anchor, index);return index;}}

输出对象类:StaffInfoEntity.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.BorderStyleEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.mike.common.core.reactor.excel.converter.DownloadUrlConverter;
import com.mike.common.core.reactor.excel.converter.ImageUrlConverter;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.net.URL;
import java.util.List;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
// 头背景设置
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
//标题高度
@HeadRowHeight(20)
//内容高度
@ContentRowHeight(40)
//内容居中,左、上、右、下的边框显示
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, borderLeft = BorderStyleEnum.THIN, borderTop = BorderStyleEnum.THIN, borderRight = BorderStyleEnum.THIN, borderBottom = BorderStyleEnum.THIN)
public class StaffInfoEntity {@ApiModelProperty(value = "名称")@ExcelProperty(value = "名称")@ColumnWidth(10)private String name;@ApiModelProperty(value = "照片")@ExcelProperty(value = "照片", converter = ImageUrlConverter.class)@ColumnWidth(15)private List<URL> imgList;@ApiModelProperty(value = "年龄")@ExcelProperty(value = "年龄")@ColumnWidth(10)private Integer age;}

代码示例:

    /*** 带图片导出:多图片导出*/@Testpublic void exportWithPicture03() {// 输出文件路径String fileName = "D:\\excel-files\\demo02.xlsx";try {List<URL> imgList1 = new ArrayList<>();imgList1.add(new URL("https://img-blog.csdnimg.cn/direct/c11088e1790049a5b84a0fda21a271b1.png"));imgList1.add(new URL("https://img-blog.csdnimg.cn/direct/bef2fdeffa644fb4aa6231d485ddaaac.png"));List<URL> imgList2 = new ArrayList<>();imgList2.add(new URL("https://img-blog.csdnimg.cn/direct/e264c110314d4ec49a7c79c51732f5f7.png"));List<StaffInfoEntity> entityList = new ArrayList<>();entityList.add(StaffInfoEntity.builder().name("米大傻").imgList(imgList1).age(18).build());entityList.add(StaffInfoEntity.builder().name("曹大力").imgList(imgList2).age(17).build());entityList.add(StaffInfoEntity.builder().name("张大大").age(18).build());// 图片列最大图片数AtomicReference<Integer> maxImageSize = new AtomicReference<>(0);entityList.forEach(item -> {// 最大图片数大小if (!CollectionUtils.isEmpty(item.getImgList()) && item.getImgList().size() > maxImageSize.get()) {maxImageSize.set(item.getImgList().size());}});// 导出数据EasyExcel.write(fileName, StaffInfoEntity.class).autoCloseStream(true)// 使用图片处理策略.registerWriteHandler(// 设置每张图片的宽度为 60px,转换因子为 32new CustomImageModifyStrategy(60, 32)).sheet("sheet").doWrite(entityList);} catch (Exception e) {System.out.println("导出异常");}}

导出结果

在这里插入图片描述

就是情景介绍中案例的效果了

这里我简单解释以下这个像素转换因子是怎么来的,为什么是 32

Sheet 中设置单元格宽度的方法为 setColumnWidth(),而 var2 的单位并不是像素

public interface Sheet extends Iterable<Row> {.../** 设置单元格宽度大小:* 		var1 为单元格列的索引* 		var2 为单元格的宽度*/void setColumnWidth(int var1, int var2);...
}

然后我就通过几组数据分析得出像素和 var2 之间有个转换关系,大概是 32 (2560/80=32)

在这里插入图片描述

那为什么不写死 32?因为发现在笔记本上导出的话,这个比例就不是 32 了,这个问题后续待解决,故先添加一个转换因子的参数

然后还有三个问题就是:

  • 每个单元格中实际存放的图片比所看到的图片多一倍,因为该拦截器并非是从原有的图片上进行缩放处理,而是从新复制了原有的图片进行缩放,再把原有的图片宽度设置为 0,就显得不存在了,弊端就是如果图片比较多的情况下,表格文件就会异常的大
  • 在多 sheet 下使用该策略会有问题,除第一个 sheet,其余的 sheet 图片宽度会被错误的置为 0,导致图片 消失
  • 所有的图片都是放在内存当中,图片比较大的时候容易出现内存溢出,并且导出时间会比较长

如果能把需要置 0 的图片删掉,那就挺完美的了,但是目前我没有很好的解决办法,今后如果有处理方案,我会第一时间进行改进

在数据量比较小,并且没有多个 sheet 的话,还是没啥问题的


3. 多图片导出(优化)

鉴于上述多图片导出案例所出现的三个问题,我目前能给的策略就是先让图片下载到本地,然后再写入表格,但是要及时清理磁盘中临时下载的文件

转换器:DownloadUrlConverter.java

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;/*** 该类主要是将 URL 资源下载到本地磁盘,需要配合 LocalImageModifyStrategy 使用*/
@Slf4j
public class DownloadUrlConverter implements Converter<List<URL>> {@Overridepublic Class<?> supportJavaTypeKey() {return List.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.EMPTY;}@Overridepublic List<URL> convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {return null;}@Overridepublic WriteCellData<?> convertToExcelData(List<URL> value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {// 这里进行对数据实体类URL集合处理List<String> filePaths = ListUtils.newArrayList();// 下载文件存放地址String folder = System.getProperty("java.io.tmpdir") + File.separator + "excel-temp" + File.separator;// for 循环一次读取for (URL url : value) {String path = url.getPath();String suffix = path.substring(path.indexOf("."));long millis = System.currentTimeMillis();String fileName = millis + suffix;String filePath = folder + fileName;// 下载文件到本地try {this.downloadURL(url, filePath);filePaths.add(filePath);log.info("The temporary file storage path: " + filePath);} catch (Exception e) {e.printStackTrace();}}WriteCellData<?> cellData = new WriteCellData<>();if (!CollectionUtils.isEmpty(filePaths)) {// 图片返回图片列表cellData.setStringValue("Files:" + String.join(",", filePaths));cellData.setType(CellDataTypeEnum.EMPTY);} else {cellData.setType(CellDataTypeEnum.STRING);}return cellData;}/*** 从 URL 中下载文件到指定路径* @param url 统一资源定位符* @param savePath 存放路径*/private void downloadURL(URL url, String savePath) throws IOException {URLConnection connection = url.openConnection();connection.connect();InputStream inputStream = new BufferedInputStream(connection.getInputStream());File file = new File(savePath);if (!file.getParentFile().exists()) {if (file.getParentFile().mkdirs()) {log.info("parent file had created.");}}OutputStream outputStream = new FileOutputStream(savePath);byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}inputStream.close();outputStream.close();log.info("Image downloaded successfully!");}}

拦截器:LocalImageModifyStrategy.java

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.Units;
import org.springframework.util.CollectionUtils;import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;/*** Excel导出单元格中有图片,图片会进行压缩,缩略,插入单元格(通过读取本地文件的方式)* 需要搭配 DownloadUrlConverter 使用*/
public class LocalImageModifyStrategy implements CellWriteHandler {/*** 单元格的图片最大张数(每列的单元格图片张数不确定,单元格宽度需按照张数最多的长度来设置)*/private final AtomicReference<Integer> MAX_IMAGE_SIZE = new AtomicReference<>(0);/*** 默认图片宽度(单位像素):60*/private final static int DEFAULT_IMAGE_WIDTH = 60;/*** 默认像素转换因子:32*/private final static int DEFAULT_PIXEL_CONVERSION_FACTOR = 32;/*** 图片宽度,单位像素*/private final int imageWidth;/*** 像素转换因子*/private final int pixelConversionFactor;public LocalImageModifyStrategy() {this.imageWidth = DEFAULT_IMAGE_WIDTH;this.pixelConversionFactor = DEFAULT_PIXEL_CONVERSION_FACTOR;}public LocalImageModifyStrategy(int imageWidth, int pixelConversionFactor) {this.imageWidth = imageWidth;this.pixelConversionFactor = pixelConversionFactor;}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {//  在数据转换成功后 不是头就把类型设置成空if (isHead) {return;}//将要插入图片的单元格的type设置为空,下面再填充图片if (!CollectionUtils.isEmpty(cellData.getImageDataList())) {cellData.setType(CellDataTypeEnum.EMPTY);}}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {//  在单元格写入完毕后 ,自己填充图片if (isHead || CollectionUtils.isEmpty(cellDataList)) {return;}boolean imgFlag = false;Sheet sheet = cell.getSheet();WriteCellData<?> writeCellData = cellDataList.get(0);CellDataTypeEnum type = writeCellData.getType();if (type != CellDataTypeEnum.EMPTY) {return;}String stringValue = writeCellData.getStringValue();if (stringValue == null) {return;}// 判断是否属于文件if (stringValue.startsWith("Files:")) {imgFlag = true;stringValue = stringValue.replace("Files:", "");}if (!imgFlag) {return;}List<String> filePaths = Arrays.asList(stringValue.split(","));if (filePaths.size() > MAX_IMAGE_SIZE.get()) {MAX_IMAGE_SIZE.set(filePaths.size());}int widthValue =  imageWidth * pixelConversionFactor;sheet.setColumnWidth(cell.getColumnIndex(), widthValue * MAX_IMAGE_SIZE.get() + pixelConversionFactor);for (int i = 0; i < filePaths.size(); i++) {String filePath = filePaths.get(i);// todo 这里可以对图片作一些处理,比如说压缩// ...// 读取文件byte[] image =  FileUtil.readBytes(filePath);this.insertImage(sheet, cell, image, i);}}/*** 重新插入一个图片** @param sheet       Excel页面* @param cell        表格元素* @param pictureData 图片数据* @param i           图片顺序*/public int insertImage(Sheet sheet, Cell cell, byte[] pictureData, int i) {int picWidth = Units.pixelToEMU(imageWidth);int index = sheet.getWorkbook().addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG);Drawing<?> drawing = sheet.getDrawingPatriarch();if (drawing == null) {drawing = sheet.createDrawingPatriarch();}CreationHelper helper = sheet.getWorkbook().getCreationHelper();ClientAnchor anchor = helper.createClientAnchor();/** 设置图片坐标* 为了不让图片遮挡单元格的上边框和右边框,故 x1、x2、y1 这几个坐标点均向后移动了一个像素点*/anchor.setDx1(Units.pixelToEMU(1) + picWidth * i);anchor.setDx2(Units.pixelToEMU(1) + picWidth + picWidth * i);anchor.setDy1(Units.pixelToEMU(1));anchor.setDy2(0);//设置图片位置int columnIndex = cell.getColumnIndex();anchor.setCol1(columnIndex);anchor.setCol2(columnIndex);int rowIndex = cell.getRowIndex();anchor.setRow1(rowIndex);anchor.setRow2(rowIndex + 1);anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);drawing.createPicture(anchor, index);return index;}
}

对应的地方改一改:

在这里插入图片描述

在这里插入图片描述

导出结果:

在这里插入图片描述

最后得出的效果是一样的,但是导出文件的大小小了一倍,如果对图片的清晰度要求不高的话,可以在拦截器当中添加图片压缩的逻辑,得到的 excel 文件会更小

在这里插入图片描述

不过得定时去清除临时文件

		// 下载文件存放地址String folder = System.getProperty("java.io.tmpdir") + File.separator + "excel-temp" + File.separator;

参考文章:

Easyexcel导出文件(多图片)(自用)https://blog.csdn.net/weixin_45564990/article/details/130636029

Easyexcel导出图片,固定单元格宽度自动高度保持图片比例:https://blog.csdn.net/AhogeK/article/details/133955861

EasyExcel导出多张图片(URL图片)的数据(图片放到一个单元格):https://blog.csdn.net/qq_36353248/article/details/135871478

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

适用于芯片行业的开发及管理工具:版本控制、持续集成、代码分析及项目管理工具介绍

3月28日-29日&#xff0c;2024国际集成电路展览会暨研讨会&#xff08;IIC Shanghai&#xff09;在上海成功举行。此次盛会汇聚了集成电路产业的众多领军企业&#xff0c;共同探寻和把握集成电路产业的发展脉络。 龙智携芯片研发及管理解决方案亮相展会&#xff0c;展示如何通…

什么是外汇杠杆交易?

外汇杠杆交易是目前的外汇交易市场中&#xff0c;投资者进行外汇交易的重要方式&#xff0c;通过这样的交易方式&#xff0c;投资者就有机会进行以小搏大的交易&#xff0c;他们的交易就有可能会更成功&#xff0c;因此&#xff0c;投资者应该对这样的交易方式进行了解&#xf…

springboot 集成 activemq

文章目录 一&#xff1a;说明二&#xff1a;e-car项目配置1 引入activemq依赖2 application启动类配置消息监听3 application.yml配置4 MQConfig.java 配置类5 ecar 项目中的监听6 junit 发送消息 三&#xff1a;tcm-chatgpt项目配置5 MQListener.java 监听消息 三 测试启动act…

RV1126添加gc2093摄像头

前言 上篇讲的是双目摄像头GC2053摄像头进行拉流&#xff0c;本篇介绍下如何适配添加RGB Sensor GC2093并且可以在windows上成功拉取视频流 平台背景 开发板&#xff1a;易百纳开发板 cpu: rv1126 sensor: gc2093 200w像素 2lanes type&#xff1a;MIPI kernel: 4.19.111 sys…

K8S深度解析:从入门到精通的全方位指南

目录 一、Kubernetes简介 二、Kubernetes的核心作用 三、Kubernetes架构图例 四、Kubernetes核心概念 Harbor仓库概述&#xff1a; Master----管理组件&#xff1a; API Server&#xff08;集群之心&#xff09;&#xff1a; Scheduler&#xff08;调度大师&…

基于Transformer深度学习的翻译模型(英->中)源码系统

第一步&#xff1a;Transformer介绍 Transformer是一种基于注意力机制的序列到序列模型&#xff0c;它在机器翻译任务中表现出色并逐渐成为自然语言处理领域的主流模型。Transformer模型的核心思想是使用自注意力机制&#xff08;self-attention&#xff09;来捕捉输入序列中各…

Redis系列:内存淘汰策略

1 前言 通过前面的一些文章我们知道&#xff0c;Redis的各项能力是基于内存实现的&#xff0c;相对其他的持久化存储&#xff08;如MySQL、File等&#xff0c;数据持久化在磁盘上&#xff09;&#xff0c;性能会高很多&#xff0c;这也是高速缓存的一个优势。 但是问题来了&am…

如果通过Glide 设置图片圆角

要给图片设置一个圆角,通常方法是在ImageView 标签外添加一个CardView 标签,然后设置圆角值,但是今天遇到一个问题就是 RecyclerView Item 中这样操作的话会遇到这样的一个报错: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.rec…

RLC防孤岛负载测试,你了解多少?

RLC防孤岛负载测试是一种用于检测并防止电力系统中出现孤岛现象的测试方法。孤岛现象是指在电网故障或停电的情况下&#xff0c;部分电网与主电网断开连接&#xff0c;形成一个独立的、不受控制的电力系统。这种情况下&#xff0c;如果不及时切断与孤岛的连接&#xff0c;可能会…

HarmonyOS 应用开发——入门

首先当然是华为的官方文档了&#xff0c;要认真学习: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/start-overview-0000001478061421-V2 不想花时间看&#xff0c;可以看我下面总结的干货&#xff0c;哈哈 第一个问题&#xff1a;stage架构和fa架构的区…

GPU服务器和普通服务器有何区别?

众所周知&#xff0c;服务器是网络中的重要设备&#xff0c;要接受少至几十人、多至成千上万人的访问&#xff0c;因此对服务器具有大数据量的快速吞吐、超强的稳定性、长时间运行等严格要求。 GPU服务器和普通服务器的主要区别在于硬件配置和适用场景&#xff0c;特别是处理器…

应急行业的智能安全帽(高端)

前面介绍了低端、中端安全帽&#xff0c;接着再讲讲高端安全帽。做高端安全帽的企业非常少&#xff0c;估计一只手都数的出来。确实也和智能安全帽这个领域体量有关系&#xff0c;并且他有一个新的“劲敌”——智能眼镜从其他领域瓜分原属于他的市场&#xff0c;这些都是题外话…

Cadence OrCAD学习笔记(1)OrCAD入门介绍

最近换份工作主要用到Cadence&#xff0c;之前都是用AD居多&#xff0c;所以现在也开始记录下Cadence学习过程&#xff0c;方便后面复习。 参考教程&#xff1a; OrCAD视频教程第1期&#xff1a;OrCAD入门介绍_哔哩哔哩_bilibili 本期主要介绍OrCAD的主要部件 其中用的较多的…

【软件开发规范篇】Git分支使用规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0c;产…

软考之零碎片段记录(二十七)+复习巩固(十三、十四)

学习 1. 案例题 涉及到更新的。肯能会是数据流的终点E, P, D 数据流转。可能是 P->EP->D(数据更新)P->P(信息处理)D->P(提取数据信息) 2. 案例2 补充关系图时会提示不增加新的实体。则增加关联关系 3. 案例3 用例图 extend用于拓展&#xff0c;当一个用例…

磁盘未格式化,数据恢复大揭秘

一、磁盘未格式化现象概述 在日常使用电脑的过程中&#xff0c;我们有时会遇到磁盘未格式化的提示&#xff0c;这意味着我们的磁盘突然间变得不可识别&#xff0c;所有的数据和文件都似乎消失了。这种情况常常发生在外接硬盘、U盘等存储设备上&#xff0c;给我们的工作和生活带…

【Python】爬虫-基础入门

目录 一、什么是爬虫 二、爬虫的主要用途 三、学会爬虫需要掌握的技能 四、爬虫使用的语言 五、编写爬虫需要的库&#xff0c;以python为例 六、爬虫示例-python 示例一 示例二 示例三 一、什么是爬虫 爬虫&#xff0c;又称网络爬虫或网页爬虫&#xff0c;是一种用来自…

RAG 的是与非、Rewrite 和 Rerank

有时候,我觉得人类还真是种擅长画地为牢的动物,因为突然发现,当人们以文化/理念的名义形成团体/圈子的时候,其结局都不可避免地走向了筛选和区分的道路。或许,大家都不约而同地笃信,在成年人的世界里,那条不成文的社交潜规则——“只筛选不教育,只选择不改变”。与千百…

16.Blender 基础渲染工作流程及安装ACES

安装插件和菜单栏设置 在菜单栏的编辑里打开偏好设置 里面的插件界面 搜索node 给第三个打勾 点击安装&#xff0c;导入cat插件 安装完后&#xff0c;一定要打勾&#xff0c;选择上cat插件 这样N窗口才会显示MMD选项 导入场景 点击打开 把输出模式的帧率改为30fps 按…

Java学习1:java入门

java入门 1.介绍Java java——sun公司——被甲骨文收购 一开始叫Oak&#xff0c;后期改名为java; java之父詹姆斯高斯林 企业级应用开发 JavaSE JavaEE JavaME 高级编程语言 2.搭建开发环境 JDK8&#xff0c;JDK11&#xff0c;JDK17————>LTS长期支持版 ps:在…