EasyExcel 无法读取图片?用poi写了一个工具类

easyexcel,poi · 浏览次数 : 0

小编点评

本文主要介绍了在开发中经常需要实现的 Excel 导入导出功能,特别是使用 EasyExcel 时的图片读写功能。由于 EasyExcel 不支持图片读取,因此本文提出了一种使用 POI 实现图片读取和写入的方法,并提供了一个工具类来实现这一功能。 1. **EasyExcel 与 POI**:本文首先介绍了 EasyExcel 和 POI 的关系,指出 EasyExcel 通常用于处理 Excel 导入导出,而 POI 则提供了更复杂的图片处理功能。 2. **图片读取方法**:接着,文章详细说明了如何使用 POI 从 Excel 文件中读取图片。具体步骤包括获取工作簿、遍历工作表中的绘图对象、提取图片数据等。 3. **工具类实现**:为了简化代码,作者创建了一个工具类,该类封装了图片读取的逻辑,并提供了一些静态方法来处理图片上传、存储等功能。 4. **实体类与注解**:文章还展示了如何为实体类添加 ExcelImageProperty 注解,以指示哪些字段应该映射到图片数据。 5. **EasyExcel 与图片写入**:最后,文章讨论了如何在 EasyExcel 中读取非图片数据,并使用工具类读取图片数据。同时,也介绍了如何使用 EasyExcel 的 API 来导出包含图片的 Excel 文件。 总的来说,本文提供了一种结合 EasyExcel 和 POI 的方法来实现 Excel 中的图片读写功能,这种方法不仅适用于 EasyExcel,也可以作为其他 Java 应用程序的一部分来实现图片处理功能。

正文

在平时的开发中,经常要开发 Excel 的导入导出功能。一般使用 poi 或者 EasyExcel 开发,使用 poi 做 excel 比较复杂,大部分开发都会使用 EasyExcel 因为一行代码就能实现导入和导出的功能。但是 EasyExcel 不支持图片的读的操作,本文操作如何实现图片的读和写的功能。

在 EasyExcel 官网的常见问题可以看到 EasyExcel 是不支持读取图片的功能。

读取图片

poi 读取图片

poi 支持图片的读取,使用 poi 写一个工具类,支持图片的读取,首先添加 maven 依赖, EasyExcel 含有 poi 依赖,无需额外添加 poi 依赖:

<!-- easyexcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>net.sf.jxls</groupId>
    <artifactId>jxls-core</artifactId>
    <version>1.0.6</version>
</dependency>

读取图片核心代码如下:

Workbook workbook = WorkbookFactory.create(inputStream);
// 默认读取第一页
XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
List<POIXMLDocumentPart> documentPartList = sheet.getRelations();
for (POIXMLDocumentPart part : documentPartList) {
    if (part instanceof XSSFDrawing) {
        XSSFDrawing drawing = (XSSFDrawing) part;
        List<XSSFShape> shapes = drawing.getShapes();
        for (XSSFShape shape : shapes) {
            XSSFPicture picture = (XSSFPicture) shape;
            XSSFClientAnchor anchor = picture.getPreferredSize();
            CTMarker marker = anchor.getFrom();
            int row = marker.getRow();
            int col = marker.getCol();
            // 从第2行开始
            if (row > 0 && row <= size) {
                PictureData pictureData = picture.getPictureData();
                String extension = pictureData.suggestFileExtension();
                byte[] bytes = pictureData.getData();
             }
        }
    }
}    

读取图片流程:

  • 首先要获取第一页(sheet)数据 workbook.getSheetAt(0)
  • 遍历 sheet.getRelations() 提取 XSSFDrawing,也就是图片数据。
  • 每一行遍历数据数据,获取 byte 字节流。

可能代码复制在 idea 会提示某些方法不存在,这里就需要核对 poi 版本,上面引用的 EasyExcel 的版本是 3.0.5,里面的 poi 版本是 4.1.2

封装工具类

通过上面的代码可以获取到图片的字节流,然后对字节流做上传图片或者服务存储图片处理,但是每个读取都写一遍这种方式,代码就比较冗余了。所以就需要将上面代码封装成一个工具类。

比如上传一个文件,需要将数据赋值给两个字段 name 和 imageStr:

@ExcelProperty("姓名")
private String name;

