20190622_系統(tǒng)中的Excel導(dǎo)入導(dǎo)出功能

問題

在一般的后臺管理系統(tǒng)中都少不了導(dǎo)入導(dǎo)出功能录粱,而且很多都是以Excel的格式進行操作签财。在之前的項目中用的是前輩們提供的一個工具類伍茄〖浒Γ總結(jié)一下這個工具類可以幫你從Excel中解析出你要的數(shù)據(jù)绞灼,也可以把你的數(shù)據(jù)轉(zhuǎn)化成Excel,功能比較基礎(chǔ)和純粹呈野。但是從一個完成的導(dǎo)入導(dǎo)出邏輯來說低矮,我們要做的遠不止這些東西,比如在我們的項目中被冒,完整的導(dǎo)入導(dǎo)出應(yīng)該是這樣的:

  • 導(dǎo)入:
    1. 用戶選擇文件军掂,點擊導(dǎo)入按鈕
    2. 前端頁面展示目前的導(dǎo)入進度轮蜕,總條數(shù),成功條數(shù)蝗锥,錯誤條數(shù)
    3. 導(dǎo)入完畢跃洛,展示包含錯誤數(shù)據(jù)的錯誤文件的下載鏈接
    4. 用戶下載錯誤文件,查看每條數(shù)據(jù)導(dǎo)入錯誤的原因终议,然后修改數(shù)據(jù)
    5. 重新導(dǎo)入
    6. 導(dǎo)入應(yīng)該支持Excel 03版本汇竭,07版本
    7. 導(dǎo)入性能應(yīng)該盡量高
    8. 導(dǎo)入最大應(yīng)該支持100萬數(shù)據(jù)的導(dǎo)入,不能出現(xiàn)OOM
  • 導(dǎo)出:
    1. 用戶點擊導(dǎo)出按鈕
    2. 彈出對話框讓用戶選擇需要導(dǎo)出的字段
    3. 用戶點擊確定穴张,開始導(dǎo)出
    4. 前端頁面顯示導(dǎo)出進度细燎,總條數(shù),當(dāng)前條數(shù)
    5. 導(dǎo)出完畢陆馁,直接下載文件到本地電腦
    6. 導(dǎo)出應(yīng)該支持Excel 03版本找颓,07版本
    7. 導(dǎo)出性能應(yīng)該盡量高
    8. 導(dǎo)出最大應(yīng)該支持100萬數(shù)據(jù)的導(dǎo)出,不能出現(xiàn)OOM

基于上面這些問題叮贩,我們原來的一個簡單的工具類是沒辦法解決的,只能另尋他路佛析。

引入EasyExcel

幾經(jīng)尋找益老,找到了這個阿里巴巴開源的項目,看了一下文檔寸莫,下載下來把我需要的幾個功能都試了一下捺萌,感覺不錯。不管從可用性膘茎,穩(wěn)定性桃纯,性能以及學(xué)習(xí)成本上都比我們原來的工具類強很多,引入EasyExcel之后我們可以解決上面導(dǎo)入導(dǎo)出場景中的6披坏,7态坦,8問題,就是說針對Excel本身的這些操作我們可以不用操心了棒拂,完全交給EasyExcel就可以了伞梯,我們自己要去解決剩下的問題。

設(shè)計導(dǎo)入模式

其實我們大部分的導(dǎo)入場景的過程都是一樣的帚屉,基本上分為以下幾步:

  1. 從Excel解析出數(shù)據(jù)谜诫,轉(zhuǎn)換為Java對象
  2. 遍歷Java對象,處理每一個Java對象攻旦,通常就是簡單的插入數(shù)據(jù)庫
  3. 導(dǎo)入完畢喻旷,得到導(dǎo)入的結(jié)果:總條數(shù),成功條數(shù)牢屋,錯誤條數(shù)且预,錯誤數(shù)據(jù)列表

所以這個地方我們可以設(shè)計一個模板模式+策略模式牺陶,這樣可以將導(dǎo)入過程統(tǒng)一起來,把公共的步驟提供默認(rèn)實現(xiàn)辣之,每個業(yè)務(wù)只需要實現(xiàn)自己不同的地方掰伸,就是實現(xiàn)怎么去處理每一個Java對象。

