EasyExcel實現動態(tài)列解析和存表

背景

一個表中的數據來源于多個其他系統(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 串将塑。

代碼

  1. 引入pom坐標
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.0</version>
</dependency>
  1. 創(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;
}
  1. 因為存在不確定的列脉顿,所以只能使用 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)列的表頭和字段的對應關系,利用此關系對數據進行解析迫肖。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末锅劝,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子咒程,更是在濱河造成了極大的恐慌鸠天,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帐姻,死亡現場離奇詭異,居然都是意外死亡奶段,警方通過查閱死者的電腦和手機饥瓷,發(fā)現死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹籍,“玉大人呢铆,你說我怎么就攤上這事《撞” “怎么了棺克?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵悠垛,是天一觀的道長。 經常有香客問我娜谊,道長确买,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任纱皆,我火速辦了婚禮湾趾,結果婚禮上,老公的妹妹穿的比我還像新娘派草。我一直安慰自己搀缠,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布近迁。 她就那樣靜靜地躺著艺普,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鉴竭。 梳的紋絲不亂的頭發(fā)上歧譬,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音拓瞪,去河邊找鬼缴罗。 笑死,一個胖子當著我的面吹牛祭埂,可吹牛的內容都是我干的面氓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼蛆橡,長吁一口氣:“原來是場噩夢啊……” “哼舌界!你這毒婦竟也來了?” 一聲冷哼從身側響起泰演,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤呻拌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后睦焕,有當地人在樹林里發(fā)現了一具尸體藐握,經...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年垃喊,在試婚紗的時候發(fā)現自己被綠了猾普。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡本谜,死狀恐怖初家,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤溜在,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布陌知,位于F島的核電站,受9級特大地震影響掖肋,放射性物質發(fā)生泄漏仆葡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一培遵、第九天 我趴在偏房一處隱蔽的房頂上張望浙芙。 院中可真熱鬧,春花似錦籽腕、人聲如沸嗡呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽南窗。三九已至,卻和暖如春郎楼,著一層夾襖步出監(jiān)牢的瞬間万伤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工呜袁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敌买,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓阶界,卻偏偏與公主長得像虹钮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膘融,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容