介紹
快速開始
引入依賴
簡單導(dǎo)出
定義實體類
復(fù)雜導(dǎo)出
簡單導(dǎo)入
介紹
EasyExcel 是一個基于 Java 的、快速、簡潔、解決大文件內(nèi)存溢出的 Excel 處理工具。它能讓你在不用考慮性能、內(nèi)存的等因素的情況下,快速完成 Excel 的讀、寫等功能。
EasyExcel文檔地址:
https://easyexcel.opensource.alibaba.com/
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
快速開始
引入依賴
com.alibaba easyexcel 3.1.3
簡單導(dǎo)出
以導(dǎo)出用戶信息為例,接下來手把手教大家如何使用EasyExcel實現(xiàn)導(dǎo)出功能!
定義實體類
在EasyExcel中,以面向?qū)ο笏枷雭韺崿F(xiàn)導(dǎo)入導(dǎo)出,無論是導(dǎo)入數(shù)據(jù)還是導(dǎo)出數(shù)據(jù)都可以想象成具體某個對象的集合,所以為了實現(xiàn)導(dǎo)出用戶信息功能,首先創(chuàng)建一個用戶對象UserDO實體類,用于封裝用戶信息:
/** *用戶信息 * *@authorwilliam@StarImmortal */ @Data publicclassUserDO{ @ExcelProperty("用戶編號") @ColumnWidth(20) privateLongid; @ExcelProperty("用戶名") @ColumnWidth(20) privateStringusername; @ExcelIgnore privateStringpassword; @ExcelProperty("昵稱") @ColumnWidth(20) privateStringnickname; @ExcelProperty("生日") @ColumnWidth(20) @DateTimeFormat("yyyy-MM-dd") privateDatebirthday; @ExcelProperty("手機號") @ColumnWidth(20) privateStringphone; @ExcelProperty("身高(米)") @NumberFormat("#.##") @ColumnWidth(20) privateDoubleheight; @ExcelProperty(value="性別",converter=GenderConverter.class) @ColumnWidth(10) privateIntegergender; }
上面代碼中類屬性上使用了EasyExcel核心注解:
@ExcelProperty: 核心注解,value屬性可用來設(shè)置表頭名稱,converter屬性可以用來設(shè)置類型轉(zhuǎn)換器;
@ColumnWidth: 用于設(shè)置表格列的寬度;
@DateTimeFormat: 用于設(shè)置日期轉(zhuǎn)換格式;
@NumberFormat: 用于設(shè)置數(shù)字轉(zhuǎn)換格式。
自定義轉(zhuǎn)換器
在EasyExcel中,如果想實現(xiàn)枚舉類型到字符串類型轉(zhuǎn)換(例如gender屬性:1 -> 男,2 -> 女),需實現(xiàn)Converter接口來自定義轉(zhuǎn)換器,下面為自定義GenderConverter性別轉(zhuǎn)換器代碼實現(xiàn):
/** *Excel性別轉(zhuǎn)換器 * *@authorwilliam@StarImmortal */ publicclassGenderConverterimplementsConverter{ @Override publicClass>supportJavaTypeKey(){ returnInteger.class; } @Override publicCellDataTypeEnumsupportExcelTypeKey(){ returnCellDataTypeEnum.STRING; } @Override publicIntegerconvertToJavaData(ReadConverterContext>context){ returnGenderEnum.convert(context.getReadCellData().getStringValue()).getValue(); } @Override publicWriteCellData>convertToExcelData(WriteConverterContext context){ returnnewWriteCellData<>(GenderEnum.convert(context.getValue()).getDescription()); } } /** *性別枚舉 * *@authorwilliam@StarImmortal */ @Getter @AllArgsConstructor publicenumGenderEnum{ /** *未知 */ UNKNOWN(0,"未知"), /** *男性 */ MALE(1,"男性"), /** *女性 */ FEMALE(2,"女性"); privatefinalIntegervalue; @JsonFormat privatefinalStringdescription; publicstaticGenderEnumconvert(Integervalue){ returnStream.of(values()) .filter(bean->bean.value.equals(value)) .findAny() .orElse(UNKNOWN); } publicstaticGenderEnumconvert(Stringdescription){ returnStream.of(values()) .filter(bean->bean.description.equals(description)) .findAny() .orElse(UNKNOWN); } }
定義接口
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") publicclassExcelController{ @GetMapping("/export/user") publicvoidexportUserExcel(HttpServletResponseresponse){ try{ this.setExcelResponseProp(response,"用戶列表"); ListuserList=this.getUserList(); EasyExcel.write(response.getOutputStream()) .head(UserDO.class) .excelType(ExcelTypeEnum.XLSX) .sheet("用戶列表") .doWrite(userList); }catch(IOExceptione){ thrownewRuntimeException(e); } } /** *設(shè)置響應(yīng)結(jié)果 * *@paramresponse響應(yīng)結(jié)果對象 *@paramrawFileName文件名 *@throwsUnsupportedEncodingException不支持編碼異常 */ privatevoidsetExcelResponseProp(HttpServletResponseresponse,StringrawFileName)throwsUnsupportedEncodingException{ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); StringfileName=URLEncoder.encode(rawFileName,"UTF-8").replaceAll("+","%20"); response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx"); } /** *讀取用戶列表數(shù)據(jù) * *@return用戶列表數(shù)據(jù) *@throwsIOExceptionIO異常 */ privateList getUserList()throwsIOException{ ObjectMapperobjectMapper=newObjectMapper(); ClassPathResourceclassPathResource=newClassPathResource("mock/users.json"); InputStreaminputStream=classPathResource.getInputStream(); returnobjectMapper.readValue(inputStream,newTypeReference >(){ }); } }
測試接口
運行項目,通過 Postman 或者 Apifox 工具來進行接口測試
注意:在 Apifox 中訪問接口后無法直接下載,需要點擊返回結(jié)果中的下載圖標才行,點擊之后方可對Excel文件進行保存。
接口地址:http://localhost:8080/excel/export/user
復(fù)雜導(dǎo)出
由于 EasyPoi 支持嵌套對象導(dǎo)出,直接使用內(nèi)置 @ExcelCollection 注解即可實現(xiàn),遺憾的是 EasyExcel 不支持一對多導(dǎo)出,只能自行實現(xiàn),通過此issues了解到,項目維護者建議通過自定義合并策略方式來實現(xiàn)一對多導(dǎo)出。
解決思路:只需把訂單主鍵相同的列中需要合并的列給合并了,就可以實現(xiàn)這種一對多嵌套信息的導(dǎo)出
自定義注解
創(chuàng)建一個自定義注解,用于標記哪些屬性需要合并單元格,哪個屬性是主鍵:
/** *用于判斷是否需要合并以及合并的主鍵 * *@authorwilliam@StarImmortal */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public@interfaceExcelMerge{ /** *是否合并單元格 * *@returntrue||false */ booleanmerge()defaulttrue; /** *是否為主鍵(即該字段相同的行合并) * *@returntrue||false */ booleanisPrimaryKey()defaultfalse; }
定義實體類
在需要合并單元格的屬性上設(shè)置 @ExcelMerge 注解,二級表頭通過設(shè)置 @ExcelProperty 注解中 value 值為數(shù)組形式來實現(xiàn)該效果:
/** *@authorwilliam@StarImmortal */ @Data publicclassOrderBO{ @ExcelProperty(value="訂單主鍵") @ColumnWidth(16) @ExcelMerge(merge=true,isPrimaryKey=true) privateStringid; @ExcelProperty(value="訂單編號") @ColumnWidth(20) @ExcelMerge(merge=true) privateStringorderId; @ExcelProperty(value="收貨地址") @ExcelMerge(merge=true) @ColumnWidth(20) privateStringaddress; @ExcelProperty(value="創(chuàng)建時間") @ColumnWidth(20) @DateTimeFormat("yyyy-MM-ddHHss") @ExcelMerge(merge=true) privateDatecreateTime; @ExcelProperty(value={"商品信息","商品編號"}) @ColumnWidth(20) privateStringproductId; @ExcelProperty(value={"商品信息","商品名稱"}) @ColumnWidth(20) privateStringname; @ExcelProperty(value={"商品信息","商品標題"}) @ColumnWidth(30) privateStringsubtitle; @ExcelProperty(value={"商品信息","品牌名稱"}) @ColumnWidth(20) privateStringbrandName; @ExcelProperty(value={"商品信息","商品價格"}) @ColumnWidth(20) privateBigDecimalprice; @ExcelProperty(value={"商品信息","商品數(shù)量"}) @ColumnWidth(20) privateIntegercount; }
數(shù)據(jù)映射與平鋪
導(dǎo)出之前,需要對數(shù)據(jù)進行處理,將訂單數(shù)據(jù)進行平鋪,orderList為平鋪前格式,exportData為平鋪后格式:
自定義單元格合并策略
當 Excel 中兩列主鍵相同時,合并被標記需要合并的列:
/** *自定義單元格合并策略 * *@authorwilliam@StarImmortal */ publicclassExcelMergeStrategyimplementsRowWriteHandler{ /** *主鍵下標 */ privateIntegerprimaryKeyIndex; /** *需要合并的列的下標集合 */ privatefinalListmergeColumnIndexList=newArrayList<>(); /** *數(shù)據(jù)類型 */ privatefinalClass>elementType; publicExcelMergeStrategy(Class>elementType){ this.elementType=elementType; } @Override publicvoidafterRowDispose(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,Rowrow,IntegerrelativeRowIndex,BooleanisHead){ //判斷是否為標題 if(isHead){ return; } //獲取當前工作表 Sheetsheet=writeSheetHolder.getSheet(); //初始化主鍵下標和需要合并字段的下標 if(primaryKeyIndex==null){ this.initPrimaryIndexAndMergeIndex(writeSheetHolder); } //判斷是否需要和上一行進行合并 //不能和標題合并,只能數(shù)據(jù)行之間合并 if(row.getRowNum()<=?1)?{ ????????????return; ????????} ????????//?獲取上一行數(shù)據(jù) ????????Row?lastRow?=?sheet.getRow(row.getRowNum()?-?1); ????????//?將本行和上一行是同一類型的數(shù)據(jù)(通過主鍵字段進行判斷),則需要合并 ????????if?(lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue()))?{ ????????????for?(Integer?mergeIndex?:?mergeColumnIndexList)?{ ????????????????CellRangeAddress?cellRangeAddress?=?new?CellRangeAddress(row.getRowNum()?-?1,?row.getRowNum(),?mergeIndex,?mergeIndex); ????????????????sheet.addMergedRegionUnsafe(cellRangeAddress); ????????????} ????????} ????} ????/** ?????*?初始化主鍵下標和需要合并字段的下標 ?????* ?????*?@param?writeSheetHolder?WriteSheetHolder ?????*/ ????private?void?initPrimaryIndexAndMergeIndex(WriteSheetHolder?writeSheetHolder)?{ ????????//?獲取當前工作表 ????????Sheet?sheet?=?writeSheetHolder.getSheet(); ????????//?獲取標題行 ????????Row?titleRow?=?sheet.getRow(0); ????????//?獲取所有屬性字段 ????????Field[]?fields?=?this.elementType.getDeclaredFields(); ????????//?遍歷所有字段 ????????for?(Field?field?:?fields)?{ ????????????//?獲取@ExcelProperty注解,用于獲取該字段對應(yīng)列的下標 ????????????ExcelProperty?excelProperty?=?field.getAnnotation(ExcelProperty.class); ????????????//?判斷是否為空 ????????????if?(null?==?excelProperty)?{ ????????????????continue; ????????????} ????????????//?獲取自定義注解,用于合并單元格 ????????????ExcelMerge?excelMerge?=?field.getAnnotation(ExcelMerge.class); ????????????//?判斷是否需要合并 ????????????if?(null?==?excelMerge)?{ ????????????????continue; ????????????} ????????????for?(int?i?=?0;?i?
定義接口
將自定義合并策略 ExcelMergeStrategy 通過 registerWriteHandler 注冊上去:
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") publicclassExcelController{ @GetMapping("/export/order") publicvoidexportOrderExcel(HttpServletResponseresponse){ try{ this.setExcelResponseProp(response,"訂單列表"); ListorderList=this.getOrderList(); List exportData=this.convert(orderList); EasyExcel.write(response.getOutputStream()) .head(OrderBO.class) .registerWriteHandler(newExcelMergeStrategy(OrderBO.class)) .excelType(ExcelTypeEnum.XLSX) .sheet("訂單列表") .doWrite(exportData); }catch(IOExceptione){ thrownewRuntimeException(e); } } /** *設(shè)置響應(yīng)結(jié)果 * *@paramresponse響應(yīng)結(jié)果對象 *@paramrawFileName文件名 *@throwsUnsupportedEncodingException不支持編碼異常 */ privatevoidsetExcelResponseProp(HttpServletResponseresponse,StringrawFileName)throwsUnsupportedEncodingException{ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); StringfileName=URLEncoder.encode(rawFileName,"UTF-8").replaceAll("+","%20"); response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx"); } }
測試接口
運行項目,通過 Postman 或者 Apifox 工具來進行接口測試
注意:在 Apifox 中訪問接口后無法直接下載,需要點擊返回結(jié)果中的下載圖標才行,點擊之后方可對Excel文件進行保存。
接口地址:http://localhost:8080/excel/export/order
簡單導(dǎo)入
以導(dǎo)入用戶信息為例,接下來手把手教大家如何使用EasyExcel實現(xiàn)導(dǎo)入功能!
/** *EasyExcel導(dǎo)入導(dǎo)出 * *@authorwilliam@StarImmortal */ @RestController @RequestMapping("/excel") @Api(tags="EasyExcel") publicclassExcelController{ @PostMapping("/import/user") publicResponseVOimportUserExcel(@RequestPart(value="file")MultipartFilefile){ try{ ListuserList=EasyExcel.read(file.getInputStream()) .head(UserDO.class) .sheet() .doReadSync(); returnResponseVO.success(userList); }catch(IOExceptione){ returnResponseVO.error(); } } } 責任編輯:彭菁
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
2998瀏覽量
73881 -
文件
+關(guān)注
關(guān)注
1文章
561瀏覽量
24695 -
Excel
+關(guān)注
關(guān)注
4文章
218瀏覽量
55448
原文標題:SpringBoot 集成 EasyExcel 3.x 優(yōu)雅實現(xiàn) Excel 導(dǎo)入導(dǎo)出
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論