SpringBoot实现Excel导入导出
在我们平时工作中经常会遇到要操作Excel的功能,比如导出个用户信息或者订单信息的Excel报表。你肯定听说过
POI这个东西,可以实现。但是POI实现的API确实很麻烦,它需要写那种逐行解析的代码(类似Xml解析)。今天
给大家推荐一款非常好用的Excel导入导出工具EasyPoi,希望对大家有所帮助!
1、EasyPoi简介
用惯了SpringBoot的朋友估计会想到,有没有什么办法可以直接定义好需要导出的数据对象,然后添加几个注
解,直接自动实现Excel导入导出功能?
EasyPoi正是这么一款工具,如果你不太熟悉POI,想简单地实现Excel操作,用它就对了!
EasyPoi的目标不是替代POI,而是让一个不懂导入导出的人也能快速使用POI完成Excel的各种操作,而不是看很
多API才可以完成这样的工作。
项目官网:https://gitee.com/lemur/easypoi
2、集成
在SpringBoot中集成EasyPoi非常简单,只需添加easypoi-spring-boot-starter
依赖即可,真正的开箱即
用!
2.1 pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.6</version><relativePath/></parent><groupId>com.easypoi</groupId><artifactId>spring-boot-easypoi</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-easypoi</name><description>pringBoot实现Excel导入导出</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.4.0</version></dependency><!--springfox swagger官方Starter--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!--lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--SpringBoot通用依赖模块--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
2.2 结果集封装
package com.easypoi.common.api;/*** 封装API的错误码*/
public interface IErrorCode {long getCode();String getMessage();
}
package com.easypoi.common.api;/*** 枚举了一些常用API操作码*/
public enum ResultCode implements IErrorCode {SUCCESS(200, "操作成功"),FAILED(500, "操作失败"),VALIDATE_FAILED(404, "参数检验失败"),UNAUTHORIZED(401, "暂未登录或token已经过期"),FORBIDDEN(403, "没有相关权限");private long code;private String message;private ResultCode(long code, String message) {this.code = code;this.message = message;}public long getCode() {return code;}public String getMessage() {return message;}
}
package com.easypoi.common.api;/*** 通用返回对象*/
public class CommonResult<T> {private long code;private String message;private T data;protected CommonResult() {}protected CommonResult(long code, String message, T data) {this.code = code;this.message = message;this.data = data;}/*** 成功返回结果** @param data 获取的数据*/public static <T> CommonResult<T> success(T data) {return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);}/*** 成功返回结果** @param data 获取的数据* @param message 提示信息*/public static <T> CommonResult<T> success(T data, String message) {return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);}/*** 失败返回结果** @param errorCode 错误码*/public static <T> CommonResult<T> failed(IErrorCode errorCode) {return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);}/*** 失败返回结果** @param message 提示信息*/public static <T> CommonResult<T> failed(String message) {return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);}/*** 失败返回结果*/public static <T> CommonResult<T> failed() {return failed(ResultCode.FAILED);}/*** 参数验证失败返回结果*/public static <T> CommonResult<T> validateFailed() {return failed(ResultCode.VALIDATE_FAILED);}/*** 参数验证失败返回结果** @param message 提示信息*/public static <T> CommonResult<T> validateFailed(String message) {return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);}/*** 未登录返回结果*/public static <T> CommonResult<T> unauthorized(T data) {return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);}/*** 未授权返回结果*/public static <T> CommonResult<T> forbidden(T data) {return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);}public long getCode() {return code;}public void setCode(long code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}
2.3 Swagger配置
package com.easypoi.config;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** Swagger2API文档的配置*/
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()//为当前包下controller生成API文档.apis(RequestHandlerSelectors.basePackage("com.easypoi.controller"))//为有@Api注解的Controller生成API文档.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))//为有@ApiOperation注解的方法生成API文档.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("SwaggerUI演示").description("mall-tiny").contact(new Contact("macro", null, null)).version("1.0").build();}
}
2.4 启动类
package com.easypoi;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SpringBootEasypoiApplication {public static void main(String[] args) {SpringApplication.run(SpringBootEasypoiApplication.class, args);}}
2.5 配置文件
server:port: 8088springfox:documentation:enabled: true
2.6 实体类
package com.easypoi.domain;import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.Date;/*** 购物会员*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {@Excel(name = "ID", width = 10)private Long id;@Excel(name = "用户名", width = 20, needMerge = true)private String username;private String password;@Excel(name = "昵称", width = 20, needMerge = true)private String nickname;@Excel(name = "出生日期", width = 20, format = "yyyy-MM-dd")private Date birthday;@Excel(name = "手机号", width = 20, needMerge = true, desensitizationRule = "3_4")private String phone;private String icon;@Excel(name = "性别", width = 10, replace = {"男_0", "女_1"})private Integer gender;
}
package com.easypoi.domain;import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.annotation.ExcelEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.Date;
import java.util.List;/*** 订单*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Order {@Excel(name = "ID", width = 10, needMerge = true)private Long id;@Excel(name = "订单号", width = 20, needMerge = true)private String orderSn;@Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss", needMerge = true)private Date createTime;@Excel(name = "收货地址", width = 20, needMerge = true)private String receiverAddress;@ExcelEntity(name = "会员信息")private Member member;@ExcelCollection(name = "商品列表")private List<Product> productList;
}
package com.easypoi.domain;import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.math.BigDecimal;/*** 商品*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Product {@Excel(name = "ID", width = 10)private Long id;@Excel(name = "商品SN", width = 20)private String productSn;@Excel(name = "商品名称", width = 20)private String name;@Excel(name = "商品副标题", width = 30)private String subTitle;@Excel(name = "品牌名称", width = 20)private String brandName;@Excel(name = "商品价格", width = 10)private BigDecimal price;@Excel(name = "购买数量", width = 10, suffix = "件")private Integer count;
}
2.7 工具类
package com.easypoi.util;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;import java.nio.charset.Charset;
import java.util.List;/*** 从本地获取JSON数据的工具类*/
public class LocalJsonUtil {/*** 从指定路径获取JSON并转换为List** @param path json文件路径* @param elementType List元素类型*/public static <T> List<T> getListFromJson(String path, Class<T> elementType) {ClassPathResource resource = new ClassPathResource(path);String jsonStr = IoUtil.read(resource.getStream(), Charset.forName("UTF-8"));JSONArray jsonArray = new JSONArray(jsonStr);return JSONUtil.toList(jsonArray, elementType);}
}
LocalJsonUtil工具类,可以直接从resources目录下获取JSON数据并转化为对象。
2.8 字段处理器
package com.easypoi.handler;import cn.afterturn.easypoi.handler.impl.ExcelDataHandlerDefaultImpl;
import cn.hutool.core.util.StrUtil;
import com.easypoi.domain.Member;/*** 自定义字段处理*/
public class MemberExcelDataHandler extends ExcelDataHandlerDefaultImpl<Member> {@Overridepublic Object exportHandler(Member obj, String name, Object value) {if("昵称".equals(name)){String emptyValue = "暂未设置";if(value==null){return super.exportHandler(obj,name,emptyValue);}if(value instanceof String&&StrUtil.isBlank((String) value)){return super.exportHandler(obj,name,emptyValue);}}return super.exportHandler(obj, name, value);}@Overridepublic Object importHandler(Member obj, String name, Object value) {return super.importHandler(obj, name, value);}
}
2.9 处理器
package com.easypoi.controller;import cn.afterturn.easypoi.entity.vo.NormalExcelConstants;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.view.PoiBaseView;
import com.easypoi.common.api.CommonResult;
import com.easypoi.domain.Member;
import com.easypoi.domain.Order;
import com.easypoi.domain.Product;
import com.easypoi.handler.MemberExcelDataHandler;
import com.easypoi.util.LocalJsonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;/*** EasyPoi导入导出测试Controller*/
@Controller
@Api(tags = "EasyPoiController", description = "EasyPoi导入导出测试")
@RequestMapping("/easyPoi")
public class EasyPoiController {@ApiOperation(value = "导出会员列表Excel")@RequestMapping(value = "/exportMemberList", method = RequestMethod.GET)public void exportMemberList(ModelMap map,HttpServletRequest request,HttpServletResponse response) {List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);ExportParams params = new ExportParams("会员列表", "会员列表", ExcelType.XSSF);//对导出结果进行自定义处理MemberExcelDataHandler handler = new MemberExcelDataHandler();handler.setNeedHandlerFields(new String[]{"昵称"});params.setDataHandler(handler);map.put(NormalExcelConstants.DATA_LIST, memberList);map.put(NormalExcelConstants.CLASS, Member.class);map.put(NormalExcelConstants.PARAMS, params);map.put(NormalExcelConstants.FILE_NAME, "memberList");PoiBaseView.render(map, request, response, NormalExcelConstants.EASYPOI_EXCEL_VIEW);}@ApiOperation("从Excel导入会员列表")@RequestMapping(value = "/importMemberList", method = RequestMethod.POST)@ResponseBodypublic CommonResult importMemberList(@RequestPart("file") MultipartFile file) {ImportParams params = new ImportParams();params.setTitleRows(1);params.setHeadRows(1);try {List<Member> list = ExcelImportUtil.importExcel(file.getInputStream(),Member.class, params);return CommonResult.success(list);} catch (Exception e) {e.printStackTrace();return CommonResult.failed("导入失败!");}}@ApiOperation(value = "导出订单列表Excel")@RequestMapping(value = "/exportOrderList", method = RequestMethod.GET)public void exportOrderList(ModelMap map,HttpServletRequest request,HttpServletResponse response) {List<Order> orderList = getOrderList();ExportParams params = new ExportParams("订单列表", "订单列表", ExcelType.XSSF);//导出时排除一些字段params.setExclusions(new String[]{"ID", "出生日期", "性别"});map.put(NormalExcelConstants.DATA_LIST, orderList);map.put(NormalExcelConstants.CLASS, Order.class);map.put(NormalExcelConstants.PARAMS, params);map.put(NormalExcelConstants.FILE_NAME, "orderList");PoiBaseView.render(map, request, response, NormalExcelConstants.EASYPOI_EXCEL_VIEW);}private List<Order> getOrderList() {List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);for (int i = 0; i < orderList.size(); i++) {Order order = orderList.get(i);order.setMember(memberList.get(i));order.setProductList(productList);}return orderList;}
}
2.10 测试
接下来介绍下EasyPoi的使用,以会员信息和订单信息的导入导出为例,分别实现下简单的单表导出和具有关联信
息的复杂导出。
2.10.1 简单导出
我们以会员信息列表导出为例,使用EasyPoi来实现下导出功能,看看是不是够简单!
-
首先创建一个会员对象
Member
,封装会员信息; -
在此我们就可以看到EasyPoi的核心注解
@Excel
,通过在对象上添加@Excel
注解,可以将对象信息直接导出到Excel中去,下面对注解中的属性做个介绍;
-
-
name:Excel中的列名;
-
width:指定列的宽度;
-
needMerge:是否需要纵向合并单元格;
-
format:当属性为时间类型时,设置时间的导出导出格式;
-
desensitizationRule:数据脱敏处理,
3_4
表示只显示字符串的前3
位和后4
位,其他为*
号; -
replace:对属性进行替换;
-
suffix:对数据添加后缀。
-
-
接下来我们在Controller中添加一个接口,用于导出会员列表到Excel,导出的
member.json
的内容:
[{"id": 1,"username": "admin","password": null,"nickname": "Admin","birthday": "1994-12-31","phone": "18790000000","icon": null,"gender": 0},{"id": 2,"username": "macro","password": null,"nickname": "Macro","birthday": "1995-01-31","phone": "18791000000","icon": null,"gender": 0},{"id": 3,"username": "andy","password": null,"nickname": "Andy","birthday": "1995-02-28","phone": "18792000000","icon": null,"gender": 1},{"id": 4,"username": "ruby","password": null,"nickname": "Ruby","birthday": "1995-03-31","phone": "18793000000","icon": null,"gender": 1},{"id": 5,"username": "tom","password": null,"nickname": "","birthday": "1995-03-31","phone": "18793000000","icon": null,"gender": 1}
]
运行项目,直接通过Swagger访问接口,注意在Swagger中访问接口无法直接下载,需要点击返回结果中的下载按
钮才行,访问地址:http://localhost:8088/swagger-ui/
下载完成后,查看下文件,一个标准的Excel文件已经被导出了。
memberList.xlsx
文件内容:
2.10.2 简单导入
导入功能实现起来也非常简单,下面以会员信息列表的导入为例。
-
在Controller中添加会员信息导入的接口,这里需要注意的是使用
@RequestPart
注解修饰文件上传参数,否则在Swagger中就没法显示上传按钮了;
-
然后在Swagger中测试接口,选择之前导出的Excel文件即可,导入成功后会返回解析到的数据。
得到的结果:
{"code": 200,"message": "操作成功","data": [{"id": 1,"username": "admin","password": null,"nickname": "Admin","birthday": "1994-12-30T16:00:00.000+00:00","phone": "187****0000","icon": null,"gender": 0},{"id": 2,"username": "macro","password": null,"nickname": "Macro","birthday": "1995-01-30T16:00:00.000+00:00","phone": "187****0000","icon": null,"gender": 0},{"id": 3,"username": "andy","password": null,"nickname": "Andy","birthday": "1995-02-27T16:00:00.000+00:00","phone": "187****0000","icon": null,"gender": 1},{"id": 4,"username": "ruby","password": null,"nickname": "Ruby","birthday": "1995-03-30T16:00:00.000+00:00","phone": "187****0000","icon": null,"gender": 1},{"id": 5,"username": "tom","password": null,"nickname": "暂未设置","birthday": "1995-03-30T16:00:00.000+00:00","phone": "187****0000","icon": null,"gender": 1}]
}
2.10.3 复杂导出
当然EasyPoi也可以实现更加复杂的Excel操作,比如导出一个嵌套了会员信息和商品信息的订单列表,下面我们来
实现下!
-
首先添加商品对象
Product
,用于封装商品信息; -
然后添加订单对象
Order
,订单和会员是一对一关系,使用@ExcelEntity
注解表示,订单和商品是一对多关系,使用
@ExcelCollection
注解表示,Order
就是我们需要导出的嵌套订单数据; -
接下来在Controller中添加导出订单列表的接口,由于有些会员信息我们不需要导出,可以调用
ExportParams
中的setExclusions
方法排除掉; -
在Swagger中访问接口测试,导出订单列表对应Excel;
products.json
的内容:
[{"id": 1,"productSn": "7437788","name": "小米8","subTitle": "全面屏游戏智能手机 6GB+64GB 黑色 全网通4G 双卡双待","brandName": "小米","price": 2699,"count": 1},{"id": 2,"productSn": "7437789","name": "红米5A","subTitle": "全网通版 3GB+32GB 香槟金 移动联通电信4G手机 双卡双待","brandName": "小米","price": 649,"count": 1},{"id": 3,"productSn": "7437799","name": "Apple iPhone 8 Plus","subTitle": "64GB 红色特别版 移动联通电信4G手机","brandName": "苹果","price": 5499,"count": 1}
]
orders.json
的内容:
[{"id": 1,"orderSn": "201809150101000001","createTime": "2021-10-10 17:02:28","receiverAddress": "广东省深圳市"},{"id": 1,"orderSn": "201809150101000002","createTime": "2021-10-11 17:02:28","receiverAddress": "江苏省南京市"},{"id": 1,"orderSn": "201809150101000003","createTime": "2021-10-12 17:02:28","receiverAddress": "江苏省苏州市"}
]
- 下载完成后,查看下文件,EasyPoi导出复杂的Excel也是很简单的!
2.10.4 自定义处理
如果你想对导出字段进行一些自定义处理,EasyPoi也是支持的,比如在会员信息中,如果用户没有设置昵称,我
们添加下暂未设置
信息。
-
我们需要添加一个处理器继承默认的
ExcelDataHandlerDefaultImpl
类,然后在exportHandler
方法中实现自定义处理逻辑;
-
然后修改Controller中的接口,调用
MemberExcelDataHandler
处理器的setNeedHandlerFields
设置需要自定义处理的字段,并调用
ExportParams
的setDataHandler
设置自定义处理器; -
再次调用导出接口,我们可以发现昵称已经添加默认设置了。
上面的代码已经是自定义处理过的。
//对导出结果进行自定义处理
MemberExcelDataHandler handler = new MemberExcelDataHandler();
handler.setNeedHandlerFields(new String[]{"昵称"});
params.setDataHandler(handler);
3、总结
体验了一波EasyPoi,它使用注解来操作Excel的方式确实非常好用。如果你想生成更为复杂的Excel的话,可以考
虑下它的模板功能。