EasyExcel实现动态列解析和存表

背景

一个表中的数据来源于多个其他系统的导出表,其中的特点就是大多数的字段都是一样的(可能导出的表头不一样),只有部分少数字段是每个系统自己独有的。围绕这个做一次功能性分析

分析:大多数字段是一样的,那么就是实际的表字段,唯一的区别就是各系统内的名字可能不一样,少数每个系统独有的字段,可以归为动态字段。

总结:

  • 公共字段(翻译表头:@ExcelProperty 可以指定多个表头( @ExcelProperty(value = {"发货数量", "采购数量(台)"}) ))
  • 动态字段(需要有每个系统内动态字段的字段名称和表头的对应关系,考虑使用字典,供业务员配置,后续如果新添加其他动态字段直接在字典中配置,无需另行开发)

注意:由于无法控制和预料固定字段在新接入的系统中的实际表头,所以如果新接入系统的公共表头与表字段不一致,需要在 @ExcelProperty(value = {}) 中添加新的表头

效果

字典配置:

字典配置

数据表结果:

数据表结果

公共字段使用常规的数据库表字段存储,动态字段使用额外列存 JSON 串。

代码

  1. 引入pom坐标
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>3.1.0</version>
</dependency>
  1. 创建实体类
public class AgentDeliverOrderImportVo {
    @ExcelProperty(value = {"订单编号"}, order = 1)
    private String deliverNo;

    @ExcelProperty(value = {"发货数量", "采购数量(台)"}, order = 14)
    @ColumnName(name = {"发货数量", "采购数量(台)"})
    private Integer deliverCount;
    
    /**
     * 动态字段(业务线编号区分)
     */
    private String dynamicFields;

    private Date createTime;

    private String createBy;
}
  1. 因为存在不确定的列,所以只能使用 EasyExcel 的不创建对象的写,那么
public String test(MultipartFile file) throws IOException {
    //假设从字典中获取字典值
    Map<String, String> dictMap = new HashMap<>();
    dictMap.put("项目", "xm");
    dictMap.put("嗨一付订单编号", "hyfddbh");
    
    try (InputStream inputStream = file.getInputStream()) {
        EasyExcel.read(inputStream, new ReadListener<Map<String, String>>(){
            private Map<Integer, String> fieldHead;

            //获取表头
            @Override
            public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
                Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context);
                log.info("解析到一条头数据:{}", JSON.toJSONString(integerStringMap));
                fieldHead = ExcelParsing.setFieldHead(integerStringMap, AgentDeliverOrderImportVo.class);
                log.info("转化后头数据:{}", JSONObject.toJSONString(fieldHead));
            }

            //获取数据
            @Override
            public void invoke(Map<String, String> map, AnalysisContext analysisContext) {
                log.info("解析到一条数据:{}", JSON.toJSONString(map));
                Map<String, String> valueMap = ExcelParsing.setFieldValue(fieldHead, dictMap, map);
                log.info("转化一条数据:{}", JSONObject.toJSONString(valueMap));
                log.info("转化一条动态数据:{}", JSONObject.toJSONString(ExcelParsing.getValueMap(valueMap, AgentDeliverOrderImportVo.class)));
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {

            }
        }).sheet().doRead();
    }
    return "完成";
}

/**
 * @author Surpass
 * @Description: excel处理类
 * @date 27/07/2022 15:04
 */
class ExcelParsing {

    /**
     * 将公共字段中的中文转换成数据库表字段,动态字段(其他字段保留)
     * @param headMap               {1:"姓名", 2:"年龄"}
     * @param obj               	AgentDeliverOrderImportVo(导入实体类)
     * @return java.util.Map<java.lang.String, java.lang.String>       {1:"name", 2:"年龄"}
     * @author Surpass
     * @date 01/08/2022 17:10
     */
    public static Map<Integer, String> setFieldHead(Map<Integer, String> headMap, Class<?> obj) {
        Field[] fields = obj.getDeclaredFields();
        for (Field field : fields) {
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation == null) {
                continue;
            }
            //存在翻译字段的情况,一个字段对应好几个表头(尽量避免)
            List<String> valueList = Arrays.asList(annotation.value());
            for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
                if (valueList.contains(entry.getValue())) {
                    headMap.put(entry.getKey(), field.getName());
                }
            }
        }

        return headMap;
    }

    /**
     * 获取数据(平铺),指动态字段kv和公共字段kv在同一级
     * @param headMap               {1:"name", 2:"年龄"}
     * @param dictMap               {"年龄":"age"}
     * @param valueMap              {1:"广州****公司", 2:"23"}
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @author Surpass
     * @date 01/08/2022 17:10
     */
    public static Map<String, String> setFieldValue(Map<Integer, String> headMap,
                                                    Map<String, String> dictMap,
                                                    Map<String, String> valueMap) {
        Map<Integer, String> valueIntegerMap = valueMap.entrySet().stream().collect(
                Collectors.toMap(item -> Integer.valueOf(String.valueOf(item.getKey())),
                        item -> StrUtil.nullToEmpty(item.getValue()))
        );

        Map<String, String> valueResultMap = new HashMap<>(valueMap.size());
        Iterator<Map.Entry<Integer, String>> iterator = valueIntegerMap.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            //动态字段
            if (dictMap != null && dictMap.containsKey(headMap.get(entry.getKey()))) {
                valueResultMap.put(dictMap.get(headMap.get(entry.getKey())), entry.getValue());
                continue;
            }
            //公共字段
            valueResultMap.put(headMap.get(entry.getKey()), entry.getValue());
            iterator.remove();
        }
        return valueResultMap;
    }

    /**
     * 获取数据(表结构),指动态字段kv已经加入到数据库表字段 dynamicFields 中
     * @param obj               	AgentDeliverOrderImportVo(导入实体类)
     * @param valueMap              {"name":"广州****公司", "age":"23"}
     * @return java.util.Map<java.lang.String, java.lang.String>  
     * 返回结果: {"name":"广州****公司","dynamicFields":{"age":"23"}}
     * @author Surpass
     * @date 01/08/2022 17:10
     */
    public static Map<String, Object> getValueMap(Map<String, String> valueMap,
                                                  Class<?> obj) {
        Map<String, Object> resultMap = new HashMap<>(valueMap);
        List<String> commonFieldList = new ArrayList<>();
        Field[] fields = obj.getDeclaredFields();
        for (Field field : fields) {
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation == null) {
                continue;
            }
            commonFieldList.add(field.getName());
        }
        //过滤掉实体中的公共字段
        Map<String, String> dynamicMap = valueMap.entrySet().stream()
                .filter(item -> !commonFieldList.contains(item.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        resultMap.put("dynamicFields", dynamicMap);;
        return resultMap;
    }
}

经过解析以后这个文档的数据已经和数据库表一致了,那么我们后续的操作就是常规的校验和插入逻辑了。

目前有一个缺点就是这样存的动态字段不好做条件查询,影响不是很大。

总结

本文介绍了使用 EasyExcel 组件来进行导入,实现公共列和动态列组合类型的导入,以及如何存储的功能,主要利用反射和字典分别来维护公共列和动态列的表头和字段的对应关系,利用此关系对数据进行解析。

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
EasyExcel实现动态列解析和存表
一个表中的数据来源于多个其他系统的导出表,其中的特点就是大多数的字段都是一样的(可能导出的表头不一样),只有部分少数字段是每个系统自己独有的。围绕这个做一次功能...
<<上一篇
下一篇>>