1.背景
原先的导入功能只支持使用固定模板导入,模板格式如下:
| 1 | 
 | 
EasyExcel 导入监听器直接使用AnalysisEventListener
现在要求用户配置了自定义字段之后,还可以导入自定义字段,同时保留对固定字段的校验逻辑。因此原有的适用对象的监听器不再适用,需要使用无对象的方式做数据校验。
2.问题
- EasyExcel 无对象方式的监听器是继承AnalysisEventListener<Map<Integer, String>>类,在重写了invoke() 方法后发现,入参是 Map<Integer, String> data,这就导致我无法对每一行数据按照原有的方式校验。
- 用户导入的模板列顺序是不固定的,因此也没法遍历 data 进行原有规则的校验。
3.解决方案
3.1 解决思路
- 既然 invoke() 方法入参是 Map<Integer, String> data 这种数据结构,那能不能把这个 Map 中固定的字段转为一个 TestCaseExcelData 对象来处理?
- 如果要转为一个对象,那怎么把 Map 中的数据跟对象的字段做映射?
3.2 Map 转对象
- Map<Integer, String> 是当前行的数据,其中 key 是当前行的列索引,value 是当前单元格的值,如果要转对象,首先得知道这个单元格对应的表头是什么,获取表头的方式很简单,直接在 listener 中定义一个 Map<Integer, String> headMap ,然后重写 invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) 方法,即可获取到表头。
- 取到表头之后,就可以在 invoke(Map<Integer, String> data, AnalysisContext context)方法中遍历data,根据此 map 的 key 来获取到当前单元格表头信息。代码如下:1 
 2
 3
 4
 5
 6
 7
 public void invoke(Map<Integer, String> data, AnalysisContext context) {
 data.forEach((index, value) -> {
 // 获取表头
 String headName = headMap.get(index);
 });
 }
- 取到了当前单元格的对应的表头之后,发现这个表头就是 TestCaseExcelData 类中属性上加的 @ExcelProperty(value = “用例版本”) 注解中 value 属性的值,那就简单了,直接通过反射获取这个类所有表头和对应的属性,然后存到一个 Map<Stirng, Field> fieldStringMap 中就好了,这样就能通过表头获取到这个表头字段对应的类属性,为我们后面创建对象奠定了基础。代码如下:1 
 2
 3
 4
 5
 6
 7
 8Field[] fields = TestCaseExcelData.class.getDeclaredFields(); 
 for (Field field : fields) {
 if (field.isAnnotationPresent(ExcelProperty.class)) {
 ExcelProperty declaredAnnotation = field.getDeclaredAnnotation(ExcelProperty.class);
 String headValue = declaredAnnotation.value()[0];
 this.fieldStringMap.put(headValue, field);
 }
 }
- 经过上面的几步操作,我们已经得到了如下的几个Map1 
 2
 3
 4
 5
 6// 当前行的数据 <列索引, 单元格值> 
 Map<Integer, String> data;
 // 表头的数据 <列索引, 单元格值>
 Map<Integer, String> headMap;
 // 实体对象表头和对应字段的数据 <表头名称, 表头对应的属性>
 Map<Stirng, Field> fieldStringMap;
- 后面的思路经很清晰了,遍历行数据Map<Integer, String> data ,通过 key 来确定
 当前单元格对应的表头,然后通过表头来获取实体类对应的属性,再通过反射来给这个属性赋值。代码如下:1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 public void invoke(Map<Integer, String> data, AnalysisContext context) {
 // 创建实体对象
 TestCaseExcelData rawData = new TestCaseExcelData();
 try {
 data.forEach((index, value) -> {
 // 获取到当前单元格的表头
 String headName = headMap.get(index);
 // 根据表头获取实体类的属性
 Field field = fieldStringMap.get(headName);
 try {
 // 判断实体类是否有此属性
 if (field != null) {
 field.setAccessible(true);
 // 通过反射直接赋值
 field.set(rawData, value);
 }
 } catch (IllegalAccessException e) {
 throw new RuntimeException(e);
 }
 // 解析自定义字段,只有系统配置的字段才会被缓存
 List<CustomFieldPO> customFieldPOS = systemCustomFieldMap.get(headName);
 if (CollectionUtils.isNotEmpty(customFieldPOS)) {
 customFieldMap.put(customFieldPOS.get(0).getFieldKey(), value);
 }
 });
 // 固定字段校验
 ExcelValidateHelper.validateEntity(rawData);
 } catch (NoSuchFieldException e) {
 e.printStackTrace();
 }
 }
- 经过以上操作,我们成功的把一个 Map 转为了一个已知的对象,这样就跟通过对象导入一样了,后面校验的代码也无需再重复编写。
4. 其他
最后,附上自定义模板校验表头的代码
| 1 | 
 | 
| 1 | /** | 