簡單寫一下偽代碼:
導(dǎo)入模板接口:

  interface ImportTemplateInterface<T>{
    /**
    *默認(rèn)方法怀估,實現(xiàn)導(dǎo)入模板過程
    */
    default String import(InputStream ins){
        //任務(wù)id狮鸭,返回給客戶端用戶查詢導(dǎo)入進度
       String taskId = UUID.random().toString();
       //異步執(zhí)行
        new Thread(
          new Runable(){
               public void run(){
                 updateProgress(taskId,START);
                 //利用easyExcel讀取數(shù)據(jù)
                 List<T> modelList = easyExcel.read(ins);
                //錯誤數(shù)據(jù)列表
                 List<ErrorModel> errorList = new ArrayList<>();
                 updateProgress(taskId,totalCount++);
                 modelList.foreach(model -> {
                       try{
                          //處理數(shù)據(jù)
                          importItem(model);
                          updateProgress(taskId,successCount++);
                        }catch(Exception e){
                            //加入到錯誤列表
                            errorList.add(toErrorModel(model));
                            updateProgress(taskId,errorCount++);
                       }
                 });
                updateProgress(END);
               }
          }
        ).start()
        return taskId;
      }

     /**
      *處理數(shù)據(jù)方法,由具體實現(xiàn)類去實現(xiàn)
      */
      void importItem(T model);

     /**
      *更新進度方法多搀,默認(rèn)實現(xiàn)類可以提供默認(rèn)實現(xiàn)歧蕉,具體實現(xiàn)類可以覆蓋
      */
      void updateProgress(String taskId,int count);

  }

模板類的默認(rèn)實現(xiàn):

abstract class DefaultImportTemplate<T> implements ImportTemplateInterface <T>{
      @Resource
      private Redis redis;
      
      /**
      *抽象方法,處理數(shù)據(jù)康铭,由子類提供實現(xiàn)
       */     
      abstract void importItem(T model);

      /**
      *默認(rèn)的更新進度實現(xiàn)
      */
      void updateProgress(String taskId,int count){
        //進度存儲到redis
        redis.update(taskId,count);
    }

    /**
    *默認(rèn)的獲取流程進度的方法
    */
     void getProgress(String taskId){
        redis.get(taskId);
      }

    /**
    *默認(rèn)的獲取錯誤數(shù)據(jù)的方法
    */
    List<ErrorModel> getErrorList(String taskId){
         redis.getErrorList(taskId);
    }
}

業(yè)務(wù)類的實現(xiàn):

class UserImportTemplate extends DefaultImportTemplate<User> {
    @Resource
    private UserDao userDao;

    /**
    *用戶導(dǎo)入實現(xiàn)處理數(shù)據(jù)的方法惯退,插入用戶
    */
    void importItem(User model){
        userDao.insert(model);
    }
}

設(shè)計導(dǎo)出模式

其實導(dǎo)出跟導(dǎo)入一樣也可以使用模板模式+策略模式,在導(dǎo)出的過程里面从藤,主要需要業(yè)務(wù)自己實現(xiàn)的是獲取數(shù)據(jù)的方法和獲取表頭的方法催跪,其他的都可以在模板里面做好。這里可以沒有默認(rèn)實現(xiàn)夷野,因為獲取數(shù)據(jù)和獲取表頭都是和業(yè)務(wù)強相關(guān)的沒法提供默認(rèn)實現(xiàn)懊蒸。偽代碼思路基本上跟導(dǎo)入是一樣的,不寫了悯搔。

導(dǎo)出的動態(tài)表頭設(shè)計

我們系統(tǒng)有點特殊的地方骑丸,就是導(dǎo)出的字段是可選的,那這個該怎么辦呢妒貌?

設(shè)計一個注解

注解有index,value兩個基礎(chǔ)屬性通危,index表示導(dǎo)出時表頭字段的順序,value表示表頭的顯示名稱灌曙,把注解加在需要導(dǎo)出的實體類的字段上菊碟。

獲取所有可選表頭

前端傳一個參數(shù)表示是哪個模塊的導(dǎo)出操作,后端根據(jù)參數(shù)找到該模塊導(dǎo)出的實體類平匈,然后利用反射找出實體類中所有加了上面那個注解的字段框沟,然后封裝成表頭對象<index,columnKey,columnName>,columnKey是指字段的名字增炭,columnName是指表頭顯示名稱忍燥,就是上面注解的value。然后把這些可選表頭全部返回給前端隙姿,給用戶選擇梅垄。