@ExcelProperty(value = "图片")
private String imageStr;

首先配置一个 ExcelImageProperty 注解,确定哪列的图片需要赋值给对应的图片字段

@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelImageProperty {

    String[] value() default {""};

    /**
     * 图片在第几列 1开始
     * @return
     */
    int index() default -1;
}

imageStr 对应第二列,字段上 ExcelImageProperty 注解的 index = 2,上面的实体修改如下:

@ExcelProperty("姓名")
private String name;

@ExcelProperty(value = "图片")
@ExcelImageProperty(index = 2)
private String imageStr;

写好实体和注解后,再写一个工具类。

@Slf4j
public class ExcelReadImageUtil {

    public static <T> void readImage(InputStream inputStream, List<T> list) {
        try {
            Workbook workbook = WorkbookFactory.create(inputStream);
            // 默认读取第一页
            XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
            List<POIXMLDocumentPart> documentPartList = sheet.getRelations();
            Integer size = list.size();
            for (POIXMLDocumentPart part : documentPartList) {
                if (part instanceof XSSFDrawing) {
                    XSSFDrawing drawing = (XSSFDrawing) part;
                    List<XSSFShape> shapes = drawing.getShapes();
                    for (XSSFShape shape : shapes) {
                        XSSFPicture picture = (XSSFPicture) shape;
                        XSSFClientAnchor anchor = picture.getPreferredSize();
                        CTMarker marker = anchor.getFrom();
                        int row = marker.getRow();
                        int col = marker.getCol();
                        // 从第2行开始
                        if (row > 0 && row <= size) {
                            PictureData pictureData = picture.getPictureData();
                            String extension = pictureData.suggestFileExtension();
                            byte[] bytes = pictureData.getData();
                            InputStream imageInputStream = new ByteArrayInputStream(bytes);
                            //String url = iTxCosService.uploadFile(new ByteArrayInputStream(bytes), UUID.randomUUID() + "." + extension);
                            for (int i = 0; i < size; i++) {
                                T item = list.get(i);
                                Class clazz = item.getClass();
                                Field[] fields = clazz.getDeclaredFields();
                                for (Field field : fields) {
                                    if (field.isAnnotationPresent(ExcelImageProperty.class)) {
                                        ExcelImageProperty excelImageProperty = field.getAnnotation(ExcelImageProperty.class);
                                        int index = excelImageProperty.index();
                                        if (index == col + 1 && row - 1 == i) {
                                            field.setAccessible(true);
                                            field.set(item,new String(bytes));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException | IllegalAccessException e) {
            e.printStackTrace();
            log.error("read image error {}",e);
        }
    }
}

传参一个列表,通过获取读取输入流获取到图片,赋值给对应的字段。

  • 此模板是一个列表模版,不支持自定义模板。
  • 使用 poi 读取图片,第二行读取数据,遍历每列数据,符合注解字段就赋值。一般获取到输入流后会上传图片,返回一个地址,这里仅仅就获取字节流,赋值给对应的字段。

使用 EasyExcel 读取非图片数据和工具类读取图片数据:

InputStream inputStream = multipartFile.getInputStream();
List<DemoExcelInput> demoExcelInputs = EasyExcelFactory.read(multipartFile.getInputStream()).head(DemoExcelInput.class).sheet().doReadSync();
ExcelReadImageUtil.readImage(inputStream,demoExcelInputs);

inputStream 不能重复使用,不然会报错 inputStream close 错误。

写图片

EasyExcel 支持多种格式的写图片,包括:

  • URL
  • InputStream
  • byte[]
  • File
  • 自定义转换器

添加写的实体:

@Data
public class DemoExcelInput {

    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty(value = "图片",converter = ExcelUrlImageConverter.class)
    private String imageStr;

    @ExcelProperty("url")
    private URL imageUrl;

    @ExcelProperty("inputstream")
    private InputStream inputStream;

    @ExcelProperty("bytes")
    private byte[] bytes;
}

读取图片

List<DemoExcelInput> demoExcelInputs = new ArrayList<>();
DemoExcelInput demoExcelInput = new DemoExcelInput();
demoExcelInput.setName("aa");
String url = "https://p26-passport.byteacctimg.com/img/user-avatar/82b069ce17bb5b0eccb7ee67d3f6f3bc~180x180.awebp";
demoExcelInput.setImageStr(url);
demoExcelInput.setImageUrl(new URL(url));
demoExcelInputs.add(demoExcelInput);

InputStream inputStream = new URL(url).openStream();
demoExcelInput.setInputStream(inputStream);
byte[] bytes = IoUtils.toByteArray(new URL(url).openStream());
demoExcelInput.setBytes(bytes);

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName= "导出excel模板";
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
response.setHeader("Content-disposition","attachment;filename*=utf-8''"+encodedFileName+".xlsx");
EasyExcel.write(response.getOutputStream(),DemoExcelInput.class).sheet("模板").doWrite(demoExcelInputs);

导出文件截图:

但是上面的 imageStr 对应的 String 类型 EasyExcel 并不支持,但是却能导出图片,这就需要使用到自定义转换器

创建 ExcelUrlImageConverter 转换器:

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.IoUtils;
import com.alibaba.excel.util.StringUtils;

import java.io.InputStream;
import java.net.URL;

public class ExcelUrlImageConverter implements Converter<String> {

    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) throws Exception {
        String urlString = context.getValue();
        if (StringUtils.isBlank(urlString)) {
            return new WriteCellData<>("");
        }
        URL url = new URL(urlString);
        InputStream inputStream = url.openStream();
        byte[] bytes = IoUtils.toByteArray(inputStream);
        return new WriteCellData<>(bytes);
    }
}

将读取到图片流转到对象 WriteCellData 中,就能写图片了。

与EasyExcel 无法读取图片?用poi写了一个工具类相似的内容:

EasyExcel 无法读取图片?用poi写了一个工具类

在平时的开发中,经常要开发 Excel 的导入导出功能。一般使用 poi 或者 EasyExcel 开发,使用 poi 做 excel 比较复杂,大部分开发都会使用 EasyExcel 因为一行代码就能实现导入和导出的功能。但是 EasyExcel 不支持图片的读的操作,本文操作如何实现图片的读和写

easyExcel多行表头设定不同样式和特定单元格设定样式的实现

前言 有个需求,需要设置Excel导出的样式,样式如下图所示,有三个表头行,第一个表头行需要加粗和灰色背景,另外两个表头行使用另外的样式,并且当测试结果单元格出现x或者未通过的时候,设置其为红色字体。 实现步骤 写入ExcelSheet的部分代码 for (Map.Entry

厉害了!12秒将百万数据通过EasyExcel导入MySQL数据库中

一、写在开头 我们在上一篇文章中提到了通过EasyExcel处理Mysql百万数据的导入功能(一键看原文),当时我们经过测试数据的反复测验,100万条放在excel中的数据,4个字段的情况下,导入数据库,平均耗时500秒,这对于我们来说肯定难以接受,今天我们就来做一次性能优化。 二、性能瓶颈分析 一

EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!

一、写在开头 今天终于更新新专栏 《EfficientFarm》 的第二篇博文啦,本文主要来记录一下对于EasyExcel的高效应用,包括对MySQL数据库百万级数据量的导入与导出操作,以及性能的优化(争取做到秒级性能!)。 二、如何做技术选型 其实在市面上我们有很多常用的excel操作依赖库,除了

[EasyExcel] 导出合并单元格

前言 使用spring boot 对excel 进行操作在平时项目中要经常使用。常见通过jxl和poi 的方式进行操作。但他们都存在一个严重的问题就是非常的耗内存。这里介绍一种 Easy Excel 工具来对excel进行操作。 一、Easy Excel是什么? EasyExcel是阿里巴巴开源的一

SpringBoot 整合 EasyExcel 实现自由导入导出,太强了

在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。 之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,性能不够好,严重的时候,还会出现内存异常的现象。 今天我给大家推荐一款性能更好的 Excel 导

在表格开发中,如何选择适合自己的处理工具?

引言 GcExcel和EasyExcel都是卓越的高性能Excel处理库。GcExcel是由葡萄城公司开发,可用于Java和.Net平台;而EasyExcel是阿里巴巴开发的基于Java的开源Excel处理库。 在本文中,我们将对GcExcel和EasyExcel进行比较,帮助读者在实际场景中做出明

比 poi导入导出更好用的 EasyExcel使用小结

转载请注明出处: 官方文档: https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read 1.简洁 Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,p