一、编写EsayExcel上传Excel文件接口
EasyExcel官方文档例子比较详细有需要的可以点击查看官方文档
前提需要导入EasyExcel的jar包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.0.2</version>
</dependency>
Swagger注解jar包(可以不要):
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
1.Controller编写
package com.my.test.member.biz.controller;
import com.alibaba.excel.EasyExcel;
import com.my.test.member.api.R;
import com.my.test.member.api.dto.MemberDTO;
import com.my.test.member.biz.listerer.DataListener;
import io.swagger.annotations.Api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* excel管理
*/
@RestController
@RequestMapping("/test")
@Api(value = "/excel", description = "excel管理", tags = {"excel管理"})
public class ExcelUploadController {
private static final Logger logger = LoggerFactory.getLogger(ExcelUploadController.class);
@Autowired
private IMemberService memberService;
/**
* 上传接口
* @param file
* @return
*/
@PostMapping("/upload")
public R upload(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(), MemberDTO.class, new DataListener(memberService)).sheet().doRead();
}catch (Exception e){
e.printStackTrace();
logger.error("上传excel失败,失败信息:"+e.getMessage());
return R.fail("系统繁忙,请稍后再试!");
}
return R.ok();
}
}
2.Listener编写
Listener是处理数据的关键类
package com.my.test.member.biz.listerer;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.my.test.member.api.dto.MemberDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class DataListener extends AnalysisEventListener<MemberDTO> {
private static final Logger LOGGER = LoggerFactory.getLogger(DataListener.class);
/**
* 因为DataListener 不能被spring管理,所以每次读取excel都要new DataListener,如果DataListener里面用到spring管理的对象,比如操作需要数据库的话Service和Dao可以通过构造方法传进来
*/
private IMemberService memberService;
/**
* 每读取1000条数据进行校验身份证,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 1000;
List<MemberDTO> list = new ArrayList<>();
public DataListener(IMemberService memberService) {
this.memberService = memberService;
}
/**
* 这个方法每一条数据解析都会来调用-easyExcel会自动过滤掉excel中的第一行(默认第一行为列名)
* 只有解析到数据才会调用,如果上传的是空excel的话该方法不会调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(MemberDTO data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", data.toString());
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
* 也就是每次请求接口就会调用一次该方法,空excel也会调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据检测完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
if (list.size() > 0){
memberService.saveBatch(list);
}
LOGGER.info("存储数据库成功!");
}
}
3.接受excel数据转换类DTO
package com.my.test.member.api.dto;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.annotations.ApiModelProperty;
/**
* easyExcel官方建议列和类属性的对应要么通过下标要么通过名字不建议都有
* * @ExcelProperty(index = 2)下标:从0开始,2表示第三列;@ExcelProperty("字符串标题")名称匹配
* * 不写@ExcelProperty注解的情况下默认使用的下标对应映射,在excel格式不做任何修改的情况下不写也能满足当前需求,如果用户修改了excel格式不写@ExcelProperty注解的话会导致数据映射关系错误类型不一致导致赋值错误
* * 所以建议使用@ExcelProperty("字符串标题")名称匹配的形式
**/
public class MemberDTO {
@ApiModelProperty("姓名")
@ExcelProperty("姓名")
private String name;
@ApiModelProperty("身份证号码")
@ExcelProperty("身份证号")
private String idCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "MemberDTO{" +
"name='" + name + '\'' +
", idCard='" + idCard + '\'' +
'}';
}
}
4.应答体类R
package com.my.test.member.api;
import com.my.test.member.api.constant.CodeConstant;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 返回编码
*/
@ApiModelProperty("应答状态码0-成功1-失败")
private int code;
/**
* 返回消息
*/
@ApiModelProperty("应答消息")
private String message;
/**
* 返回数据
*/
@ApiModelProperty("数据")
private T data;
public int getCode() {
return code;
}
public void setCode(int 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;
}
public static R ok() {
return setResult(CodeConstant.SUCCESS,CodeConstant.OK_MSG,null);
}
public static <T> R ok(String msg,T data) {
return setResult(CodeConstant.SUCCESS,msg,data);
}
public static <T> R ok(T data) {
return setResult(CodeConstant.SUCCESS,CodeConstant.OK_MSG,data);
}
public static <T> R ok(int code,String message,T data) {
return setResult(code,message,data);
}
public static R ok(int code,String message) {
return setResult(code,message,null);
}
public static R fail() {
return setResult(CodeConstant.FAIL,CodeConstant.FAIL_MSG,null);
}
public static R fail(String message) {
return setResult(CodeConstant.FAIL,message,null);
}
public static <T> R fail(String msg,T data) {
return setResult(CodeConstant.FAIL,msg,data);
}
public static <T> R fail(T data) {
return setResult(CodeConstant.FAIL,CodeConstant.FAIL_MSG,data);
}
public static <T> R fail(int code,String message,T data) {
return setResult(code,message,data);
}
public static R fail(int code,String message) {
return setResult(code,message,null);
}
private static <T> R<T> setResult(int code,String message,T data){
R<T> result = new R();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
5.应答状态常量类
package com.my.test.member.api.constant;
public class CodeConstant {
/**
* 成功code标识
*/
public static final int SUCCESS = 0;
/**
* 失败code标识
*/
public static final int FAIL = 1;
/**
* 消息提示成功
*/
public static final String OK_MSG = "OK";
/**
* 消息提示失败
*/
public static final String FAIL_MSG = "FAIL";
}
到此上传接口编写完毕,DTO和R根据自己项目实际情况定义
二、Postman测试接口
如果你测试或和前端对接时遇到异常“the request was rejected because no multipart boundary was found”异常,原因是requests模块会自动处理在Header中设置"Content-type:multipart/form-data"不需要代码中设置Content-type,如果前端在调用接口时加上了Content-type,去掉后可以正常上传excel文件。
上传非Excel文件报错如下:
excel有数据但是映射不到类上会出现这个错误,也就是excel中的数据的列名和类中的@ExcelProperty(“姓名”)对应不上: