1 根據實體類生成對應的模板
/** * * excel 模板demo 還可以加一些屬性的格式化注解 比如時間 * @author: kuangql@fadada.com * @date: 2020/11/25 15:16 * @description: TODO */ @Data public class DemoExcelEntity { public static final String bigTitle= "填寫須知: \n" + "1.第1、2行為固定結構,不可更改;以下示例行,導入前請先刪除\n" + "2.請嚴格按照填寫規則輸入數據,不合規的數據無法成功導入"; @ExcelProperty(value = {bigTitle,"姓名(必填)"}, index = 0) private String userName; @ExcelProperty(value = {bigTitle,"性別(必填)"}, index = 1) private String userSexName; @ExcelProperty(value = {bigTitle,"手機號碼(必填)"}, index = 2) private String userMobile; @ExcelProperty(value = {bigTitle,"出生年月(必填)"}, index = 3) private String userBirthday; @ExcelProperty(value = {bigTitle,"工作單位(必填)"}, index = 4) private String deptName; @ExcelProperty(value = {bigTitle,"職務(必填)"}, index = 5) private String unitPosition; @ExcelProperty(value = {bigTitle,"干部類別(必填)"}, index = 6) private String leaderTypeName; @ExcelProperty(value = {bigTitle,"用戶狀態(必填)"}, index = 7) private String userStatusName; /** * 每個模板的首行高度, 換行數目+2 乘以15 */ public static int getHeadHeight(){ return (StringUtils.getCharCounts(bigTitle,"\n")+2)*15; } }
2 根據該實體類生成的excel模板樣式如下:
3 解釋下@ExcelProperty這個注解
3.1 在實體屬性上增加這個注解。表示這個屬性是會輸出到excel單元格里,怎么輸出呢,就是由 value 和index 來控制。
3.2 value 存放的值是一個數組。數組元素幾個,表示占用幾行作為表頭。index表示該實體類的屬性在單元格的那一列。
3.3 結合我的demo看,我的value 存放的元素大小為2,所以我的表頭為2行。如果需要出現占據多行的樣式,就在對應的元素位置放相同的內容,就會形成合並單元格。
4
excelUtil 工具類:
/** * 生成excel模板 * * @param response * @param fileName 下載的文件名, * @param sheetName sheet名 * @param data 導出的數據 * @param model 導出的頭 * @param heardHeight 頭行高 */ public static void createTemplate(HttpServletResponse response, String fileName, String sheetName, List<? extends Object> data, Class<?> model, int heardHeight) { HorizontalCellStyleStrategy horizontalCellStyleStrategy = setMyCellStyle(); EasyExcel.write(getOutputStream(fileName, response, ExcelTypeEnum.XLSX), model). excelType(ExcelTypeEnum.XLSX).sheet(sheetName) .registerWriteHandler(new TemplateCellWriteHandler(heardHeight)) .registerWriteHandler(horizontalCellStyleStrategy) .doWrite(data); } /** * 創建我的cell 策略 * * @return */ public static HorizontalCellStyleStrategy setMyCellStyle() { // 頭的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 設置表頭居中對齊 headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 顏色 headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 10); // 字體 headWriteCellStyle.setWriteFont(headWriteFont); headWriteCellStyle.setWrapped(true); // 內容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 設置內容靠中對齊 contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); // 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉 return horizontalCellStyleStrategy; }
4.1 controller相關代碼:
@ApiOperation(value = "下載模板文件測試-----該方法是測試使用") @GetMapping("/template2/{type}") public void template2(@PathVariable("type") String type, HttpServletResponse response) { String fileName = "導入模板"; String sheetName = "模板"; try { // List<DemoExcelEntity> sysUserImportExcelList = getSysUserImportExcel(); //輸出文件流 //后期根據傳參處理獲取對應的模板實體類 如果需要導出數據,將nul改為對應List<DemoExcelEntity> 傳進去
ExcelUtils.createTemplate(response, fileName, sheetName, null, DemoExcelEntity.class, DemoExcelEntity.getHeadHeight()); } catch (Exception e) { e.printStackTrace(); } }
4.3 讀取文件數據:
1 我的文件數據截圖:
2 表頭占據了2行,所以讀取數據的時候需要跳過2行表頭。 2行表頭等於@ExcelProperty里的value 元素個數
代碼如下:
/** * 讀取 Excel(第一個 sheet) 指定行開始讀取 * @param excel 文件 * @param rowType 模板實體類 * @param header 指定不讀取的表頭行數, * @param <T> * @return 集合數據 * @throws ExcelException */ public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType,int header) throws ExcelException { ExcelReader reader = getReader(excel, header); if (reader == null) { return new ArrayList<>(); } return readExcel(reader, rowType, 0); }
3 如果在導入數據的時候,需要將導入的數據的錯誤信息追加到后一列,可以新建一個實體類
/** * 用於輸出 導入的時候,錯誤信息輸出 * @author: kuangql@fadada.com * @date: 2020/11/27 13:37 * @description: TODO */ @Data public class DemoExcelEntityError extends DemoExcelEntity { /** * 說明: 該對象只針對導入的時候,在用戶上傳的文件后面追加一列錯誤信息描述 */ @ExcelProperty(value = {bigTitle,"錯誤描述"}, index = 8) private String errorMessage; }
// 該方法是測試使用 @ApiOperation(value = "上傳文件數據------測試使用 返回錯誤信息 ") @PostMapping("/upload") public void template2(MultipartFile multipartFile, HttpServletResponse response) { List<DemoExcelEntity> demoExcelEntities = ExcelUtils.readFirstSheetExcel(multipartFile, DemoExcelEntity.class, 2); List<DemoExcelEntityError> demoExcelEntityErrorList = new ArrayList<>(); demoExcelEntities.forEach( demoExcelEntity -> { System.out.println(demoExcelEntity.toString()); DemoExcelEntityError demoExcelEntityError = new DemoExcelEntityError(); BeanUtils.copyProperties(demoExcelEntity, demoExcelEntityError); demoExcelEntityError.setErrorMessage("我的錯誤信息描述"); demoExcelEntityErrorList.add(demoExcelEntityError); } ); ExcelUtils.writeExcel(response, demoExcelEntityErrorList, "demo", "1", ExcelTypeEnum.XLSX); }
效果如下:
好了,基本描述完畢。如下是所有相關的代碼:
/** * excel導入導出工具類 * BeanCopy ExcelException 屬於自定義數據,屬於可自定義依賴 * * @version 2.0.0 * @author: * @date: 2020/10/27 */ @NoArgsConstructor public class ExcelUtils { /** * 讀取 Excel(多個 sheet) * @param reader 讀取的excel * @param rowModel excel模板實體類 * @param sheetCount sheet * @param <T> * @return */ public static <T> List<T> readExcel(ExcelReader reader, Class<T> rowModel, int sheetCount) { if (reader == null) { return new ArrayList<>(); } List<ReadSheet> readSheetList = new ArrayList<>(); ExcelListener<T> excelListener = new ExcelListener<>(); ReadSheet readSheet = EasyExcel.readSheet(sheetCount) .head(rowModel) .registerReadListener(excelListener) .build(); readSheetList.add(readSheet); reader.read(readSheetList); return getExtendsBeanList(excelListener.getDataList(), rowModel); } /** * 讀取 Excel(多個 sheet) * 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener * 重寫invoke doAfterAllAnalysed方法 * getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取 * * @param excel 文件 * @param header 不讀取表頭數目(表頭2行,讀取數據時候從第三行開始,讀取幾行看對應的模板實體類) * @param sheetNo * @param rowModel excel模板實體類 * @return * @throws ExcelException */ private static List[] readExcel(MultipartFile excel,int header, Integer sheetNo, Class<?>[] rowModel) throws ExcelException { ExcelReader reader = getReader(excel,header); if (reader == null) { return new ArrayList[rowModel.length]; } List[] result = new ArrayList[rowModel.length]; for (int sheetCount = 0; sheetCount < rowModel.length; sheetCount++) { if (sheetNo != null && sheetNo != sheetCount) { continue; } result[sheetCount].addAll(readExcel(reader, rowModel[sheetCount], sheetCount)); } return result; } /** * 讀取 Excel(多個 sheet) * 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener * 重寫invoke doAfterAllAnalysed方法 * getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取 * @param excel * @param header 不讀取表頭數目(表頭2行,讀取數據時候從第三行開始,讀取幾行看對應的模板實體類) * @param rowModel * @return * @throws ExcelException */ public static List[] readExcel(MultipartFile excel, int header ,Class<?>... rowModel) throws ExcelException { ExcelReader reader = getReader(excel,header); if (reader == null) { return new ArrayList[rowModel.length]; } List[] result = new ArrayList[rowModel.length]; for (int sheetCount = 0; sheetCount < rowModel.length; sheetCount++) { result[sheetCount] = new ArrayList<>(readExcel(reader, rowModel[sheetCount], sheetCount)); } return result; } /** * 讀取 Excel(單個 sheet) * 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener * 重寫invoke doAfterAllAnalysed方法 * getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取 */ /* public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType) throws ExcelException { ExcelReader reader = getReader(excel); if (reader == null) { return new ArrayList<>(); } return readExcel(reader, rowType, 0); } */ /** * 讀取某個 sheet 的 Excel * * @param excel 文件 * @param rowModel 實體類映射 * @param sheetNo sheet 的序號 從1開始 * @return Excel 數據 list */ public static <T> List readExcel(MultipartFile excel, Class<T> rowModel, int sheetNo) throws ExcelException { Class[] classes = {rowModel}; return ExcelUtils.readExcel(excel, sheetNo, classes)[0]; } /** * 導出 Excel :一個 sheet,帶表頭 * 自定義WriterHandler 可以定制行列數據進行靈活化操作 * * @param response HttpServletResponse * @param list 數據 list * @param fileName 導出的文件名 * @param sheetName 導入文件的 sheet 名 */ public static <T> void writeExcel(HttpServletResponse response, List<T> list, String fileName, String sheetName, ExcelTypeEnum excelTypeEnum) throws ExcelException { if (sheetName == null || "".equals(sheetName)) { sheetName = "sheet1"; } if (CollectionUtils.isEmpty(list)) { return; } EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum), list.get(0).getClass()).sheet(sheetName).doWrite(list); } /** * 導出 Excel :一個 sheet,帶表頭 * 自定義WriterHandler 可以定制行列數據進行靈活化操作 * * @param response HttpServletResponse * @param list 數據 list * @param fileName 導出的文件名 */ public static <T> void writeExcel(HttpServletResponse response, List<T> list, String fileName, ExcelTypeEnum excelTypeEnum) throws ExcelException { if (CollectionUtils.isEmpty(list)) { return; } String sheetName = list.get(0).getClass().getAnnotation(SheetName.class).value(); sheetName = StrUtil.isNotBlank(sheetName) ? sheetName : "sheet1"; EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum), list.get(0).getClass()).sheet(sheetName).doWrite(list); } /** * 導出 Excel :一個 sheet,帶表頭 * 自定義WriterHandler 可以定制行列數據進行靈活化操作 * * @param response HttpServletResponse * @param fileName 導出的文件名 */ public static void writeExcel(HttpServletResponse response, String fileName, ExcelTypeEnum excelTypeEnum, List... lists) throws ExcelException { ExcelWriter excelWriter = null; try { excelWriter = EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum)).build(); for (int count = 0; count < lists.length; count++) { if (CollectionUtils.isEmpty(lists[count])) { continue; } String sheetName = lists[count].get(0).getClass().getAnnotation(SheetName.class).value(); sheetName = StrUtil.isNotBlank(sheetName) ? sheetName : "sheet" + (count + 1); WriteSheet writeSheet = EasyExcel.writerSheet(count, sheetName) .head(lists[count].get(0).getClass()) .build(); excelWriter.write(lists[count], writeSheet); } } finally { if (excelWriter != null) { excelWriter.finish(); } } } /** * 導出文件時為Writer生成OutputStream */ private static OutputStream getOutputStream(String fileName, HttpServletResponse response, ExcelTypeEnum excelTypeEnum) throws ExcelException { //創建本地文件 String filePath = fileName + excelTypeEnum.getValue(); try { fileName = new String(filePath.getBytes(), StandardCharsets.ISO_8859_1); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType("application/vnd.ms-excel"); response.addHeader("Content-Disposition", "filename=" + fileName); return response.getOutputStream(); } catch (IOException e) { throw new ExcelException(ResultCode.File.CREATE_FILE_FAILED); } } /** * 返回 ExcelReader * * @param excel 需要解析的 Excel 文件 */ /* public static ExcelReader getReader(MultipartFile excel) throws ExcelException { String fileName = excel.getOriginalFilename(); if (fileName == null) { throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR); } if (!fileName.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) && !fileName.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())) { throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR); } InputStream inputStream; try { inputStream = excel.getInputStream(); return EasyExcel.read(inputStream).build(); } catch (IOException e) { //do something } return null; } */ /** * 利用BeanCopy轉換list */ public static <T> List<T> getExtendsBeanList(List<?> list, Class<T> typeClazz) { return BeanConvertUtils.copyList(list, typeClazz); } /** * 生成excel模板 * * @param response * @param fileName 下載的文件名, * @param sheetName sheet名 * @param data 導出的數據 * @param model 導出的頭 * @param heardHeight 頭行高 */ public static void createTemplate(HttpServletResponse response, String fileName, String sheetName, List<? extends Object> data, Class<?> model, int heardHeight) { HorizontalCellStyleStrategy horizontalCellStyleStrategy = setMyCellStyle(); EasyExcel.write(getOutputStream(fileName, response, ExcelTypeEnum.XLSX), model). excelType(ExcelTypeEnum.XLSX).sheet(sheetName) .registerWriteHandler(new TemplateCellWriteHandler(heardHeight)) .registerWriteHandler(horizontalCellStyleStrategy) .doWrite(data); } /** * 創建我的cell 策略 * * @return */ public static HorizontalCellStyleStrategy setMyCellStyle() { // 頭的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 設置表頭居中對齊 headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 顏色 headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 10); // 字體 headWriteCellStyle.setWriteFont(headWriteFont); headWriteCellStyle.setWrapped(true); // 內容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 設置內容靠中對齊 contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); // 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉 return horizontalCellStyleStrategy; } /** * 讀取 Excel(第一個 sheet) 指定行開始讀取 * @param excel 文件 * @param rowType 模板實體類 * @param header 指定不讀取的表頭行數, * @param <T> * @return 集合數據 * @throws ExcelException */ public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType,int header) throws ExcelException { ExcelReader reader = getReader(excel, header); if (reader == null) { return new ArrayList<>(); } return readExcel(reader, rowType, 0); } /** * * @param excel 需要解析的 Excel 文件 * @param header 指定不讀取表頭行數, * @return * @throws ExcelException */ public static ExcelReader getReader(MultipartFile excel,int header) throws ExcelException { String fileName = excel.getOriginalFilename(); if (fileName == null) { throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR); } if (!fileName.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) && !fileName.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())) { throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR); } InputStream inputStream; try { inputStream = excel.getInputStream(); return EasyExcel.read(inputStream). headRowNumber(header). build(); } catch (IOException e) { //do something } return null; } }
2 實體類:
/** * * excel 模板demo 還可以加一些屬性的格式化注解 比如時間 * @author: kuangql@fadada.com * @date: 2020/11/25 15:16 * @description: TODO */ @Data public class DemoExcelEntity { public static final String bigTitle= "填寫須知: \n" + "1.第1、2行為固定結構,不可更改;以下示例行,導入前請先刪除\n" + "2.請嚴格按照填寫規則輸入數據,不合規的數據無法成功導入"; @ExcelProperty(value = {bigTitle,"姓名(必填)"}, index = 0) private String userName; @ExcelProperty(value = {bigTitle,"性別(必填)"}, index = 1) private String userSexName; @ExcelProperty(value = {bigTitle,"手機號碼(必填)"}, index = 2) private String userMobile; @ExcelProperty(value = {bigTitle,"出生年月(必填)"}, index = 3) private String userBirthday; @ExcelProperty(value = {bigTitle,"工作單位(必填)"}, index = 4) private String deptName; @ExcelProperty(value = {bigTitle,"職務(必填)"}, index = 5) private String unitPosition; @ExcelProperty(value = {bigTitle,"干部類別(必填)"}, index = 6) private String leaderTypeName; @ExcelProperty(value = {bigTitle,"用戶狀態(必填)"}, index = 7) private String userStatusName; /** * 每個模板的首行高度, 換行數目+2 乘以15 */ public static int getHeadHeight(){ return (StringUtils.getCharCounts(bigTitle,"\n")+2)*15; } }
/** * 用於輸出 導入的時候,錯誤信息輸出 * @author: kuangql@fadada.com * @date: 2020/11/27 13:37 * @description: TODO */ @Data public class DemoExcelEntityError extends DemoExcelEntity { /** * 說明: 該對象只針對導入的時候,在用戶上傳的文件后面追加一列錯誤信息描述 */ @ExcelProperty(value = {bigTitle,"錯誤描述"}, index = 8) private String errorMessage; }
/** * excel通用單元格格式類 * @description: * @author: * @time: 2020/9/20 9:54 */ public class TemplateCellWriteHandler implements CellWriteHandler { /** * 模板的首行行高 ,通過構造器注入 */ private int height; public TemplateCellWriteHandler(int height) { this.height = height; } @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { Workbook workbook = writeSheetHolder.getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); Font font = workbook.createFont(); if (cell.getRowIndex() == 0) { font.setFontHeightInPoints((short) 10); font.setFontName("宋體"); //加粗 font.setBold(true); cellStyle.setFont(font); cellStyle.setAlignment(HorizontalAlignment.CENTER); cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //設置 自動換行 cellStyle.setWrapText(true); Row row = cell.getRow(); row.setHeightInPoints(height); } if (cell.getRowIndex() == 1) { font.setFontHeightInPoints((short) 11); font.setFontName("宋體"); font.setBold(true); cellStyle.setFont(font); cellStyle.setAlignment(HorizontalAlignment.LEFT); cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //設置 自動換行 cellStyle.setWrapText(true); Row row = cell.getRow(); row.setHeightInPoints(26); } cell.setCellStyle(cellStyle); } }