EasyExcel3.x文件导入SpringBoot2

news2024/9/21 10:53:18

引入依赖

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>easyexcel</artifactId>

<version>3.1.3</version>

</dependency>

简单导出

以导出用户信息为例,接下来手把手教大家如何使用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<Integer> {

@OverridepublicClass<?> supportJavaTypeKey() {

returnInteger.class;

}

@OverridepublicCellDataTypeEnumsupportExcelTypeKey() {

returnCellDataTypeEnum.STRING;

}

@OverridepublicIntegerconvertToJavaData(ReadConverterContext<?> context) {

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

}

@OverridepublicWriteCellData<?> convertToExcelData(WriteConverterContext<Integer> 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<UserDO> 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<UserDO> getUserList() throwsIOException {

ObjectMapperobjectMapper = newObjectMapper();

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

InputStreaminputStream = classPathResource.getInputStream();

returnobjectMapper.readValue(inputStream, newTypeReference<List<UserDO>>() {

});

}

}

测试接口

运行项目,通过 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<Integer> 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<OrderDO> orderList = this.getOrderList();

List<OrderBO> 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<UserDO> userList = EasyExcel.read(file.getInputStream())

.head(UserDO.class)

.sheet()

.doReadSync();

returnResponseVO.success(userList);

} catch (IOExceptione) {

returnResponseVO.error();

}

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/391561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试题,用 Java 解【数字涂色】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

微软正式推出用于 WSL 的 D3D12 GPU 视频加速

导读在允许 WSL 使用 OpenGL、OpenCL 和 Vulkan API 进行 GPU 加速之后&#xff0c;微软又正式发布了针对 Linux 的 Windows 子系统 (WSL2) 的 Direct3D 12 GPU 视频加速支持。 在允许 WSL 使用 OpenGL、OpenCL 和 Vulkan API 进行 GPU 加速之后&#xff0c;微软又正式发布了针…

【java基础】泛型的通配符(extends,super,?)

文章目录基本概念通配符extends通配符super无限定通配符?总结基本概念 如果不使用通配符&#xff0c;那么我们在使用了泛型之后就不允许类型参数发生改变了&#xff0c;但是有了通配符就可以更加灵活的控制类型参数&#xff0c;类型参数可以发生改变。下面准备了3个类用于演示…

【MAC OS 命令行】Redis的安装、启动和停止。就是如此简单

目录Mac 安装 Redis使用 Homebrew 安装 Redis总结Mac 安装 Redis 使用 Homebrew 安装 Redis 如果没有安装 Homebrew&#xff0c;先安装 Homebrew 执行命令&#xff1a; 方法一、brew 官网的安装脚本 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homeb…

浮点数的表示和运算

文章目录浮点数的表示表示溢出规格化IEEE754格式浮点数真值的计算公式范围浮点数的加减浮点数的乘除刷题小结浮点数的表示 表示 为什么会有浮点数&#xff1f;位数有限的情况下&#xff0c;既扩大了数的范围&#xff0c;也保持了数的精度。 浮点数的意思是小数点是浮动的&…

ERP(企业资源管理)概述

&#x1f31f;所属专栏&#xff1a;ERP企业资源管理&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该系列将持续更新ERP的相关学习笔记&#xff0c;欢迎和我一样的小白订阅&#xff0c;一起学习共同进步~&#x1f449;文章简介&a…

利用Python按数字顺序批量修改文件名称

为了按顺序修改文件名称&#xff0c;采用对【文件名称列表】进行排序的方法。 要对列表进行排序&#xff0c;这里使用列表的sort()方法。这个方法会在不创建新的列表的情况下直接对列表进行排序。 逆序排列&#xff1a;sort(reverseTrue) files.sort()结果还是不理想&#x…

quarkus 搭建与基础开发环境配置总结

quarkus搭建与基础开发环境配置总结 大纲 基础概念quarkus2.13.7脚手架工程配置配置maven3.8.7quarkus快速启动quarkus的三种打包方式quarkus将程序打包为二进制文件window环境下quarkus云原生二进制文件打包环境搭建使用GraalVM-java11替换本地java8运行二进制文件 基础概念…

Linux操作系统学习(线程同步)

文章目录线程同步条件变量生产者与消费者模型信号量环形队列应用生产者消费者模型线程同步 ​ 现实生活中我们经常会遇到同一个资源多个人都想使用的问题&#xff0c;例如游乐园过山车排队&#xff0c;玩完的游客还想再玩&#xff0c;最好的办法就是玩完的游客想再玩就去重新排…

