当前位置:网站首页>EasyExcel实现动态列解析和存表
EasyExcel实现动态列解析和存表
2022-08-02 19:46:00 【青衫染红尘】
背景
一个表中的数据来源于多个其他系统的导出表,其中的特点就是大多数的字段都是一样的(可能导出的表头不一样),只有部分少数字段是每个系统自己独有的。围绕这个做一次功能性分析
分析:大多数字段是一样的,那么就是实际的表字段,唯一的区别就是各系统内的名字可能不一样,少数每个系统独有的字段,可以归为动态字段。
总结:
- 公共字段(翻译表头:
@ExcelProperty
可以指定多个表头(@ExcelProperty(value = {"发货数量", "采购数量(台)"})
)) - 动态字段(需要有每个系统内动态字段的字段名称和表头的对应关系,考虑使用字典,供业务员配置,后续如果新添加其他动态字段直接在字典中配置,无需另行开发)
注意:由于无法控制和预料固定字段在新接入的系统中的实际表头,所以如果新接入系统的公共表头与表字段不一致,需要在
@ExcelProperty(value = {})
中添加新的表头
效果
字典配置:
数据表结果:
公共字段使用常规的数据库表字段存储,动态字段使用额外列存 JSON
串。
代码
- 引入pom坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
- 创建实体类
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;
}
- 因为存在不确定的列,所以只能使用
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
组件来进行导入,实现公共列和动态列组合类型的导入,以及如何存储的功能,主要利用反射和字典分别来维护公共列和动态列的表头和字段的对应关系,利用此关系对数据进行解析。
边栏推荐
- 成为黑客不得不学的语言,看完觉得你们还可吗?
- Based on OpenGL glaciers and firebird (illumination calculation model, visual, particle system)
- golang刷leetcode 经典(10) tire树与ac自动机
- 一款好用的FAQ搭建工具
- Fetch 请求不转换BLOB正常显示GBK编码的数据
- LeetCode 622 设计循环队列[数组 队列] HERODING的LeetCode之路
- J9数字论:互联网跨链桥有什么作用呢?
- 斯堪尼亚SCANIA OTL标签介绍
- 【软件工程导论】软件工程导论笔记
- 遇上Mysql亿级优化,怎么办
猜你喜欢
Leetcode刷题——单调栈问题(739每日温度问题、496下一个更大元素I、503下一个更大元素 II)
PG's SQL execution plan
MySQL安装配置教程(超级详细)
MySQL安装(详细,适合小白)
线性表(顺序表和链表)
4 kmiles join YiSheng group, with more strong ability of digital business, accelerate China's cross-border electricity full domain full growth
【心理学 · 人物】第一期
MOSN 反向通道详解
SQL Server安装教程
es 读流程源码解析
随机推荐
golang刷leetcode动态规划(12)最小路径和
牛客题目——滑动窗口的最大值、矩阵最长递增路径、顺时针旋转矩阵、接雨水问题
Introduction of uncommon interfaces of openlayers
Compose主题切换——让你的APP也能一键换肤
ShapeableImageView 的使用,告别shape、三方库
银保监会:人身险产品信披材料应由保险公司总公司统一负责管理
解析List接口中的常用的被实现子类重写的方法
所谓武功再高也怕菜刀-分区、分库、分表和分布式的优劣
AI Scientist: Automatically discover hidden state variables of physical systems
golang刷leetcode 经典(9)为运算表达式设计优先级
golang刷leetcode 经典(11) 朋友圈
【Psychology · Characters】Issue 1
J9数字论:互联网跨链桥有什么作用呢?
NC | Structure and function of soil microbiome reveal N2O release from global wetlands
MaxCompute 的SQL 引擎参数化视图具体有哪些增强功能?
服务器Centos7 静默安装Oracle Database 12.2
ECCV 2022 | 通往数据高效的Transformer目标检测器
日志框架学习
技术分享 | Apache Linkis 快速集成网页IDE工具 Scriptis
【心理学 · 人物】第一期