天天看點

【Alibaba工具型技術系列】「EasyExcel技術專題」實戰項目中常用的Excel操作指南

EasyExcel教程

本文使用的技術是Alibaba集團開源的EasyExcel技術,該技術是針對Apache POI技術的封裝和優化,主要解決了POI技術的耗記憶體問題,并且提供了較好的API使用。
  • 使用步驟繁瑣
  • 動态寫出Excel操作非常麻煩
  • 對于新手來說,很難在短時間内上手
  • 讀寫時需要占用較大的内容,當資料量大時容器發生OOM

Maven依賴

<!-- easyexcel 依賴 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.7</version>
        </dependency>           

EasyExcel API分析介紹

  • Ø EasyExcel入口類,用于建構開始各種操作
  • Ø ExcelReaderBuilder ExcelWriterBuilder 建構出一個 ReadWorkbook WriteWorkbook,可以了解成一個excel對象,一個excel隻要建構一個
  • Ø ExcelReaderSheetBuilder ExcelWriterSheetBuilder 建構出一個 ReadSheet WriteSheet對象,可以了解成excel裡面的一頁,每一頁都要建構一個
  • Ø ReadListener在每一行讀取完畢後都會調用ReadListener來處理資料
  • Ø WriteHandler在每一個操作包括建立單元格、建立表格等都會調用WriteHandler來處理資料
  • Ø 所有配置都是繼承的,Workbook的配置會被Sheet繼承,是以在用EasyExcel設定參數的時候,在EasyExcel…sheet()方法之前作用域是整個sheet,之後針對單個sheet

EasyExcel 注解

  • Ø ExcelProperty 指定目前字段對應excel中的那一列。可以根據名字或者Index去比對。當然也可以不寫,預設第一個字段就是index=0,以此類推。千萬注意,要麼全部不寫,要麼全部用index,要麼全部用名字去比對。千萬别三個混着用,除非你非常了解源代碼中三個混着用怎麼去排序的。
  • Ø ExcelIgnore 預設所有字段都會和excel去比對,加了這個注解會忽略該字段
  • Ø DateTimeFormat 日期轉換,用String去接收excel日期格式的資料會調用這個注解。裡面的value參照java.text.SimpleDateFormat
  • Ø NumberFormat 數字轉換,用String去接收excel數字格式的資料會調用這個注解。裡面的value參照java.text.DecimalFormat
  • Ø ExcelIgnoreUnannotated 預設不加ExcelProperty 的注解的都會參與讀寫,加了不會參與

通用參數

  • Ø ReadWorkbook,ReadSheet 都會有的參數,如果為空,預設使用上級。
  • Ø converter 轉換器,預設加載了很多轉換器。也可以自定義。
  • Ø readListener 監聽器,在讀取資料的過程中會不斷的調用監聽器。
  • Ø headRowNumber 需要讀的表格有幾行頭資料。預設有一行頭,也就是認為第二行開始起為資料。
  • Ø head 與clazz二選一。讀取檔案頭對應的清單,會根據清單比對資料,建議使用class。
  • Ø clazz 與head二選一。讀取檔案的頭對應的class,也可以使用注解。如果兩個都不指定,則會讀取全部資料。
  • Ø autoTrim 字元串、表頭等資料自動trim
  • Ø password 讀的時候是否需要使用密碼

ReadWorkbook(了解成excel對象)參數

  • Ø excelType 目前excel的類型 預設會自動判斷
  • Ø inputStream 與file二選一。讀取檔案的流,如果接收到的是流就隻用,不用流建議使用file參數。因為使用了inputStream easyexcel會幫忙建立臨時檔案,最終還是file
  • Ø file 與inputStream二選一。讀取檔案的檔案。
  • Ø autoCloseStream 自動關閉流。
  • Ø readCache 預設小于5M用 記憶體,超過5M會使用 EhCache,這裡不建議使用這個參數。

ReadSheet(就是excel的一個Sheet)參數

  • Ø sheetNo 需要讀取Sheet的編碼,建議使用這個來指定讀取哪個Sheet
  • Ø sheetName 根據名字去比對Sheet,excel 2003不支援根據名字去比對

注解

  • Ø ExcelProperty index 指定寫到第幾列,預設根據成員變量排序。value指定寫入的名稱,預設成員變量的名字,多個value可以參照快速開始中的複雜頭
  • Ø ExcelIgnore 預設所有字段都會寫入excel,這個注解會忽略這個字段
  • Ø DateTimeFormat 日期轉換,将Date寫到excel會調用這個注解。裡面的value參照java.text.SimpleDateFormat
  • Ø NumberFormat 數字轉換,用Number寫excel會調用這個注解。裡面的value參照java.text.DecimalFormat

參數

  • Ø WriteWorkbook,WriteSheet ,WriteTable都會有的參數,如果為空,預設使用上級。
  • Ø writeHandler 寫的處理器。可以實作WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在寫入excel的不同階段會調用
  • Ø relativeHeadRowIndex 距離多少行後開始。也就是開頭空幾行
  • Ø needHead 是否導出頭
  • Ø head 與clazz二選一。寫入檔案的頭清單,建議使用class。
  • Ø clazz 與head二選一。寫入檔案的頭對應的class,也可以使用注解。

WriteWorkbook(了解成excel對象)參數

  • Ø excelType 目前excel的類型 預設xlsx
  • Ø outputStream 與file二選一。寫入檔案的流
  • Ø file 與outputStream二選一。寫入的檔案
  • Ø templateInputStream 模闆的檔案流
  • Ø templateFile 模闆檔案
  • Ø password 寫的時候是否需要使用密碼
  • Ø useDefaultStyle 寫的時候是否是使用預設頭

WriteSheet(就是excel的一個Sheet)參數

  • Ø sheetNo 需要寫入的編碼。預設0
  • Ø sheetName 需要些的Sheet名稱,預設同sheetNo

WriteTable(就把excel的一個Sheet,一塊區域看一個table)參數

  • Ø tableNo 需要寫入的編碼。預設0

EasyExcel用法指南

