由於公司內部之前對於excel封裝操作並不是很方便,而且對於特殊的需求不是很容易滿足,這個月的任務是遷移部分業務小報表順便重構下,因此這裏造個輪子,便於導入和導出對應的excel報表。
代碼
https://github.com/mrdear/easy-excel
編寫原則
- 統一操作入口,作爲工具架包,其對外的使用策略應當保證簡單性。
- 鏈式操作,報表獲取數據之後,導出應當一氣呵成,也就是一個鏈式操作完成。
- 導入導出的可定製性,報表業務往往各種奇葩需求,因此需要暴露出鉤子定製相應邏輯。
- 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
提供ResponseHelper
從HttpServletResponse
獲取對應的輸出流,然後放入
OutputStream outputStream = ResponseHelper.wrapper(response, "order.xlsx"); EasyExcel.export(outputStream)....