造輪子--Excel報表工具

由於公司內部之前對於excel封裝操作並不是很方便,而且對於特殊的需求不是很容易滿足,這個月的任務是遷移部分業務小報表順便重構下,因此這裏造個輪子,便於導入和導出對應的excel報表。

代碼

https://github.com/mrdear/easy-excel

編寫原則

  1. 統一操作入口,作爲工具架包,其對外的使用策略應當保證簡單性。
  2. 鏈式操作,報表獲取數據之後,導出應當一氣呵成,也就是一個鏈式操作完成。
  3. 導入導出的可定製性,報表業務往往各種奇葩需求,因此需要暴露出鉤子定製相應邏輯。
  4. Excel Header多種策略支持,註解,Map,實體類等等。

核心類

  • EasyExcel : 入口類,所有對外的操作都是由該類發起,主要有export與read兩個操作。
  • ExcelWriter:導出類,其方法分爲非終端操作與終端操作,終端操作會輸出並關閉該流,非終端操作則可以繼續接着讀取,應對一張excel中含有多個sheet的情況。
  • ExcelReader:讀取類,與上述ExcelWriter一樣的操作。
  • ExcelField:修飾實體類註解,Excel中最麻煩的是header,因此提倡每一張報表單獨對應一個POJO類,使用註解標識相應字段。
  • ExcelWriteContext:針對導出過程中一張sheet的配置,使用Builder模式構建。
  • ExcelReadContext:針對讀取過程中一張sheet的配置,使用Builder模式構建。

Example

實體類 實體類使用註解標識字段,不使用的話則屬性名會作爲對應的columnName

public class UserWithAnnotation {

  @ExcelField(columnName = "用戶名")
  private String username;

  @ExcelField(columnName = "用戶密碼")
  private String passwd;

  @ExcelField(columnName = "登錄日期",
      writerConvert = DateToStringConvert.class,
      readerConvert = StringToDateConvert.class)
  private Date date;
}

單張表

export

@Test
 public void testSimpleWithAnnotationExport() {
   List<UserWithAnnotation> users = mockUserWithAnnotation(5);
   EasyExcel.export("/tmp/test.xlsx")
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user")
           .build())
       .write();
 }

import

@Test
 public void testRead2() {
   InputStream inputStream = SimpleExcelReaderTest.class
       .getClassLoader().getResourceAsStream("user2.xlsx");
   ExcelReader reader = EasyExcel.read(inputStream);

   List<UserWithAnnotation> result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .build());

   Assert.assertEquals(result.size(), 5);
   Assert.assertEquals(result.get(0).getPasswd(), "0b6df627-5975-417b-abc9-1f2bad5ca1e2");
   Assert.assertEquals(result.get(1).getUsername(), "張三1");

   reader.close();
 }

多張表+自定義header

sheet1最頂部有自定義的title

sheet2爲普通表格

export 由於自定義的title往往非常複雜且多變,很難做到通用,因此這裏是直接拋出一個鉤子,可以自己實現自己想要的任何操作。

@Test
 public void testCustom() {
   List<UserWithAnnotation> users = mockUserWithAnnotation(5);
   EasyExcel.export("/tmp/test.xlsx")
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user1")
           .createSheetHook((sheet, context) -> {
             Row row = sheet.createRow(0);
             sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
             Cell cell = row.createCell(0);
             cell.setCellValue("custom header");
           })
           .startRow(1)
           .build())
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user2")
           .build())
       .write();
 }

import

@Test
 public void testCustom() {
   InputStream inputStream = SimpleExcelReaderTest.class
       .getClassLoader().getResourceAsStream("user3.xlsx");
   ExcelReader reader = EasyExcel.read(inputStream);

   List<UserWithAnnotation> sheet1Result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .headerStart(1)
       .sheetIndex(0)
       .readSheetHook((sheet, context) -> {
         Row row = sheet.getRow(0);
         Assert.assertEquals(row.getCell(0).getStringCellValue(), "custom header");
       })
       .build());

   Assert.assertEquals(sheet1Result.size(), 5);
   Assert.assertEquals(sheet1Result.get(1).getUsername(), "張三1");


   List<UserWithAnnotation> sheet2Result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .sheetIndex(1)
       .build());

   Assert.assertEquals(sheet2Result.size(), 5);
   Assert.assertEquals(sheet2Result.get(1).getUsername(), "張三1");

 }

寫入HttpServletResponse

提供ResponseHelperHttpServletResponse獲取對應的輸出流,然後放入

OutputStream outputStream = ResponseHelper.wrapper(response, "order.xlsx");
EasyExcel.export(outputStream)....
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章