Java:匯出Excel大批量資料的優化過程

翁智華 發表於 2021-08-18
Java Excel

背景

團隊目前在做一個使用者資料看板(下面簡稱看板),基本覆蓋使用者的所有行為資料,並生成分析報表,使用者行為由多個資料來源組成(餐飲、生活日用、充值消費、交通出行、通訊物流、交通出行、醫療保健、住房物業、運動健康...)

基於大量資料的組合、排序和統計。根據最新的統計報告,每天將近100W+的行為資料產生,所以這個資料基數是非常大的。

而這個資料中心,對接很多的業務團隊,這些團隊根據自己的需要,對某些維度進行篩選,然後直接從我們的中心上下載資料(excel)文件進行分析。所以下個幾十萬上百萬行的資料是很常見的。

問題和解決方案

遇到的問題

目前遇到的主要問題是,隨著行為能力逐漸的完善閉環,使用者資料沉澱的也越來越多了,同時業務量的也在不斷擴大。

業務團隊有時候會下載超量的資料來進行分析,平臺上的資料下載能力就顯得尤為重要了。而我們的問題是下載效率太慢,10W的資料大約要5分鐘以上才能下載下來,這顯然有問題了。

解決步驟

程式碼是之前團隊遺留的,原先功能沒開放使用,沒有資料量,所以沒有發現問題。以下是原來的匯出模組,原程式如下,我做了基本還原。