根據(jù)選擇的表頭獲取數(shù)據(jù)

用戶在頁面上勾選的表頭時候提交到后端,后端獲取了表頭之后,使用EasyExcel的創(chuàng)建表頭功能創(chuàng)建表頭队丝,這一步比較簡單靡馁,就是把我們自己的表頭對象轉(zhuǎn)換成EasyExcel的格式,主要用到index和columnName兩個屬性机久。然后查詢出需要導(dǎo)出的數(shù)據(jù)臭墨,因為我們查詢出來的對象里面是所有屬性都有值的,如果直接使用EasyExcel導(dǎo)出的話膘盖,會把所有數(shù)據(jù)都會導(dǎo)出胧弛,這樣就會出現(xiàn),表頭是選擇的那么多侠畔,但是每一行后面的數(shù)據(jù)會多出那些沒有選擇的列结缚。因此,我們在從數(shù)據(jù)庫獲取數(shù)據(jù)之后软棺,我們還需要利用表頭中的columnKey的值進行反射獲取對象的屬性值红竭,我們只獲取用戶選擇的那些columnKey的值,然后組裝成EasyExcel中需要的那種格式喘落,然后就可以導(dǎo)出了茵宪。在這個過程中我們的表頭和我們的內(nèi)容順序要保持一致,要不然會出現(xiàn)內(nèi)容和表頭對不上揖盘,所以我們在創(chuàng)建表頭和內(nèi)容是都要先對提交上來的可選表頭按index排序眉厨,這樣就OK了。用文字描述可能有點抽象兽狭,其實自己去實現(xiàn)的話,沒有說的那么麻煩鹿蜀。如果你的導(dǎo)出表頭是固定的那就很簡單了箕慧,只需要使用EasyExcel的注解,然后直接查出數(shù)據(jù)茴恰,直接導(dǎo)出就可以了颠焦。EasyExcel提供了根據(jù)模型注解導(dǎo)出,也可以自己組裝數(shù)據(jù)和表頭往枣,所以我們利用后面這個特點就可以實現(xiàn)動態(tài)表頭的導(dǎo)出功能伐庭。
這個地方還有一點要注意的是,你要使用反射來自己獲取屬性值的話分冈,那日期類型的數(shù)據(jù)你要自己格式化一下圾另,要不然導(dǎo)出到Excel里面格式是不固定的,也許不是你想要的格式雕沉。

好了集乔,寫了這么多,感覺就是一個簡單的導(dǎo)入導(dǎo)出功能坡椒,如果是需要做到系統(tǒng)里面的導(dǎo)入導(dǎo)出比較統(tǒng)一實現(xiàn)的話扰路,可以參考一下尤溜,這樣其他的伙伴在開發(fā)的時候就比較簡單了,不用每個人都把這些做一遍汗唱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宫莱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子哩罪,更是在濱河造成了極大的恐慌授霸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件识椰,死亡現(xiàn)場離奇詭異绝葡,居然都是意外死亡,警方通過查閱死者的電腦和手機腹鹉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門藏畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人功咒,你說我怎么就攤上這事愉阎。” “怎么了力奋?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵榜旦,是天一觀的道長。 經(jīng)常有香客問我景殷,道長溅呢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任猿挚,我火速辦了婚禮咐旧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绩蜻。我一直安慰自己铣墨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布办绝。 她就那樣靜靜地躺著伊约,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孕蝉。 梳的紋絲不亂的頭發(fā)上屡律,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音昔驱,去河邊找鬼疹尾。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纳本。 我是一名探鬼主播窍蓝,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起张弛,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘫辩,失蹤者是張志新(化名)和其女友劉穎模庐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年叁鉴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佛寿。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡幌墓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冀泻,到底是詐尸還是另有隱情常侣,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布弹渔,位于F島的核電站胳施,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肢专。R本人自食惡果不足惜舞肆,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望博杖。 院中可真熱鬧胆绊,春花似錦、人聲如沸欧募。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跟继。三九已至,卻和暖如春镣丑,著一層夾襖步出監(jiān)牢的瞬間舔糖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工莺匠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留金吗,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像摇庙,于是被迫代替她去往敵國和親旱物。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容