日常需求一次使用EasyExcel而引发的问题与思考~
Posted Code皮皮虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常需求一次使用EasyExcel而引发的问题与思考~相关的知识,希望对你有一定的参考价值。
文章目录
前言
大家好啊,我是皮皮虾~,最近接的个需求中有个小功能是要上传用户id或email的excel,解析返回出正常的用户数和异常的用户数(简单来说就是能查到用户信息的数量和不能查到的数量)。
擦,我寻思这不得涉及到流的操作,这可是我的弱项啊~
当然来,正经人谁直接手写,当然要拥抱开源啦,这不就遇到了阿里的EasyExcel
如何利用EasyExcel解析Excel
1. 首先要定义一个对象
根据EasyExcel的思想,excel的每一行都认为是一个对象,每一列可以认为是一个字段
那么对应到Java中,我们就可以建一个实体类,来对应我们需要解析的Excel。
实习类中的字段注意要加上@ExcelProperty
注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserData
@ExcelProperty(index = 0)
private Long uid;
@ExcelProperty(index = 1)
private String email;
@ExcelProperty
注解,可以设置如下四个字段的值,一般情况下,我们需要设置 index 代表这个字段是对应的第几列(从0开始),或者设置 value 代表列名称,也就是通过列名称来匹配
**要注意的是,在官方文档中也提示了我们,不要同时使用index和value,如果excel没有设置标题或者标题重复可能导致解析不尽人意~ **
2. 创建监听器
创建一个监听器类,可以选择继承AnalysisEventListener
类或者实现ReadListener
接口
那么这两个有什么区别嘞?
其实区别不大,AnalysisEventListener
实现了ReadListener
的四个方法,主要的话还是在于对于表头数据的读取,通过重写invokeHead
方法,帮我们手动将表头数据转换了一下,这样如果我们需要读取表头数据的话,直接继承AnalysisEventListener
,实现invokeHeadMap
即可,不然如果是依然实现ReadListener
,我们还需要手动ConverterUtils.convertToStringMap
转换
@EqualsAndHashCode(callSuper = true)
@Slf4j
@Data
@AllArgsConstructor
public class UserDataListener extends AnalysisEventListener<UserData>
/**
* uid集合
*/
private List<Long> uidList;
/**
* 邮箱集合
*/
private List<String> emailList;
/**
* 有效用户数量
*/
private Integer validUserNum;
/**
* 无效用户数量
*/
private Integer invalidUserNum;
/**
* 类型(0:uid,1:email)
*/
private Integer type;
public UserDataListener(Integer type)
this.type = type;
if (type.equals(0))
uidList = new ArrayList<>();
return;
emailList = new ArrayList<>();
@Override
public void invoke(UserData data, AnalysisContext context)
if (type.equals(0))
uidList.add(data.getUid());
return;
emailList.add(data.getEmail());
@Override
public void doAfterAllAnalysed(AnalysisContext context)
// 解析完成之后调用该方法
/**
* rpc批量查询用户信息,通过type来区分是通过uidList查询还是emailList查询
* ps:批量查询中,如果用户uid或者email不存在,最终返回的结果里则不包含该用户信息
* 所以,rpc用户集合数量 <= uidList.size() or emailList.size()
* 通过这个来计算validUserNum 和 invalidUserNum
*/
@Override
public void onException(Exception exception, AnalysisContext context)
throw new RuntimeException("解析user excel异常", exception);
3. 解析excel
获取前端传过来的excel文件和上传类型,进行基本的参数校验之后,进行excel的解析
要注意的是监听器不能被Spring管理,每次都要去new一个新的对象
@Override
public UserUploadVO uploadFile(MultipartFile file, Integer type) throws Exception
if (file == null || type == null)
throw new RuntimeException("Param error");
UserUploadVO userUploadVO = new UserUploadVO();
String fileName = file.getOriginalFilename();
if (StringUtils.isEmpty(fileName))
return userUploadVO;
String fileXlsx = fileName.substring(fileName.length() - 5);
String fileXls = fileName.substring(fileName.length() - 4);
if (!".xlsx".equals(fileXlsx) && !".xls".equals(fileXls))
return userUploadVO;
UserDataListener userDataListener = new UserDataListener(type);
// 解析excel
EasyExcel.read(file.getInputStream(), UserData.class, userDataListener).sheet().doRead();
// 设置有效用户数和无效用户数
userUploadVO.setInvalidUserNum(userDataListener.getInvalidUserNum());
userUploadVO.setValidUserNum(userDataListener.getValidUserNum());
return userUploadVO;
由于计算有效用户数和无效用户数是口述了下逻辑,这边就不给小伙伴们去展示结果啦,有兴趣的小伙伴们可以自己试试
完结撒花 ???
本来写到这,皮皮虾已经自测通过了,觉得没啥问题了,但我突然又去嘴贱的问了下产品姐姐
皮皮虾:产品姐姐,那上传的时候,如果是传uid的excel都从第一列第二行开始,传email的excel都从第二列第二行开始可以不?
产品姐姐:为啥要这样嘞?都从第一列第二行开始不好嘛,我觉得统一一点比较好,不然上传的时候有问题,大家都以为是bug。
前面已经跟大家提过了,EasyExcel每一行都认为是一个对象,我们在字段上添加@ExcelProperty(index = 0)
注解时,已经表明了这个字段是第几列的数据…,现在都搞到第一列,本质上还一个是String
一个是Long
不过想想,人家产品说的也有道理,于是我礼貌的回复了下:好的啊~
如何解决?
看者眼前的聊天框,我陷入的沉思…,艹,大不了我写两套代码,能实现需求就行
一段狂敲代码过后,原本的一个实体类一个监听器,变成了两个实体类两个监听器,解析exel逻辑里也充满了if-else…
测试过后也没发现有啥问题,十分的nice
虽然功能实现了,但是看着眼前屎山般的代码,我恨不得抽自己两巴掌,这写的什么玩意😭。
于是,我开始思索解决之道,虽然@ExcelProperty(index = 0)
限定了该字段是哪一列的数据,但是我可以反射修改啊哈哈,这样不就行了嘛,但转念一想,这玩意不是监听器,不是每次都new的一个新的出来,那如果在并发上传的时候,我要是动态修改了字段对应的列,那岂不是完蛋…
这也不行,那也不行,搜Google也没有个文章能给个解决方案,没办法的我只能继续去看看官方文档
于是,我就发现了下面这个不创建对象的读
之前解析excel,我们都是通过创建对象来做与excel行的映射,对象中固定了字段与列的对应,此方法满足不了我的需求
那么这个不创建对象的读,是怎么实现的,该怎么去理解呢?
AnalysisEventListener<Map<Integer, String>>
,在不创建对象的情况下,泛型类型传递的是Map<Integer, String>
那这是如何与Excel对应的?我也没发现官方文档中的解释
搜索了一圈,网上的波客文章跟官方文档的一摸一样,我也是醉了,还得得靠自己~
快速修改了代码,启动项目进行debug测试
第一次
第二次
通过Debug测试,我们也是能快速理解,这种不创建对象的读,采用了Map<Integer, String>
来代替了我们自定义的对象,
整个map就是当前行的数据,key就是列数,value就是列对应的值
通过这种方式完全可以满足此次的需求,我们们只需要get(0)
即可,通过type
来区分是uid
还是email
public void invoke(Map<Integer, String> data, AnalysisContext context)
String userInfo = data.get(0);
if (type.equals(0))
uidList.add(Long.valueOf(userInfo));
return;
emailList.add(userInfo);
后话
虽然这个功能比较简单,但是实现过程中也能暴露出我们许多问题,首先在于沟通,我们不能直接想当然的开发,功能开发之前要与产品沟通好,不然写了半天发现与产品想要结果存在出入,等于白写,其次是一定要对自己的代码有所要求,不能只是完成需求就行,这样对自己是没有提升的,总之一句话,逆水行舟,不进则退!
我是 皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!
觉得文章不错的话,可以在 CSDN 关注我,或者是我的公众号——JavaCodes。
以上是关于日常需求一次使用EasyExcel而引发的问题与思考~的主要内容,如果未能解决你的问题,请参考以下文章