簡單的讀取excel檔案
public void read() {
    String fileName = "demo.xlsx";
    // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
    // 參數一:讀取的excel檔案路徑
    // 參數二:讀取sheet的一行,将參數封裝在DemoData實體類中
    // 參數三:讀取每一行的時候會執行DemoDataListener監聽器
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}           
簡單的寫入excel檔案
@Test
public void simpleWrite() {
    String fileName = "demo.xlsx";
    // 這裡 需要指定寫用哪個class去讀,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
    // 如果這裡想使用03 則 傳入excelType參數即可
    // 參數一:寫入excel檔案路徑
    // 參數二:寫入的資料類型是DemoData
    // data()方法是寫入的資料,結果是List<DemoData>集合
    EasyExcel.write(fileName, DemoData.class).sheet("模闆").doWrite(data());
}           
Web上傳與下載下傳
/**
    excel檔案的下載下傳
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");
    EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模闆").doWrite(data());
}

/**
    excel檔案的上傳
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
    EasyExcel.read(file.getInputStream(), DemoData.class, new DemoDataListener()).sheet().doRead();
    return "success";
}           

詳解讀取Excel

對象模型
// 如果沒有特殊說明,下面的案例将預設使用這個實體類
public class DemoData {
    /**
     * 強制讀取第三個 這裡不建議 index 和 name 同時用,要麼一個對象隻用index,要麼一個對象隻用name去比對
     */
    @ExcelProperty(index = 2)
    //我想接收百分比的數字
    @NumberFormat("#.##%")
    @ExcelProperty(value="浮點數标題", converter = CustomStringConverter.class)
    private Double doubleData;
    /**
     * 用名字去比對,這裡需要注意,如果名字重複,會導緻隻有一個字段讀取到資料
     */
    @ExcelProperty(value="字元串标題", converter = CustomStringConverter.class)
    // converter屬性定義自己的字元串轉換器
    private String string;

    @DateTimeFormat("yyyy年MM月dd日 HH時mm分ss秒")
    @ExcelProperty("日期标題")
    //這裡用string 去接日期才能格式化
    private Date date;
}           
監聽器
// 如果沒有特殊說明,下面的案例将預設使用這個監聽器
public class DemoDataListener extends AnalysisEventListener<DemoData> {

    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 如果使用了spring,請使用這個構造方法。每次建立Listener的時候需要把spring管理的類傳進來
     */
    public DemoDataListener() {}
    /**
     * 這個每一條資料解析都會來調用
     *
     * @param data
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println("解析到一條資料:{}", JSON.toJSONString(data));
        list.add(data);
    }

    /**
     * 所有資料解析完成了 都會來調用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.println(JSON.toJSONString(list));
    }
}           
代碼
@Test
public void simpleRead() {
    // 寫法1:
    String fileName = "demo.xlsx";
    // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    // 寫法2:
    fileName = "demo.xlsx";
    ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
    ReadSheet readSheet = EasyExcel.readSheet(0).build();
    excelReader.read(readSheet);
    // 這裡千萬别忘記關閉,讀的時候會建立臨時檔案,到時磁盤會崩的
    excelReader.finish();
}           
讀取多個sheet
@Test
public void repeatedRead() {
    String fileName = "demo.xlsx";
    // 讀取全部sheet
    // 這裡需要注意 DemoDataListener的doAfterAllAnalysed 會在每個sheet讀取完畢後調用一次。然後所有sheet都會往同一個DemoDataListener裡面寫
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
    // 讀取部分sheet
    fileName = "demo.xlsx";
    ExcelReader excelReader = EasyExcel.read(fileName).build();
    // 這裡為了簡單 是以注冊了 同樣的head 和Listener 自己使用功能必須不同的Listener
    // readSheet參數設定讀取sheet的序号
    ReadSheet readSheet1 =
        EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
    ReadSheet readSheet2 =
        EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
    // 這裡注意 一定要把sheet1 sheet2 一起傳進去,不然有個問題就是03版的excel 會讀取多次,浪費性能
    excelReader.read(readSheet1, readSheet2);
    // 這裡千萬别忘記關閉,讀的時候會建立臨時檔案,到時磁盤會崩的
    excelReader.finish();
}           
自定義格式轉換
public class CustomStringStringConverter implements Converter<String> {

    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 這裡讀的時候會調用
     *
     * @param cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定義:" + cellData.getStringValue();
    }

    /**
     * 這裡是寫的時候會調用 不用管
     *
     * @param value
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}           

多行頭

@Test
public void complexHeaderRead() {
    String fileName = "demo.xlsx";
    // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
        // 這裡可以設定1,因為頭就是一行。如果多行頭,可以設定其他值。不傳入預設1行
        .headRowNumber(1).doRead();
}           

讀取表頭資料

覆寫監聽器invokeHeadMap方法

/**
 * 這裡會一行行的傳回頭
 * 監聽器隻需要重寫這個方法就可以讀取到頭資訊
 * @param headMap
 * @param context
 */
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    LOGGER.info("解析到一條頭資料:{}", JSON.toJSONString(headMap));
}           

異常處理

覆寫監聽器onException方法