現在如何保證資料的高效匯出是我們最重要的目標,這個也是業務團隊最關心的。 

  1     /**
  2      * 獲取匯出的Excel的檔案流資訊
  3      * @param exportData
  4      * @return
  5      * @throws Exception
  6      */
  7     private OutputStream getExportOutPutStream(List<UBehavDto> exportData) throws Exception {
  8         JSONObject object = new JSONObject();
  9         List<ExcelCell[]> excelCells = new ArrayList<>();
 10         String[] headers = new String[] { "A欄位","B欄位","C欄位","D","E","F","G","H","I","J","K","L",
 11                 "M","N","O","P","Q","R","S","T","U","V","W",
 12                 "X","Y","Z","AA","AB","AC","AD","AE欄位","AF欄位","AG欄位" };
 13         ExcelCell[] headerRow = getHeaderRow(headers);
 14         excelCells.add(headerRow);
 15         String pattern = "yyyy-MM-dd hh:mm:ss";
 16         for (UBehavDto uBehavDto:exportData) {
 17             String[] singleRow = new String[] { uBehavDto.getA(),uBehavDto.getB(),uBehavDto.getC(),uBehavDto.getD(),uBehavDto.getE(),uBehavDto.getF(),
 18                     DateFormatUtils.format(uBehavDto.getAddTime(), pattern),DateFormatUtils.format(uBehavDto.getDate(), pattern),
 19                     uBehavDto.getG(),uBehavDto.getH(),uBehavDto.getI(),uBehavDto.getJ(),uBehavDto.getK(),uBehavDto.getL(),uBehavDto.getM(),
 20                     uBehavDto.getN(),uBehavDto.getO(),uBehavDto.getP(),
 21                     uBehavDto.getQ(),uBehavDto.getR(),uBehavDto.getS(),String.valueOf(uBehavDto.getT()),uBehavDto.getMemo(),uBehavDto.getU(),uBehavDto.getV(),
 22                     uBehavDto.getW(),uBehavDto.getX(),
 23                     uBehavDto.getY(),uBehavDto.getZ(),uBehavDto.getAA(),uBehavDto.getAB(),uBehavDto.getAC() };
 24             ExcelCell[] cells = new ExcelCell[singleRow.length];
 25             ExcelCell getA=new ExcelCell();getA.setValue(uBehavDto.getA());
 26             ExcelCell getB=new ExcelCell();getB.setValue(uBehavDto.getB());
 27             ExcelCell getC=new ExcelCell();getC.setValue(uBehavDto.getC());
 28             ExcelCell getD=new ExcelCell();getD.setValue(uBehavDto.getD());
 29             ExcelCell getE=new ExcelCell();getE.setValue(uBehavDto.getE());
 30             ExcelCell getF=new ExcelCell();getF.setValue(uBehavDto.getF());
 31             ExcelCell getAddTime=new ExcelCell();getAddTime.setValue(DateFormatUtils.format(uBehavDto.getAddTime(), pattern));
 32             ExcelCell getDate=new ExcelCell();getDate.setValue(DateFormatUtils.format(uBehavDto.getDate(), pattern));
 33             ExcelCell getG=new ExcelCell();getG.setValue(uBehavDto.getG());
 34             ExcelCell getH=new ExcelCell();getH.setValue(uBehavDto.getH());
 35             ExcelCell getI=new ExcelCell();getI.setValue(uBehavDto.getI());
 36             ExcelCell getJ=new ExcelCell();getJ.setValue(uBehavDto.getJ());
 37             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getK());
 38             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getL());
 39             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getM());
 40             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getN());
 41             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getO());
 42             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getP());
 43             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getQ());
 44             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getR());
 45             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getS());
 46             ExcelCell a=new ExcelCell();a.setValue(String.valueOf(uBehavDto.getT()));
 47             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getMemo());
 48             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getU());
 49             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getV());
 50             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getW());
 51             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getX());
 52             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getY());
 53             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getZ());
 54             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getAA());
 55             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getAB());
 56             ExcelCell a=new ExcelCell();a.setValue(uBehavDto.getAC());
 57             ExcelCell[] cells = {
 58                     new ExcelCell(uBehavDto.getA()),
 59                     new ExcelCell().setValue(uBehavDto.getB()),
 60                     new ExcelCell().setValue(uBehavDto.getC()),
 61                     new ExcelCell().setValue(uBehavDto.getD()),
 62                     new ExcelCell().setValue(uBehavDto.getE()),
 63                     new ExcelCell().setValue(uBehavDto.getF()),
 64                     new ExcelCell().setValue(DateFormatUtils.format(uBehavDto.getAddTime(), pattern)),
 65                     new ExcelCell().setValue(DateFormatUtils.format(uBehavDto.getDate(), pattern)),
 66                     new ExcelCell().setValue(uBehavDto.getG()),
 67                     new ExcelCell().setValue(uBehavDto.getH()),
 68                     new ExcelCell().setValue(uBehavDto.getI()),
 69                     new ExcelCell().setValue(uBehavDto.getJ()),
 70                     new ExcelCell().setValue(uBehavDto.getK()),
 71                     new ExcelCell().setValue(uBehavDto.getL()),
 72                     new ExcelCell().setValue(uBehavDto.getM()),
 73                     new ExcelCell().setValue(uBehavDto.getN()),
 74                     new ExcelCell().setValue(uBehavDto.getO()),
 75                     new ExcelCell().setValue(uBehavDto.getP()),
 76                     new ExcelCell().setValue(uBehavDto.getQ()),
 77                     new ExcelCell().setValue(uBehavDto.getR()),
 78                     new ExcelCell().setValue(uBehavDto.getS()),
 79                     new ExcelCell().setValue(String.valueOf(uBehavDto.getT())),
 80                     new ExcelCell().setValue(uBehavDto.getMemo()),
 81                     new ExcelCell().setValue(uBehavDto.getU()),
 82                     new ExcelCell().setValue(uBehavDto.getV()),
 83                     new ExcelCell().setValue(uBehavDto.getW()),
 84                     new ExcelCell().setValue(uBehavDto.getX()),
 85                     new ExcelCell().setValue(uBehavDto.getY()),
 86                     new ExcelCell().setValue(uBehavDto.getZ()),
 87                     new ExcelCell().setValue(uBehavDto.getAA()),
 88                     new ExcelCell().setValue(uBehavDto.getAB()),
 89                     new ExcelCell().setValue(uBehavDto.getAC())
 90             };
 91 
 92             for(int idx=0;idx<singleRow.length;idx++) {
 93                 ExcelCell cell = new ExcelCell();
 94                 cell.setValue(singleRow[idx]);
 95                 cells[idx] = cell;
 96             }
 97             excelCells.add(cells);
 98         }
 99         object.put("行為資料", excelCells);
100         ExcelUtils utils = new ExcelUtils();
101         OutputStream outputStream = utils.writeExcel(object);
102         return outputStream;
103     }

 

看看標紅的程式碼,這個生成Excel的方式是對Excel中的每一個cell進行渲染,逐行的進行資料填充,效率太慢了,根據日誌分析發現:基本時間都耗費在資料生成Excel上。每生成1W左右的資料基本

消耗1分鐘的時間。原來在其他業務中他只是作為簡量資料匯出來使用,比如幾百條的資料,很快就出來了,但是遇到大量資料匯出的情況,效能問題就立馬現形了。

 

團隊內討論了一下並參考了資料,發現原來業內有很多好用強大的Excel處理元件,我們優先選用阿里的easy excel來做一下嘗試。

Pom新增 easyexcel 如下:

 <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>2.1.4</version>
 </dependency>

程式碼:dto內容(中文為配置好的表頭):

 1 package com.xxx.xxx.modules.worklog.dto;
 2 
 3 import com.alibaba.excel.annotation.ExcelProperty;
 4 import lombok.Getter;
 5 import lombok.Setter;
 6 import java.io.Serializable;
 7 import java.util.Date;
 8 
 9 /**
10  * <p>Description:XX表基本資訊 </p>
11  * <p>Copyright: Copyright (c) 2021  </p>
12  * <p>Company: XX Co., Ltd.             </p>
13  *
14  * @author brand
15  * @date 2021-06-26 10:07:46
16  * <p>Update Time:                      </p>
17  * <p>Updater:                          </p>
18  * <p>Update Comments:                  </p>
19  */
20 @Setter
21 @Getter
22 public class WorkLogDto implements Serializable {
23     private static final long serialVersionUID = -5523294561640180605L;
24     @ExcelProperty("A欄位")
25     private String aClolumn;
26     @ExcelProperty("B欄位")
27     private String BColumn;
28     @ExcelProperty("C欄位")
29     private String cColumn;
30     @ExcelProperty("D欄位")
31     private String dColumn;
32     @ExcelProperty("E欄位")
33     private String eColumn;
34     @ExcelProperty("F欄位")
35     private String fColumn;
36     @ExcelProperty("G欄位")
37     private Date gColumn;
38     @ExcelProperty("H欄位")
39     private Date hColumn;
40     @ExcelProperty("I欄位")
41     private String iColumn;
42     @ExcelProperty("J欄位")
43     private String jColumn;
44     @ExcelProperty("K欄位")
45     private String kColumn;
46     @ExcelProperty("L欄位")
47     private String lColumn;
48     @ExcelProperty("M欄位")
49     private String mColumn;
50     @ExcelProperty("N欄位")
51     private String nColumn;
52     @ExcelProperty("O欄位")
53     private String oColumn;
54     @ExcelProperty("P欄位")
55     private String pColumn;
56     @ExcelProperty("Q欄位")
57     private String qColumn;
58     @ExcelProperty("R欄位")
59     private String rColumn;
60     @ExcelProperty("S欄位")
61     private String sColumn;
62     @ExcelProperty("T欄位")
63     private String tColumn;
64     @ExcelProperty("U欄位")
65     private String uColumn;
66     @ExcelProperty("V欄位")
67     private double vColumn;
68     @ExcelProperty("W欄位")
69     private String wColumn;
70     @ExcelProperty("X欄位")
71     private String xClumn;
72     @ExcelProperty("Y欄位")
73     private String yColumn;
74     @ExcelProperty("Z欄位")
75     private String zColumn;    
76

生成檔案流的步驟(程式碼很清晰了):

 1 /**
 2      * EasyExcel 生成檔案流
 3      * @param exportData
 4      * @return
 5      */
 6     private byte[] getEasyExcelOutPutStream(List<WorkLogDto> exportData) {
 7         try {
 8             WriteCellStyle headWriteCellStyle = new WriteCellStyle();
 9             WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
10             contentWriteCellStyle.setWrapped(true);
11             HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
12             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
13             EasyExcel.write(outputStream, WorkLogDto.class).sheet("行為業務資料") //  Sheet名稱
14                     .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
15                     .registerWriteHandler(horizontalCellStyleStrategy)
16                     .doWrite(exportData);
17             byte[] bytes = outputStream.toByteArray();
18             // 關閉流
19             outputStream.close();
20             return bytes;
21         }
22         catch (Exception ex) {
23             log.error("輸出Excel檔案流失敗:"+ex.getMessage());
24             return null;
25         }
26     } 

完整生成Excel檔案流並上傳:

 1      /**
 2      * 上傳使用者資料包表
 3      * @param prmWorkLogExport
 4      * @param order
 5      * @param orderType
 6      * @return
 7      */
 8     @Override
 9     @Async
10     public Object uploadWorkLogData(PrmWorkLogExport prmWorkLogExport,ExportTaskDomain domain, String order, String orderType,String suid) {
11         try {
12             log.info(String.format("ExportWorkLog->:%s", "開始獲取資料"));
13             List<WorkLogDto> logList = getLogList(prmWorkLogExport,order,orderType);
14             log.info(String.format("ExportWorkLog->:結束獲取資料,總 %d 條資料", logList.size()));
15             byte[] bytes = getEasyExcelOutPutStream(logList);
16             log.info(String.format("ExportWorkLog->:%s","完成資料轉excel檔案流"));
17             /* 暫時作廢 Todo
18             int max=55;int min=40;
19             Random random = new Random();
20             int rd = random.nextInt(max)%(max-min+1) + min;
21             modifyExportTask(domain.getId(),0,rd);//計算生成資料的進度
22             */
23             //開始投遞檔案叢集伺服器,並將結果反寫到資料庫
24             log.info(String.format("ExportWorkLog->:%s","開始將資料寫入檔案服務系統"));
25             Dentry dentry = csService.coverUploadByByteArrayByToken(domain, bytes);
26             //執行非同步記錄,以免連線池關閉
27             executor.execute(() -> {
28                 try {
29                    asynworkService.finishExportTask(domain.getId(),domain.getFileName(), dentry);
30                 } catch (Exception e) {
31                     log.error("更新任務狀態失敗:", e.getMessage());
32                 }
33             });
34 
35         } catch (Exception ex) {
36             // 1完成 0進行中 2生產錯誤
37             String updateSql = String.format(" update exporttask set statu=2 where taskid=%s;",domain.getId());
38             Query query = entityManager.createNativeQuery(updateSql);
39             query.executeUpdate();
40             entityManager.flush();
41             entityManager.clear();
42             log.info(String.format("ExportWorkLog->:上傳檔案異常:%s",ex.getMessage()));
43         }
44         return null;
45     }

改用阿里 easyexcel 元件後,10W+ 的資料從生成Excel檔案流到上傳只要8秒,原來約要8分鐘 ,以下為各個步驟時間點的日誌記錄,可以看出時間消耗:

Java:匯出Excel大批量資料的優化過程

Java:匯出Excel大批量資料的優化過程

整理工具類

工具類和使用說明

參考網上整理的工具類,有些類、方法在之前的版本是ok的,新版本下被標記為過時了。

  1 package com.nd.helenlyn.common.utils;
  2 
  3 import com.alibaba.excel.EasyExcelFactory;
  4 import com.alibaba.excel.ExcelWriter;
  5 import com.alibaba.excel.context.AnalysisContext;
  6 import com.alibaba.excel.event.AnalysisEventListener;
  7 import com.alibaba.excel.metadata.BaseRowModel;
  8 import com.alibaba.excel.metadata.Sheet;
  9 import lombok.Data;
 10 import lombok.Getter;
 11 import lombok.Setter;
 12 import lombok.extern.slf4j.Slf4j;
 13 import org.springframework.util.CollectionUtils;
 14 import org.springframework.util.StringUtils;
 15 
 16 import java.io.FileInputStream;
 17 import java.io.FileNotFoundException;
 18 import java.io.FileOutputStream;
 19 import java.io.IOException;
 20 import java.io.InputStream;
 21 import java.io.OutputStream;
 22 import java.util.ArrayList;
 23 import java.util.Collections;
 24 import java.util.List;
 25 
 26 /**
 27  * @author brand
 28  * @Description:
 29  * @Copyright: Copyright (c) 2021
 30  * @Company: XX, Inc. All Rights Reserved.
 31  * @date 2021/7/10 3:54 下午
 32  * @Update Time:
 33  * @Updater:
 34  * @Update Comments:
 35  */
 36 @Slf4j
 37 public class EasyExcelUtil {
 38         private static Sheet initSheet;
 39         static {
 40             initSheet = new Sheet(1, 0);
 41             initSheet.setSheetName("sheet");
 42             //設定自適應寬度,避免表頭重疊情況
 43             initSheet.setAutoWidth(Boolean.TRUE);
 44         }
 45 
 46         /**
 47          * 讀取少於1000行資料的情況
 48          * @param filePath 檔案存放的絕對路徑
 49          * @return
 50          */
 51         public static List<Object> lessThan1000Row(String filePath){
 52             return lessThan1000RowBySheet(filePath,null);
 53         }
 54 
 55         /**
 56          * 讀小於1000行資料, 帶樣式
 57          * filePath 檔案存放的絕對路徑
 58          * initSheet :
 59          *      sheetNo: sheet頁碼,預設為1
 60          *      headLineMun: 從第幾行開始讀取資料,預設為0, 表示從第一行開始讀取
 61          *      clazz: 返回資料List<Object> 中Object的類名
 62          */
 63         public static List<Object> lessThan1000RowBySheet(String filePath, Sheet sheet){
 64             if(!StringUtils.hasText(filePath)){
 65                 return null;
 66             }
 67 
 68             sheet = sheet != null ? sheet : initSheet;
 69 
 70             InputStream fileStream = null;
 71             try {
 72                 fileStream = new FileInputStream(filePath);
 73                 return EasyExcelFactory.read(fileStream, sheet);
 74             } catch (FileNotFoundException e) {
 75                 log.info("找不到檔案或檔案路徑錯誤, 檔案:{}", filePath);
 76             }finally {
 77                 try {
 78                     if(fileStream != null){
 79                         fileStream.close();
 80                     }
 81                 } catch (IOException e) {
 82                     log.info("excel檔案讀取失敗, 失敗原因:{}", e);
 83                 }
 84             }
 85             return null;
 86         }
 87 
 88         /**
 89          * 讀大於1000行資料
 90          * @param filePath 檔案存放的絕對路徑
 91          * @return
 92          */
 93         public static List<Object> mareThan1000Row(String filePath){
 94             return moreThan1000RowBySheet(filePath,null);
 95         }
 96 
 97         /**
 98          * 讀大於1000行資料, 帶樣式
 99          * @param filePath 檔案存放的絕對路徑
100          * @return
101          */
102         public static List<Object> moreThan1000RowBySheet(String filePath, Sheet sheet){
103             if(!StringUtils.hasText(filePath)){
104                 return null;
105             }
106 
107             sheet = sheet != null ? sheet : initSheet;
108 
109             InputStream fileStream = null;
110             try {
111                 fileStream = new FileInputStream(filePath);
112                 ExcelListener excelListener = new ExcelListener();
113                 EasyExcelFactory.readBySax(fileStream, sheet, excelListener);
114                 return excelListener.getDatas();
115             } catch (FileNotFoundException e) {
116                 log.error("找不到檔案或檔案路徑錯誤, 檔案:{}", filePath);
117             }finally {
118                 try {
119                     if(fileStream != null){
120                         fileStream.close();
121                     }
122                 } catch (IOException e) {
123                     log.error("excel檔案讀取失敗, 失敗原因:{}", e);
124                 }
125             }
126             return null;
127         }
128 
129         /**
130          * 生成excle
131          * @param filePath  絕對路徑, 如:/home/{user}/Downloads/123.xlsx
132          * @param data 資料來源
133          * @param head 表頭
134          */
135         public static void writeBySimple(String filePath, List<List<Object>> data, List<String> head){
136             writeSimpleBySheet(filePath,data,head,null);
137         }
138 
139         /**
140          * 生成excle
141          * @param filePath 絕對路徑, 如:/home/{user}/Downloads/123.xlsx
142          * @param data 資料來源
143          * @param sheet excle頁面樣式
144          * @param head 表頭
145          */
146         public static void writeSimpleBySheet(String filePath, List<List<Object>> data, List<String> head, Sheet sheet){
147             sheet = (sheet != null) ? sheet : initSheet;
148 
149             if(head != null){
150                 List<List<String>> list = new ArrayList<>();
151                 head.forEach(h -> list.add(Collections.singletonList(h)));
152                 sheet.setHead(list);
153             }
154 
155             OutputStream outputStream = null;
156             ExcelWriter writer = null;
157             try {
158                 outputStream = new FileOutputStream(filePath);
159                 writer = EasyExcelFactory.getWriter(outputStream);
160                 writer.write1(data,sheet);
161             } catch (FileNotFoundException e) {
162                 log.error("找不到檔案或檔案路徑錯誤, 檔案:{}", filePath);
163             }finally {
164                 try {
165                     if(writer != null){
166                         writer.finish();
167                     }
168 
169                     if(outputStream != null){
170                         outputStream.close();
171                     }
172 
173                 } catch (IOException e) {
174                     log.error("excel檔案匯出失敗, 失敗原因:{}", e);
175                 }
176             }
177 
178         }
179 
180         /**
181          * 生成excle
182          * @param filePath 檔案存放的絕對路徑, 如:/home/{user}/Downloads/123.xlsx
183          * @param data 資料來源
184          */
185         public static void writeWithTemplate(String filePath, List<? extends BaseRowModel> data){
186             writeWithTemplateAndSheet(filePath,data,null);
187         }
188 
189         /**
190          * 生成excle
191          * @param filePath 檔案存放的絕對路徑, 如:/home/user/Downloads/123.xlsx
192          * @param data 資料來源
193          * @param sheet excle頁面樣式
194          */
195         public static void writeWithTemplateAndSheet(String filePath, List<? extends BaseRowModel> data, Sheet sheet){
196             if(CollectionUtils.isEmpty(data)){
197                 return;
198             }
199 
200             sheet = (sheet != null) ? sheet : initSheet;
201             sheet.setClazz(data.get(0).getClass());
202 
203             OutputStream outputStream = null;
204             ExcelWriter writer = null;
205             try {
206                 outputStream = new FileOutputStream(filePath);
207                 writer = EasyExcelFactory.getWriter(outputStream);
208                 writer.write(data,sheet);
209             } catch (FileNotFoundException e) {
210                 log.error("找不到檔案或檔案路徑錯誤, 檔案:{}", filePath);
211             }finally {
212                 try {
213                     if(writer != null){
214                         writer.finish();
215                     }
216 
217                     if(outputStream != null){
218                         outputStream.close();
219                     }
220                 } catch (IOException e) {
221                     log.error("excel檔案匯出失敗, 失敗原因:{}", e);
222                 }
223             }
224 
225         }
226 
227         /**
228          * 生成多Sheet的excle
229          * @param filePath 絕對路徑, 如:/home/{user}/Downloads/123.xlsx
230          * @param multipleSheelPropetys
231          */
232         public static void writeWithMultipleSheel(String filePath,List<MultipleSheelPropety> multipleSheelPropetys){
233             if(CollectionUtils.isEmpty(multipleSheelPropetys)){
234                 return;
235             }
236 
237             OutputStream outputStream = null;
238             ExcelWriter writer = null;
239             try {
240                 outputStream = new FileOutputStream(filePath);
241                 writer = EasyExcelFactory.getWriter(outputStream);
242                 for (MultipleSheelPropety multipleSheelPropety : multipleSheelPropetys) {
243                     Sheet sheet = multipleSheelPropety.getSheet() != null ? multipleSheelPropety.getSheet() : initSheet;
244                     if(!CollectionUtils.isEmpty(multipleSheelPropety.getData())){
245                         sheet.setClazz(multipleSheelPropety.getData().get(0).getClass());
246                     }
247                     writer.write(multipleSheelPropety.getData(), sheet);
248                 }
249 
250             } catch (FileNotFoundException e) {
251                 log.error("找不到檔案或檔案路徑錯誤, 檔案:{}", filePath);
252             }finally {
253                 try {
254                     if(writer != null){
255                         writer.finish();
256                     }
257 
258                     if(outputStream != null){
259                         outputStream.close();
260                     }
261                 } catch (IOException e) {
262                     log.error("excel檔案匯出失敗, 失敗原因:{}", e);
263                 }
264             }
265 
266         }
267 
268 
269         /*********************以下為內部類,可以提取到獨立類中******************************/
270 
271         @Data
272         public static class MultipleSheelPropety{
273 
274             private List<? extends BaseRowModel> data;
275 
276             private Sheet sheet;
277         }
278 
279         /**
280          * 解析監聽器,
281          * 每解析一行會回撥invoke()方法。
282          * 整個excel解析結束會執行doAfterAllAnalysed()方法
283          *
284          * @author: chenmingjian
285          * @date: 19-4-3 14:11
286          */
287         @Getter
288         @Setter
289         public static class ExcelListener extends AnalysisEventListener {
290 
291             private List<Object> datas = new ArrayList<>();
292 
293             /**
294              * 逐行解析
295              * object : 當前行的資料
296              */
297             @Override
298             public void invoke(Object object, AnalysisContext context) {
299                 //當前行
300                 // context.getCurrentRowNum()
301                 if (object != null) {
302                     datas.add(object);
303                 }
304             }
305 
306 
307             /**
308              * 解析完所有資料後會呼叫該方法
309              */
310             @Override
311             public void doAfterAllAnalysed(AnalysisContext context) {
312                 //解析結束銷燬不用的資源
313             }
314 
315         }
316 }

參考資料

語雀例子文件:https://www.yuque.com/easyexcel/doc/easyexcel
easyexcel GitHub地址:https://github.com/alibaba/easyexcel