背景
一個表中的數據來源于多個其他系統(tǒng)的導出表噪叙,其中的特點就是大多數的字段都是一樣的(可能導出的表頭不一樣),只有部分少數字段是每個系統(tǒng)自己獨有的霉翔。圍繞這個做一次功能性分析
分析:大多數字段是一樣的睁蕾,那么就是實際的表字段,唯一的區(qū)別就是各系統(tǒng)內的名字可能不一樣早龟,少數每個系統(tǒng)獨有的字段惫霸,可以歸為動態(tài)字段猫缭。
總結:
公共字段(翻譯表頭:
@ExcelProperty
可以指定多個表頭(@ExcelProperty(value = {"發(fā)貨數量", "采購數量(臺)"})
))動態(tài)字段(需要有每個系統(tǒng)內動態(tài)字段的字段名稱和表頭的對應關系,考慮使用字典壹店,供業(yè)務員配置猜丹,后續(xù)如果新添加其他動態(tài)字段直接在字典中配置,無需另行開發(fā))
注意:由于無法控制和預料固定字段在新接入的系統(tǒng)中的實際表頭硅卢,所以如果新接入系統(tǒng)的公共表頭與表字段不一致射窒,需要在
@ExcelProperty(value = {})
中添加新的表頭
效果
字典配置:
數據表結果:
公共字段使用常規(guī)的數據庫表字段存儲,動態(tài)字段使用額外列存 JSON
串将塑。
代碼
- 引入pom坐標
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
- 創(chuàng)建實體類
public class AgentDeliverOrderImportVo {
@ExcelProperty(value = {"訂單編號"}, order = 1)
private String deliverNo;
@ExcelProperty(value = {"發(fā)貨數量", "采購數量(臺)"}, order = 14)
@ColumnName(name = {"發(fā)貨數量", "采購數量(臺)"})
private Integer deliverCount;
/**
* 動態(tài)字段(業(yè)務線編號區(qū)分)
*/
private String dynamicFields;
private Date createTime;
private String createBy;
}
- 因為存在不確定的列脉顿,所以只能使用
EasyExcel
的不創(chuàng)建對象的寫,那么
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("轉化一條動態(tài)數據:{}", 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 {
/**
* 將公共字段中的中文轉換成數據庫表字段点寥,動態(tài)字段(其他字段保留)
* @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;
}
/**
* 獲取數據(平鋪),指動態(tài)字段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();
//動態(tài)字段
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;
}
/**
* 獲取數據(表結構)敢辩,指動態(tài)字段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;
}
}
經過解析以后這個文檔的數據已經和數據庫表一致了蔽莱,那么我們后續(xù)的操作就是常規(guī)的校驗和插入邏輯了。
目前有一個缺點就是這樣存的動態(tài)字段不好做條件查詢戚长,影響不是很大盗冷。
總結
本文介紹了使用 EasyExcel
組件來進行導入,實現公共列和動態(tài)列組合類型的導入同廉,以及如何存儲的功能仪糖,主要利用反射和字典分別來維護公共列和動態(tài)列的表頭和字段的對應關系,利用此關系對數據進行解析迫肖。