/**
* 監聽器實作這個方法就可以在讀取資料的時候擷取到異常資訊
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
    LOGGER.error("解析失敗,但是繼續解析下一行:{}", exception.getMessage());
    // 如果是某一個單元格的轉換異常 能擷取到具體行号
    // 如果要擷取頭的資訊 配合invokeHeadMap使用
    if (exception instanceof ExcelDataConvertException) {
        ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
        LOGGER.error("第{}行,第{}列解析異常", excelDataConvertException.getRowIndex(),
            excelDataConvertException.getColumnIndex());
    }
}           

導出指定的列

@Test
public void excludeOrIncludeWrite() {

    String fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
    // 忽略 date 不導出
    Set<String> excludeColumnFiledNames = new HashSet<String>();
    excludeColumnFiledNames.add("date");
    // 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
    EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("忽略date")
        .doWrite(data());
    fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
    // 根據使用者傳入字段 假設我們隻要導出 date
    Set<String> includeColumnFiledNames = new HashSet<String>();
    includeColumnFiledNames.add("date");
    // 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
    EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("導出date")
        .doWrite(data());
}           

調整指定列順序

public class IndexData {
    /**
    * 導出的excel第二列和第四列将空置
    */
    @ExcelProperty(value = "字元串标題", index = 0)
    private String string;
    @ExcelProperty(value = "日期标題", index = 2)
    private Date date;
    @ExcelProperty(value = "浮點數标題", index = 4)
    private Double doubleData;
}
           

複雜頭寫入

public class ComplexHeadData {
    /**
    * 主标題 将整合為一個單元格效果如下:
    * —————————————————————————
    * |          主标題        |
    * —————————————————————————
    * |字元串标題|日期标題|數字标題|
    * —————————————————————————
    */
    @ExcelProperty({"主标題", "字元串标題"})
    private String string;
    @ExcelProperty({"主标題", "日期标題"})
    private Date date;
    @ExcelProperty({"主标題", "數字标題"})
    private Double doubleData;
}           
前面屬于主标題,後面屬于副标題

圖檔導出

@Data
@ContentRowHeight(200)
@ColumnWidth(200 / 8)
public class ImageData {
    // 圖檔導出方式有5種
    private File file;
    private InputStream inputStream;
    /**
     * 如果string類型 必須指定轉換器,string預設轉換成string,該轉換器是官方支援的
     */
    @ExcelProperty(converter = StringImageConverter.class)
    private String string;
    private byte[] byteArray;
    /**
     * 根據url導出 版本2.1.1才支援該種模式
     */
    private URL url;
}

 @Test
public void imageWrite() throws Exception {
    String fileName = "imageWrite" + System.currentTimeMillis() + ".xlsx";
    // 如果使用流 記得關閉
    InputStream inputStream = null;
    try {
        List<ImageData> list = new ArrayList<ImageData>();
        ImageData imageData = new ImageData();
        list.add(imageData);
        String imagePath = "converter" + File.separator + "img.jpg";
        // 放入五種類型的圖檔 根據實際使用隻要選一種即可
        imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));
        imageData.setFile(new File(imagePath));
        imageData.setString(imagePath);
        inputStream = FileUtils.openInputStream(new File(imagePath));
        imageData.setInputStream(inputStream);
        imageData.setUrl(new URL(
            "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));
        EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);
    } finally {
        if (inputStream != null) {
            inputStream.close();
        }
    }
}
           

列寬、行高

@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
    @ExcelProperty("字元串标題")
    private String string;
    @ExcelProperty("日期标題")
    private Date date;
    /**
     * 寬度為50,覆寫上面的寬度25
     */
    @ColumnWidth(50)
    @ExcelProperty("數字标題")
    private Double doubleData;
}           
  • @HeadRowHeight(value = 35) // 表頭行高
  • @ContentRowHeight(value = 25) // 内容行高
  • @ColumnWidth(value = 50) // 列寬

此外還有,自适應寬度,但是這個不是特别精确

@Test
void contextLoads() {
    EasyExcel.write("自适應.xlsx", Student.class)
        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
        .sheet()
        .doWrite(getData());
}           

動态表頭

@Test
public void dynamicHeadWrite() {
    String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
    EasyExcel.write(fileName)
        // 這裡放入動态頭
        .head(head()).sheet("模闆")
        // 當然這裡資料也可以用 List<List<String>> 去傳入
        .doWrite(data());
}
// 動态表頭的資料格式List<List<String>>
private List<List<String>> head() {
    List<List<String>> list = new ArrayList<List<String>>();
    List<String> head0 = new ArrayList<String>();
    head0.add("字元串" + System.currentTimeMillis());
    List<String> head1 = new ArrayList<String>();
    head1.add("數字" + System.currentTimeMillis());
    List<String> head2 = new ArrayList<String>();
    head2.add("日期" + System.currentTimeMillis());
    list.add(head0);
    list.add(head1);
    list.add(head2);
    return list;
}           

合并單元格

@Test
 public void mergeWrite() {
     String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";
     LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
     EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("合并單元格")
         .doWrite(data());
 }           
  • 每隔2行會合并 把eachColumn 設定成 3 也就是我們資料的長度,是以就第一列會合并。當然其他合并政策也可以自己寫
  • 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉

web資料寫出

@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    // 這裡URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系
    String fileName = URLEncoder.encode("資料寫出", "UTF-8");
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模闆").doWrite(data());
}           

模闆格式導出

如果需要橫向填充隻需要模闆設定好就可以。
簡單的Excel模闆
public class FillData {
    private String name;
    private double number;
    // getting setting
}           
實作模闆填充
@Test
public void simpleFill() {
    String templateFileName = "simple.xlsx";

    // 方案1 根據對象填充
    String fileName = System.currentTimeMillis() + ".xlsx";
    // 這裡 會填充到第一個sheet, 然後檔案流會自動關閉
    FillData fillData = new FillData();
    fillData.setName("知春秋");
    fillData.setNumber(25);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);

    // 方案2 根據Map填充
    fileName = System.currentTimeMillis() + ".xlsx";
    // 這裡 會填充到第一個sheet, 然後檔案流會自動關閉
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("name", "知春秋");
    map.put("number", 25);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}           
  • 模闆注意 用{} 來表示你要用的變量 如果本來就有"{","}" 特殊字元 用"{","}"代替

複雜的填充

@Test
public void complexFill() {
    String templateFileName = "complex.xlsx";
    String fileName = System.currentTimeMillis() + ".xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    // 如果資料量大 list不是最後一行 參照下一個
    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
    excelWriter.fill(data(), fillConfig, writeSheet);
    excelWriter.fill(data(), fillConfig, writeSheet);
    // 其他參數可以使用Map封裝
    Map<String, Object> map = new HashMap<String, Object>();
    excelWriter.fill(map, writeSheet);
    excelWriter.finish();
}           
  • // 這裡注意 入參用了forceNewRow 代表在寫入list的時候不管list下面有沒有空行 都會建立一行,然後下面的資料往後移動。預設 是false,會直接使用下一行,如果沒有則建立。
  • // forceNewRow 如果設定了true,有個缺點 就是他會把所有的資料都放到記憶體了,是以慎用
  • // 簡單的說 如果你的模闆有list,且list不是最後一行,下面還有資料需要填充 就必須設定 forceNewRow=true 但是這個就會把所有資料放到記憶體 會很耗記憶體

總結一個工具類

public class ExcelUtil {
    /**
     * 寫出一個 excel 檔案到本地
     * <br />
     * 将類型所有加了 @ExcelProperty 注解的屬性全部寫出
     *
     * @param fileName  檔案名 不要字尾
     * @param sheetName sheet名
     * @param data      寫出的資料
     * @param clazz     要寫出資料類的Class類型對象
     * @param <T>       寫出的資料類型
     */
    public static <T> void writeExcel(String fileName, String sheetName, List<T> data, Class<T> clazz) {
        writeExcel(null, fileName, sheetName, data, clazz);
    }

    /**
     * 按照指定的屬性名進行寫出 一個 excel
     *
     * @param attrName  指定的屬性名 必須與資料類型的屬性名一緻
     * @param fileName  檔案名 不要字尾
     * @param sheetName sheet名
     * @param data      要寫出的資料
     * @param clazz     要寫出資料類的Class類型對象
     * @param <T>       要寫出的資料類型
     */
    public static <T> void writeExcel(Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
        fileName = StringUtils.isBlank(fileName) ? "學生管理系統" : fileName;
        sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;

        try(FileOutputStream fos = new FileOutputStream(fileName)) {
            write(fos,attrName,sheetName,data,clazz);
        } catch (Exception exception) {
            exception.printStackTrace();

        }

    }

