在平时的开发中,经常要开发 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写了一个工具类的更多相关文章

  1. java里poi操作excel的工具类(兼容各版本)

    转: java里poi操作excel的工具类(兼容各版本) 下面是文件内具体内容,文件下载: import java.io.FileNotFoundException; import java.io. ...

  2. //读取配置文件(属性文件)的工具类-ConfigManager

    package com.pb.news.util; import java.io.IOException;import java.io.InputStream;import java.sql.Resu ...

  3. 写文件的工具类,输出有格式的文件(txt、json/csv)

    import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io. ...

  4. 学习Java AES加解密字符串和文件方法,然后写个简单工具类

    Reference Core Java Volume Ⅱ 10th Edition 1 对称加密 "Java密码扩展"包含了一个Cipher,它是所有密码算法的超类.通过getIn ...

  5. Java读取Maven工程下的配置文件,工具类

    Java开发中,经常需要在maven工程中读取src/main/resources下的配置文件: 思路如下: Class.getClassLoader() 返回类加载器ClassLoader,进而可以 ...

  6. 用C#写的一个OA类的APP, ios、Android都能跑,有源代码

    这是一个用C#写的OA类APP,功能包含请假.报销.部门管理.签到.IM.文件上传等功能 话不多说,先看视频 视频地址:http://v.youku.com/v_show/id_XMzUwMjQ1Mz ...

  7. 很久以前写的一个 ShareRestrictedSD 类

    代码中一开始的 几个 USES 单元,可能是多余的. unit ShareRestrictedSD; interface uses Windows, Messages, SysUtils, Class ...

  8. 收集C#常用类:自己写的一个DBHelper类

    随着学的东西越来越多,一点点的完善吧! using System; using System.Collections.Generic; using System.Linq; using System. ...

  9. 写的一个HttpClient类

    package com.ca.test.cainterface.common.util.http; import com.ca.test.cainterface.common.util.data.Da ...

  10. 利用detours写了一个工具用于instrument任意指定dll的任意指定函数入口

    目录 wiki Disas Dtest Simple withdll load一个dll到指定进程 tracebld显示相关进程涉及的文件读写操作 My Instrumentation tool: w ...

随机推荐

  1. SemanticFunction 融合 LLM 和传统编程

    本文将继续和大家介绍 SemanticKernel 神奇的魔法,将使用 LLM 大语言模型编写的自然语言函数和传统的编程语言编写的函数融合到一起的例子.通过本文的例子,大家可以看到 SemanticK ...

  2. .Net 8.0 下的新RPC,IceRPC之使用Dev Containers进行 .NET QUIC 精简开发

    作者引言 很高兴啊,我们来到了IceRPC之使用Dev Containers进行 .NET QUIC 精简开发,主要是一篇指引,如何使用开发容器做为开发环境,进行开发IceRPC,可适用于任务应用的开 ...

  3. ES_CCS/R(二):跨集群搜索 Cross-cluster search (CCS)

    跨集群搜索(cross-cluster search)使你可以针对一个或多个远程集群运行单个搜索请求. 例如,你可以使用跨集群搜索来筛选和分析存储在不同数据中心的集群中的日志数据. 示例 :在一个集群 ...

  4. mac常用

    目录 Mac 删除键(Delete) 这三招你会吗?可大幅加快打字速度 Mac软件打开提示:已损坏,无法打开.您应该将它移到废纸娄 怎么解决? 卸载手动安装的软件 如何在Mac上使用Charles进行 ...

  5. cesium教程9-加载倾斜摄影并解决高度问题

    无人机航拍的倾斜摄影,用照片和视频处理生成三维模型,一般照片都带有坐标信息,所以一般都能定位的比较准确,但是经常会出现高度偏差,这个时候就需要特殊处理了. 今天航拍建模的效果如下: 这个建模没有裁剪, ...

  6. gRPC入门学习之旅(八)

    gRPC入门学习之旅(一) gRPC入门学习之旅(二) gRPC入门学习之旅(三) gRPC入门学习之旅(四) gRPC入门学习之旅(五) gRPC入门学习之旅(六) gRPC入门学习之旅(七) 3. ...

  7. ansible使用详解

    ansible执行,用户主机配置 免密同一个同一个用户执行命令 1 能免密登录的[root@mcw1 ~]$ ansible 10.0.0.132 -m shell -a "hostname ...

  8. Ceph 架构以及部署

    目录 Ceph架构 存储类型 为什么用到Ceph? 1. NFS 2. MooseFS MooseFS瓶颈 3. GlusterFS 4. Ceph Ceph的组件 Ceph部署 前期准备 1.1 修 ...

  9. 大数据之Hadoop的HDFS存储优化—异构存储(冷热数据分离)

    异构存储主要解决,不同的数据,储存在不同类型的硬盘中,达到最佳性能的问题 1)存储类型 RAM_DISK:内存镜像文件系统 SSD:SSD固态硬盘 DISK:普通磁盘,在HDFS中,如果没有主动声明数 ...

  10. C# 炸弹人 winform版

    实现这个游戏的基本功能包含几个对象:玩家,怪物,墙砖,炸弹,通关的门.玩家通过上下左右方向键移动,放置炸弹,被怪物杀死,被炸弹炸死.怪物随机方向移动,能杀死玩家.炸弹有爆炸功能,炸弹的火花长度.通过的 ...