分享几种WordPress怎么实现相关文章功能

一淘模板&#xff08;56admin.com&#xff09;给大家介绍一下WordPress代码实现相关文章的几种方法&#xff0c;希望对大家有所帮助&#xff01; WordPress很多插件可以实现相关文章的功能&#xff0c;插件的优点是配置简单&#xff0c;但是可能会对网站的速度造成一些小的影响…

做数据分析有前景吗?

当然有前景的。 每个行业都有发展前景&#xff0c;只是看你自身的技能情况或者关系人脉、软实力方面是否到位&#xff0c;不同的行业要求不一样。作为数据分析领域而言&#xff0c;属于IT行业&#xff0c;看的是你的专业技能&#xff1b;只要你技能过硬&#xff0c;就能在行业…

蓝桥杯 时间显示

题目 输入输出样例 示例 1 输入 46800999输出 13:00:00示例 2 输入 1618708103123输出 01:08:23评测用例规模与约定 对于所有评测用例&#xff0c;给定的时间为不超过 10^{18}1018 的正整数。 运行限制 最大运行时间&#xff1a;1s最大运行内存: 512M 基础知识 时间的转换…

Go底层原理:一起来唠唠GMP调度(一)

目录前言一、进程、线程、Goroutine1、进程与线程2、Goroutine二、Go调度器设计思想1、线程模型1.1 内核级线程模型1.2 用户级线程模型1.3 混合型线程模型2、 被废弃的 G-M 调度器2.1 了解 G-M 调度如何工作3、如今高效的 GMP 模型3.1 GMP模型调度流程3.2 GMP调度设计策略3.3 G…

【Vue3】封装数字框组件

数量选择组件-基本结构 &#xff08;1&#xff09;准备基本结构 <script lang"ts" setup name"Numbox"> // </script> <template><div class"numbox"><div class"label">数量</div><div cla…

C语言-基础了解-19-C位域

C位域 一、C位域 如果程序的结构中包含多个开关量&#xff0c;只有 TRUE/FALSE 变量&#xff0c;如下&#xff1a; struct {unsigned int widthValidated;unsigned int heightValidated; } status;这种结构需要 8 字节的内存空间&#xff0c;但在实际上&#xff0c;在每个变…

引领云数仓创新浪潮 HashData闪耀PostgreSQL中国技术大会

3月3日-3月5日&#xff0c;第12届PostgreSQL中国技术大会在杭州举行。本次大会以“突破•进化•共赢——安全可靠&#xff0c;共建与机遇”为主题&#xff0c;以线上线上结合的方式&#xff0c;邀请了PG领域众多行业大咖、学术精英及技术专家&#xff0c;共同探讨数据库未来的发…

【C/C++ 数据结构】-八大排序之 归并排序其它排序

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【C/C数据结构与算法】 分享&#xff1a;本王在此&#xff0c;狼狈为奸者&#xff0c;谋权篡位者&#xff0c;倒行逆施者&#xff0c;都得死&#xff01; ——岐王李茂贞《画江湖…

Linux 静态与动态编译、静态库与动态库

文章目录一、库的简介二、静态链接和动态链接1、静态链接2、动态链接3、GCC 下动态库与静态库三、静态库制作和使用四、动态库制作和使用总结一、库的简介 什么是库文件呢&#xff1f; 所谓库文件&#xff0c;大家可以将其等价为压缩包文件&#xff0c;该文件内部通常包含不止…

MyBatis源码分析(六)MetaObject工具类的使用与源码分析

文章目录一、MetaObject基本使用二、关键类源码分析1、MetaObject的构造方法2、PropertyTokenizer分词器3、BeanWrapper4、MetaClass5、DefaultReflectorFactory6、Reflector7、总结三、MetaObject的getValue源码分析写在后面一、MetaObject基本使用 public class User {priva…

OPT(奥普特)一键测量传感器SmartFlash高精度的四重保证

OPT&#xff08;奥普特&#xff09;一键测量传感器SmartFlash集成了机器视觉的边缘提取、自动匹配、自动对焦、自动学习及图像合成等人工智能技术&#xff0c;采用双远心光路及多角度照明系统设计&#xff0c;搭载高精度运动平台&#xff0c;并通过亚像素边缘提取算法处理图像&…