    /**
     * 讀取 指定格式的 excel文檔
     *
     * @param fileName 檔案名
     * @param clazz    資料類型的class對象
     * @param <T>      資料類型
     * @return
     */
    public static <T> List<T> readExcel(String fileName, Class<T> clazz) {

        return readExcel(fileName, clazz, null);
    }

    /**
     * 取 指定格式的 excel文檔
     * 注意一旦傳入自定義監聽器,則傳回的list為空,資料需要在自定義監聽器裡面擷取
     *
     * @param fileName     檔案名
     * @param clazz        資料類型的class對象
     * @param readListener 自定義監聽器
     * @param <T>          資料類型
     * @return
     */
    public static <T> List<T> readExcel(String fileName, Class<T> clazz, ReadListener<T> readListener) {

        try(FileInputStream fis = new FileInputStream(fileName)) {
            return read(fis,clazz,readListener);
        } catch (Exception exception) {
            exception.printStackTrace();

        }
    }

    /**
     * 導出  一個 excel
     *         導出excel所有資料
     * @param response
     * @param fileName  件名 最好為英文,不要字尾名
     * @param sheetName sheet名
     * @param data      要寫出的資料
     * @param clazz     要寫出資料類的Class類型對象
     * @param <T>       要寫出的資料類型
     */
    public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) {
        export(response, null, fileName, sheetName, data, clazz);
    }

    /**
     * 按照指定的屬性名進行寫出 一個 excel
     *
     * @param response
     * @param attrName  指定的屬性名 必須與資料類型的屬性名一緻
     * @param fileName  檔案名 最好為英文,不要字尾名
     * @param sheetName sheet名
     * @param data      要寫出的資料
     * @param clazz     要寫出資料類的Class類型對象
     * @param <T>       要寫出的資料類型
     */
    public static <T> void export(HttpServletResponse response, Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {

        fileName = StringUtils.isBlank(fileName) ? "student-system-manager" : fileName;
        sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;

        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());

        try(OutputStream os = response.getOutputStream()) {
            write(os,attrName,sheetName,data,clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收一個excel檔案,并且進行解析
     *  注意一旦傳入自定義監聽器,則傳回的list為空,資料需要在自定義監聽器裡面擷取
     * @param multipartFile excel檔案
     * @param clazz 資料類型的class對象
     * @param readListener 監聽器
     * @param <T>
     * @return
     */
    public static <T> List<T> importExcel(MultipartFile multipartFile,Class<T> clazz,ReadListener<T> readListener) {

        try(InputStream inputStream = multipartFile.getInputStream()) {
            return read(inputStream,clazz,readListener);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static <T> void write(OutputStream os, Set<String> attrName, String sheetName, List<T> data, Class<T> clazz) {
        ExcelWriterBuilder write = EasyExcel.write(os, clazz);
        // 如果沒有指定要寫出那些屬性資料,則寫出全部
        if (!CollectionUtils.isEmpty(attrName)) {
            write.includeColumnFiledNames(attrName);
        }
        write.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(data);
    }

    private static <T> List<T> read(InputStream in,Class<T> clazz, ReadListener<T> readListener) {
        List<T> list = new ArrayList<>();

        Optional<ReadListener> optional = Optional.ofNullable(readListener);

        EasyExcel.read(in, clazz, optional.orElse(new AnalysisEventListener<T>() {

            @Override
            public void invoke(T data, AnalysisContext context) {
                list.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                  System.out.println("解析完成");
            }
        })).sheet().doRead();

        return list;
    }
}           

參考資料

  • https://github.com/alibaba/easyexcel/blob/master/docs/API.md 官方api
  • https://github.com/alibaba/easyexcel easyexcel github 位址
  • https://blog.csdn.net/sinat_32366329/article/details/103109058 easyexcel總結
  • https://alibaba-easyexcel.github.io/index.html 官方示例