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.hqwc.cn/news/661828.html

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

相关文章

24.什么是跨域?解决方案有哪些?

为什么会出现跨域问题 存在浏览器同源策略&#xff0c;所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到&#xff0c;跨域限制主要的目的就是为了用户的上网安全。 同源策略导致的跨域是浏览器单方面拒绝响应数据&#xff0c;服务器端是处理完毕…

java+jsp+Oracle+Tomcat 记账管理系统论文(二)

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ ➡️点击免费下载全套资料:源码、数据库、部署教程、论文、答辩ppt一条龙服务 ➡️有部署问题可私信联系 ⬆️⬆️⬆️​​​​​​​⬆️…

从0到1手写注册中心Registry之主从数据同步

集群选主完成后&#xff0c;从节点需要从主节点同步数据。 一、快照数据 同步的快照数据是描述注册中心的服务信息。 registry: 每个服务对应的实例&#xff1b;versions&#xff1a;每个服务的版本信息&#xff1b;timestamps&#xff1a;每个服务的时间戳&#xff1b;vers…

工作任务管理平台B端实战项目作品集+WebApp项目源文件 figma格式

首先&#xff0c;作品集是什么&#xff1f;通常应该包含什么内容&#xff1f;为什么大家都在做自己的作品集呢&#xff1f; 作品集是个人或公司展示其过往工作成果的集合&#xff0c;通常包括各种专案、作品或成就的范例&#xff0c;用以展示创建者的技能、经验和专业水平。 …

视频改字祝福 豪车装X系统源码uniapp前端源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 uniapp视频改字祝福 豪车装X系统源码 全开源。 创意无限&#xff01;AI视频改字祝福&#xff0c;豪车装X系统源码开源&#xff0c;打造个性化祝福视频不再难&#xff01; 想要为你的…

Sobel算法:边缘提取的原理与实践【基于python、C++基于opencv的代码实现!!】

Sobel算法&#xff1a;深入解析边缘检测的原理与实现 在图像处理领域&#xff0c;边缘检测是一项至关重要的任务。其中&#xff0c;Sobel算法以其高效和稳定的性能&#xff0c;成为边缘检测中的常用方法之一。本文将深入解析Sobel算法的原理与实现&#xff0c;带您了解如何通过…

Python 与 TensorFlow2 生成式 AI(一)

原文&#xff1a;zh.annas-archive.org/md5/d06d282ea0d9c23c57f0ce31225acf76 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 序言 “想象力比知识更重要。” – 阿尔伯特爱因斯坦&#xff0c;《爱因斯坦关于宇宙宗教和其他见解与格言》&#xff08;2009&#xff09;…

打破失联困境:门店如何利用AI智能名片B2B2C商城小程序重构与消费者的紧密连接?

在如今这个消费者行为日益碎片化的时代&#xff0c;门店经营者们时常感叹&#xff1a;消费者进店如同一场不期而遇的缘分&#xff0c;然而一旦离开门店&#xff0c;就仿佛消失在茫茫人海中&#xff0c;难以再觅其踪迹。这种“进店靠缘分&#xff0c;离店就失联”的困境&#xf…

GPT3 终极指南(一)

原文&#xff1a;zh.annas-archive.org/md5/6de8906c86a2711a5a84c839bec7e073 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 GPT-3&#xff0c;或者说是 Generative Pre-trained Transformer 3&#xff0c;是由 OpenAI 开发的基于 Transformer 的大型语言模型…

[高质量]2024五一数学建模A题保奖思路+代码(后续会更新)

你的点赞收藏是我继续更新的最大动力&#xff0c;可点击文末卡片获取更多资料 你是否在寻找数学建模比赛的突破点&#xff1f; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 年华东杯&#xff08;A题&#xff09;的全面解析包。这个解决方案包不仅包括完整的代…

CSS高级选择器

一、属性选择器 以value开头的att属性的E元素&#xff1a;E[att^"value"]{ ;} a[href^http]{background-color"red";} css a[href^http]{background-color"red"; } html <!DOCTYPE html> <html lang"en"> <head&…

78、贪心-跳跃游戏

思路 方法1: canJump01 - 使用递归&#xff08;回溯法&#xff09; 这个方法是通过递归实现的&#xff0c;它从数组的第一个位置开始&#xff0c;尝试所有可能的跳跃步数&#xff0c;直到达到数组的最后一个位置或遍历完所有的可能性。 思路&#xff1a; 如果数组为空或者长…