Easypoi(一)表格工具类封装
- September 2, 2021
作者:lomtom
个人网站:https://lomtom.cn 🔗
个人公众号:博思奥园 🔗
你的支持就是我最大的动力。
表格导入工具类系列:
需求是这样子的: 现在需要对一个表格进行导入,导入后还需要把图片上传带OSS里面,方便后面访问,导入的数据保存到数据库里面。 目前已经有成熟的Excel工具类对Excel进行导入导出操作,也可以将文件的图片转换出来,但是把图片上传到OSS上面的操作还需要自己来写,恰好,业务需要用到这种需求的场景还比较多,那么我为啥不把它封装成一个工具类呢?
说干就干!!!
准备
导入poi的依赖
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
编写实体类
-
使用
@Excel
标注属性,对应Excel表格的一列,并且使用name
标明Excel中的表头明(默认为第一行) -
如果是图片,就加一个
type=2
,并且标明保存的路径(默认为/excel/upload/img
) -
高级用法,如果需要实现自己的自定义校验器,就需要实现
IExcelDataModel
与IExcelModel
接口,并且增加rowNum
和errorMsg
两个属性,因为会将行号与错误信息保存在这两个字段里面。注意:实现
IExcelDataModel
与IExcelModel
接口,将不能使用链式编程,即不能在实体类上加@Accessors(chain = true)
。 -
更多用法,请参见官方文档:http://easypoi.mydoc.io/ 🔗
package com.lomtom.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.handler.inter.IExcelDataModel;
import cn.afterturn.easypoi.handler.inter.IExcelModel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author Feyoung
* @description:商品入库模板
* @date 2021/8/4 14:42
*/
@Data
public class CommodityTemplate implements IExcelDataModel, IExcelModel {
/**
* 行号
*/
private Integer rowNum;
/**
* 错误消息
*/
private String errorMsg;
@Excel(name = "编号")
private String id;
/**
* 商品名字
*/
@Excel(name = "名字")
private String name;
/**
* 商品图片
*/
@Excel(name = "图片", type = 2, savePath = "\\imgggg")
private String imageUrl;
/**
* 创建时间
*/
@Excel(name = "日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createTime;
@Excel(name = "头像", type = 2, savePath = "\\imgggg")
private String img;
}
文件转换为流
由于只支持流或者本地件的形式,那么我们则需要将文件转换为流的形式,或者保存到本地再进行读取,这里以流为例。
第一步:前提,需要引入OSS的sdk依赖,可见我另一篇文章:https://blog.csdn.net/qq_41929184/article/details/115555334 🔗
第二步: 使用OSS的客户端对文件进行读取,然后转换为流,方便后续进行处理。
1、配置通过配置文件配置后获取的
String url
String bucket
String regionId
String accessKeyId
String accessKeySecret
2、读取OSS的文件
String head= "https://oss-" + regionId + ".aliyuncs.com";
String root = "https://" + bucket + ".oss-" + regionId + ".aliyuncs.com/";
url = url.replace(root,"");
OSS ossClient = new OSSClientBuilder().build(head,
accessKeyId, accessKeySecret);
OSSObject ossObject = ossClient.getObject(bucket, url);
InputStream in = ossObject.getObjectContent();
使用OSS的客户端对文件进行读取,这里需要注意几个小点
- 由于
ossClient.getObject
的文件地址不是全地址,只需要去除域名后的地址,所以使用String.replace
函数进行去除。 new OSSClientBuilder().build()
的参数分别为Bucket所在地域对应的Endpoint、AccessKey、秘钥- 参数分别对应(配置通过配置文件配置后获取的)
String bucket bucket: demo-excel String regionId regionId: cn-hangzhou String accessKeyId ak: ************* String accessKeySecret sk: ********************
使用easypoi进行读取
使用easypoi进行文件读取,如果没有图片或者没有其他数据验证的需求,那么到这里就结束了。
ImportParams params = new ImportParams();
List<T> results = ExcelImportUtil.importExcel(in, tClass, importParams);
其中
- 因为要封装,所以使用泛型,,如果没有图片的需求,可以将这里的T进行替换,例如实体类名为
CommodityTemplate
,那么将T换成CommodityTemplate
。 - tClass为
CommodityTemplate.class
对图片进行存储
在此之前,需要明白几个问题
- 因为封装成工具类,那么我的代码针对不同的实体类都能适用,不能因为字段的改变而导致工具类不能使用。
- 针对不同的场景(例如读取OSS文件,本地,不带图片,带图片),都能进行相应的操作。
- 还需要考虑自定义验证的功能。
针对第一个问题:
-
因为需要对图片进行操作,所以得知道,图片对应的字段是哪一个。
-
而easypoi会将图片保存到本地,然后将图片地址保存到对应的字段,例如,文章开始时展示的表格的头像会保存到
\imggg
(本地的imggg),会将图片地址保存到img
属性。 -
那么我最初的想法是自定义一个注解,在我需要保存的属性上加这个注解,读取时,对每个属性进行扫描,如果该属性有我加的注解,那么就对该属性进行存储操作。
-
但是,恰好,easypoi不是有一个注解
@Excel
,并且注解有一个属性(type)标识该字段为图片。这不恰好可以利用起来吗。
针对第二个问题: 可以参考公司大佬工具类的写法,对某些代码进行复用,这个问题不大。
针对第三个问题:
这个我也是之前没有想到的问题
查看easypoi的源码,可以看到easypoi有支持自定义验证的,而且恰好参数是自己传的,那么不就可以利用起来,只要自定义验证器实现IExcelVerifyHandler
接口即可。
public class ImportParams extends ExcelBaseParams {
private IExcelVerifyHandler verifyHandler;
}
ok,思路有了,进行实际行动。
第一步:对easypoi返回的结果进行遍历,这里使用foreach进行操作,这样可以获取到集合中的每一个元素。
list.forEach(obj -> {
doSomeThing....
}
第二步:获取每个实体的属性,使用obj.getClass().getDeclaredFields()
获取全部字段,然后再对每一个字段进行判断操作。
List<Field> fieldList = Arrays.asList(obj.getClass().getDeclaredFields());
fieldList.forEach(item -> {
doSomeThing....
}
第三步:判断该字段是否被某个注解修饰,使用Field.isAnnotationPresent
进行判断,参数为注解类对象,这里为easypoi的@Excel
。
boolean annotationPresent = item.isAnnotationPresent(Excel.class);
if (annotationPresent) {
doSomeThing....
}
第四步:使用Field.getAnnotation
获取该注解,因为@Excel
的参数type
为2时表示为图片,所以通过anno.type() == 2
判断是否符合条件。
Excel anno = item.getAnnotation(Excel.class);
if (anno.type() == 2) {
doSomeThing....
}
第五步:拿到该字段的值,也就是说图片存在本地的路径地址,读取并且转换为流,在传到OSS里面,再将OSS的文件地址保存到该属性中,大功就告成了
String path = getFieldValueByName(item.getName(), obj);
InputStream inputStream = new FileInputStream(path);
String ossFileName = path.replace("\\", "");
ossClient.putObject(bucket, rootPath + ossFileName, inputStream);
String ossPath = "https://" + bucket + "." + "oss-" + regionId + ".aliyuncs.com" + "/" + rootPath + ossFileName;
item.setAccessible(true);
item.set(obj, ossPath);
自定义函数:
private static String getFieldValueByName(String fieldName, Object o) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter);
Object value = method.invoke(o);
return value.toString();
}
第六步:针对该方法对不同的场景进行代码的复用、抽取。
public static <T> ExcelImportResult<T> importExcelByLocalUrlWithImg(String url, Class<T> tClass, OssProperties ossProperties, String rootPath, ImportParams importParams) throws Exception {
return importExcelByLocalUrlWithImg(url, tClass, ossProperties.getBucket(), ossProperties.getRegionId(), ossProperties.getAk(), ossProperties.getSk(), rootPath, importParams);
}
public static <T> ExcelImportResult<T> importExcelByLocalUrl(String url, Class<T> tClass, ImportParams importParams) throws Exception {
InputStream in = new FileInputStream(url);
return importExcelByStream(in, tClass, importParams);
}
public static <T> ExcelImportResult<T> importExcelByOssUrlWithImg(String url, Class<T> tClass, OssProperties ossProperties, String rootPath, ImportParams importParams) throws Exception {
return importExcelByOssUrlWithImg(url, tClass, ossProperties.getBucket(), ossProperties.getRegionId(), ossProperties.getAk(), ossProperties.getSk(), rootPath, importParams);
}
public static <T> ExcelImportResult<T> importExcelByOssUrl(String url, Class<T> tClass, OssProperties ossProperties, ImportParams importParams) throws Exception {
return importExcelByOssUrl(url, tClass, ossProperties.getBucket(), ossProperties.getRegionId(), ossProperties.getAk(), ossProperties.getSk(), importParams);
}
public static <T> ExcelImportResult<T> importExcelByStreamWithImg(InputStream in, Class<T> tClass, OssProperties ossProperties, String rootPath, ImportParams importParams) throws Exception {
return importExcelByStreamWithImg(in, tClass, ossProperties.getBucket(), ossProperties.getRegionId(), ossProperties.getAk(), ossProperties.getSk(), rootPath, importParams);
}
public static <T> ExcelImportResult<T> importExcelByStream(InputStream in, Class<T> tClass, ImportParams importParams) throws Exception {
return getResults(in, tClass, importParams);
}
测试
/**
* 调用OSS(带有图片)
* @throws Exception Exception
*/
@Test
public void importExcelByOssUrlWithImg() throws Exception {
ImportParams params = new ImportParams();
ExcelImportResult<CommodityTemplate> result= ExcelImportUtils.importExcelByOssUrlWithImg(url1,
CommodityTemplate.class,ossProperties,"changxing/test/",params);
if (result.isVerifyFail()){
for (CommodityTemplate entity : result.getFailList()) {
String msg = "第" + entity.getRowNum() + "行的错误是:" + entity.getErrorMsg();
System.out.println(msg);
}
}
result.getList().forEach(System.out::println);
}
注意:
- 实现
IExcelDataModel
与IExcelModel
接口,将不能使用链式编程,即不能在实体类上加@Accessors(chain = true)
,具体原因是使用该注解后,set
方法将返回该对象,而不是void,这将会产生冲突。 - 若项目中还是用了其他excel工具类,请将公有的依赖排除,例如easyexcel
<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>2.3.0</version> <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.0-beta2</version> <exclusions> <exclusion> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> </exclusion> <exclusion> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> </exclusion> </exclusions> </dependency>
- 若使用了
easyexcel
,在引入easypoi并且排除共有依赖导致easyexcel的部分不可用,请升级easypoi的版本。<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>4.1.3</version> </dependency>
注:本工具类是基于easypoi的工具类封装
参考: