EasyExcel3.x文件导入SpringBoot2
创始人
2024-05-29 06:48:16
0

引入依赖

com.alibaba

easyexcel

3.1.3

简单导出

以导出用户信息为例,接下来手把手教大家如何使用EasyExcel实现导出功能!

定义实体类

在EasyExcel中,以面向对象思想来实现导入导出,无论是导入数据还是导出数据都可以想象成具体某个对象的集合,所以为了实现导出用户信息功能,首先创建一个用户对象UserDO实体类,用于封装用户信息:

/** * 用户信息 * * @author william@StarImmortal */@DatapublicclassUserDO {

@ExcelProperty("用户编号")

@ColumnWidth(20)

privateLongid;

@ExcelProperty("用户名")

@ColumnWidth(20)

privateStringusername;

@ExcelIgnoreprivateStringpassword;

@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属性可用来设置表头名称,converter属性可以用来设置类型转换器;

  • @ColumnWidth:用于设置表格列的宽度;

  • @DateTimeFormat:用于设置日期转换格式;

  • @NumberFormat:用于设置数字转换格式。

自定义转换器

在EasyExcel中,如果想实现枚举类型到字符串类型转换(例如gender属性:1 -> 男,2 -> 女),需实现Converter接口来自定义转换器,下面为自定义GenderConverter性别转换器代码实现:

/** * Excel 性别转换器 * * @author william@StarImmortal */publicclassGenderConverterimplementsConverter {

@OverridepublicClass supportJavaTypeKey() {

returnInteger.class;

}

@OverridepublicCellDataTypeEnumsupportExcelTypeKey() {

returnCellDataTypeEnum.STRING;

}

@OverridepublicIntegerconvertToJavaData(ReadConverterContext context) {

returnGenderEnum.convert(context.getReadCellData().getStringValue()).getValue();

}

@OverridepublicWriteCellData convertToExcelData(WriteConverterContext context) {

returnnewWriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());

}

}

/** * 性别枚举 * * @author william@StarImmortal */@Getter@AllArgsConstructorpublicenumGenderEnum {

/** * 未知 */UNKNOWN(0, "未知"),

/** * 男性 */MALE(1, "男性"),

/** * 女性 */FEMALE(2, "女性");

privatefinalIntegervalue;

@JsonFormatprivatefinalStringdescription;

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导入导出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")

publicclassExcelController {

@GetMapping("/export/user")

publicvoidexportUserExcel(HttpServletResponseresponse) {

try {

this.setExcelResponseProp(response, "用户列表");

List userList = this.getUserList();

EasyExcel.write(response.getOutputStream())

.head(UserDO.class)

.excelType(ExcelTypeEnum.XLSX)

.sheet("用户列表")

.doWrite(userList);

} catch (IOExceptione) {

thrownewRuntimeException(e);

}

}

/** * 设置响应结果 * * @param response 响应结果对象 * @param rawFileName 文件名 * @throws UnsupportedEncodingException 不支持编码异常 */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");

}

/** * 读取用户列表数据 * * @return 用户列表数据 * @throws IOException IO异常 */privateList getUserList() throwsIOException {

ObjectMapperobjectMapper = newObjectMapper();

ClassPathResourceclassPathResource = newClassPathResource("mock/users.json");

InputStreaminputStream = classPathResource.getInputStream();

returnobjectMapper.readValue(inputStream, newTypeReference>() {

});

}

}

测试接口

运行项目,通过 Postman 或者 Apifox 工具来进行接口测试

注意:在 Apifox 中访问接口后无法直接下载,需要点击返回结果中的下载图标才行,点击之后方可对Excel文件进行保存。

接口地址:http://localhost:8080/excel/export/user

复杂导出

由于 EasyPoi 支持嵌套对象导出,直接使用内置 @ExcelCollection 注解即可实现,遗憾的是 EasyExcel 不支持一对多导出,只能自行实现,通过此issues了解到,项目维护者建议通过自定义合并策略方式来实现一对多导出。

解决思路:只需把订单主键相同的列中需要合并的列给合并了,就可以实现这种一对多嵌套信息的导出

自定义注解

创建一个自定义注解,用于标记哪些属性需要合并单元格,哪个属性是主键:

/** * 用于判断是否需要合并以及合并的主键 * * @author william@StarImmortal */@Target({ElementType.FIELD})

@Retention(RetentionPolicy.RUNTIME)

@Documentedpublic @interface ExcelMerge {

/** * 是否合并单元格 * * @return true || false */booleanmerge() defaulttrue;

/** * 是否为主键(即该字段相同的行合并) * * @return true || false */booleanisPrimaryKey() defaultfalse;

}

定义实体类

在需要合并单元格的属性上设置 @ExcelMerge 注解,二级表头通过设置 @ExcelProperty 注解中 value 值为数组形式来实现该效果:

/** * @author william@StarImmortal */@DatapublicclassOrderBO {

@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 = "创建时间")

@ColumnWidth(20)

@DateTimeFormat("yyyy-MM-dd HH:mm:ss")

@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 = {"商品信息", "商品数量"})

@ColumnWidth(20)

privateIntegercount;

}

数据映射与平铺

导出之前,需要对数据进行处理,将订单数据进行平铺,orderList为平铺前格式,exportData为平铺后格式:

自定义单元格合并策略

当 Excel 中两列主键相同时,合并被标记需要合并的列:

/** * 自定义单元格合并策略 * * @author william@StarImmortal */publicclassExcelMergeStrategyimplementsRowWriteHandler {

/** * 主键下标 */privateIntegerprimaryKeyIndex;

/** * 需要合并的列的下标集合 */privatefinalList mergeColumnIndexList = newArrayList<>();

/** * 数据类型 */privatefinalClass elementType;

publicExcelMergeStrategy(Class elementType) {

this.elementType = elementType;

}

@OverridepublicvoidafterRowDispose(WriteSheetHolderwriteSheetHolder, WriteTableHolderwriteTableHolder, Rowrow, IntegerrelativeRowIndex, BooleanisHead) {

// 判断是否为标题if (isHead) {

return;

}

// 获取当前工作表Sheetsheet = writeSheetHolder.getSheet();

// 初始化主键下标和需要合并字段的下标if (primaryKeyIndex == null) {

this.initPrimaryIndexAndMergeIndex(writeSheetHolder);

}

// 判断是否需要和上一行进行合并// 不能和标题合并,只能数据行之间合并if (row.getRowNum() <= 1) {

return;

}

// 获取上一行数据RowlastRow = sheet.getRow(row.getRowNum() - 1);

// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并if (lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue())) {

for (IntegermergeIndex : mergeColumnIndexList) {

CellRangeAddresscellRangeAddress = newCellRangeAddress(row.getRowNum() - 1, row.getRowNum(), mergeIndex, mergeIndex);

sheet.addMergedRegionUnsafe(cellRangeAddress);

}

}

}

/** * 初始化主键下标和需要合并字段的下标 * * @param writeSheetHolder WriteSheetHolder */privatevoidinitPrimaryIndexAndMergeIndex(WriteSheetHolderwriteSheetHolder) {

// 获取当前工作表Sheetsheet = writeSheetHolder.getSheet();

// 获取标题行RowtitleRow = sheet.getRow(0);

// 获取所有属性字段Field[] fields = this.elementType.getDeclaredFields();

// 遍历所有字段for (Fieldfield : fields) {

// 获取@ExcelProperty注解,用于获取该字段对应列的下标ExcelPropertyexcelProperty = field.getAnnotation(ExcelProperty.class);

// 判断是否为空if (null == excelProperty) {

continue;

}

// 获取自定义注解,用于合并单元格ExcelMergeexcelMerge = field.getAnnotation(ExcelMerge.class);

// 判断是否需要合并if (null == excelMerge) {

continue;

}

for (inti = 0; i < fields.length; i++) {

Cellcell = titleRow.getCell(i);

if (null == cell) {

continue;

}

// 将字段和表头匹配上if (excelProperty.value()[0].equalsIgnoreCase(cell.getStringCellValue())) {

if (excelMerge.isPrimaryKey()) {

primaryKeyIndex = i;

}

if (excelMerge.merge()) {

mergeColumnIndexList.add(i);

}

}

}

}

// 没有指定主键,则异常if (null == this.primaryKeyIndex) {

thrownewIllegalStateException("使用@ExcelMerge注解必须指定主键");

}

}

}

定义接口

将自定义合并策略 ExcelMergeStrategy 通过 registerWriteHandler 注册上去:

/** * EasyExcel导入导出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")

publicclassExcelController {

@GetMapping("/export/order")

publicvoidexportOrderExcel(HttpServletResponseresponse) {

try {

this.setExcelResponseProp(response, "订单列表");

List orderList = 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);

}

}

/** * 设置响应结果 * * @param response 响应结果对象 * @param rawFileName 文件名 * @throws UnsupportedEncodingException 不支持编码异常 */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 中访问接口后无法直接下载,需要点击返回结果中的下载图标才行,点击之后方可对Excel文件进行保存。

接口地址:http://localhost:8080/excel/export/order

简单导入

以导入用户信息为例,接下来手把手教大家如何使用EasyExcel实现导入功能!

/** * EasyExcel导入导出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")

@Api(tags = "EasyExcel")

publicclassExcelController {

@PostMapping("/import/user")

publicResponseVOimportUserExcel(@RequestPart(value = "file") MultipartFilefile) {

try {

List userList = EasyExcel.read(file.getInputStream())

.head(UserDO.class)

.sheet()

.doReadSync();

returnResponseVO.success(userList);

} catch (IOExceptione) {

returnResponseVO.error();

}

}

}

相关内容

热门资讯

关于一个童话 关于一个童话这个童话叫"田螺姑娘"那个公主?她不是公主~是个田螺精~但是后来他们一样过得很幸福田螺姑...
办理遗产买卖房产时产生欠条有法... 办理遗产买卖房产时产生欠条有法律效应吗?请详细描述,我们好准确回答.楼主所问的问题太简单,建议具体叙...
昆明好耍的地方 昆明好耍的地方1、大渔公园大渔公园位于昆明市呈贡县渔浦路怡和小区北,是集娱乐休闲、旅游度假为一体的好...
宋末词四大家 宋末词四大家只说名字周邦彦辛弃疾王沂孙吴文英张炎、王沂孙、蒋捷、周密宋词四大家是周邦彦、辛弃疾、王沂...
求:电影《布达佩斯之恋》里面钢... 求:电影《布达佩斯之恋》里面钢琴家安德拉许去餐馆应聘时弹的钢琴曲叫什么?不是那个黑色星期天!真是太好...
与张柏芝频传恋情,吴建豪和张柏... 与张柏芝频传恋情,吴建豪和张柏芝是否真的恋爱了?他们是真的没有恋爱,因为张柏芝还有谢霆锋的几个孩子,...
极品都市小说求推荐 百万字以上... 极品都市小说求推荐 百万字以上最好最强弃少,这个比较好看
什么是“状元、榜眼、探花”是何... 什么是“状元、榜眼、探花”是何典故?人文常识:古代科举考试中的第三名怎么称呼?状元、榜眼还是探花状元...
网上不是说冥王星在2010年被... 网上不是说冥王星在2010年被重新归为九大行星之列了吗?太阳系八大行星,不包括冥王星,如果冥王星算进...
韩版恶作剧之吻其他演员 韩版恶作剧之吻其他演员貌似确定了,是金贤重朴宝英男主是金贤重 女主的话 朴宝英可能性更大一些...
约会大作战第一季,第二季分别有... 约会大作战第一季,第二季分别有多少集第一季12急 两集ova 一集剧场版 第二季10集第一集十二集,...
关于母子间误会的故事 关于母子间误会的故事急求一个故事,关于母亲为孩子无悔付出,却在不经意间伤害了孩子,导致孩子误会了母亲...
一部电视剧是关于选美的几个女孩... 一部电视剧是关于选美的几个女孩子的,香港的,片子很老了,忘记叫什么名字了,诸位可否帮帮忙啊?香港小姐...
你听过最好的情话有哪些? 你听过最好的情话有哪些?鲜花给你,戒指给你,茱萸给你,柔情似水给你,热情如火给你,夏天的西瓜给你,秋...
倒影什么? 倒影什么?倒影是光照射在平静的水面上所成的等大虚像。成像原理遵循平面镜成像的原理。
益安宁丸的功效有什么? 益安宁丸的功效有什么?养心安神,健脾益肝,补血活血,还可以治疗肝肾不足,气血虚弱,所引起的腰漆酸软,...
求《声声慢》小石头和孩子们 求《声声慢》小石头和孩子们求《声声慢》小石头和孩子们声声慢,寻寻觅觅冷冷清清凄凄啴啴七七嗯这一个铲子...
我的同桌 不要抄袭。急。 我的同桌 不要抄袭。急。什么哦?作文还是?怎一个“慢”字了得  我有一个同桌,她是个文文静静的女孩子...
文风类似疯丢子的作者有哪些? 文风类似疯丢子的作者有哪些?文风类似疯丢子的作者有哪些?最好推荐他们的小说。《银河第一纪元》迷路的龙
紫薇适合在家里养吗 紫薇适合在家里养吗紫薇属于乔木,树形太大,养在院子里应该可以的,如果养在室内可能不适宜,很难养好。