注解 | 类型 | 描述 |
---|---|---|
ExcelProperty | 导入 | 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。 |
ExcelIgnore | 导入 | 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段 |
DateTimeFormat | 导入 | 日期转换,用String 去接收excel日期格式的数据会调用这个注解。里面的value 参照java.text.SimpleDateFormat |
NumberFormat | 导入 | 数字转换,用String 去接收excel数字格式的数据会调用这个注解。里面的value 参照java.text.DecimalFormat |
ExcelIgnoreUnannotated | 导入 | 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与 |
导入方法参数:ReadWorkbook
,ReadSheet
都会有的参数,如果为空,默认使用上级。
converter
转换器,默认加载了很多转换器。也可以自定义。readListener
监听器,在读取数据的过程中会不断的调用监听器。headRowNumber
需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。head
与clazz
二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class。clazz
与head
二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。autoTrim
字符串、表头等数据自动trim- password 读的时候是否需要使用密码
ReadWorkbook(理解成excel对象)参数
-
excelType
当前excel的类型 默认会自动判断 -
inputStream
与file
二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用file
参数。因为使用了inputStream
easyexcel会帮忙创建临时文件,最终还是file
-
file
与inputStream
二选一。读取文件的文件。 -
autoCloseStream
自动关闭流。 -
readCache
默认小于5M用 内存,超过5M会使用EhCache
,这里不建议使用这个参数。 -
useDefaultListener@since 2.1.4默认会加入ModelBuildEventListener来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到
class对象,请调用readListener方法,加入自定义的beforeListener、ModelBuildEventListener、 自定义afterListener即可。
ReadSheet(就是excel的一个Sheet)参数
sheetNo
需要读取Sheet的编码,建议使用这个来指定读取哪个SheetsheetName
根据名字去匹配Sheet,excel 2003不支持根据名字去匹配
添加pom依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
<!--工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.23</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.21</version>
</dependency>
<!--commons依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
第一种:简单导入
实体类
package com.example.mybatismysql8demo.excel;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
//忽视无注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty(value = {"商品信息","商品名称"},index = 0)
public String goodsName;
@ExcelProperty(value = {"商品信息","商品价格"},index = 1)
public BigDecimal price;
@ExcelProperty(value = {"商品信息","商品数量"},index = 2)
public Integer num;
}
监听器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
import java.util.function.Consumer;
/**
* 读取excel数据
*/
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {
/**临时存储正常数据集合,最大存储100*/
private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(100);
/**自定义消费者函数接口用于自定义监听器中数据组装*/
private final Consumer<List<GoodsImportExcel>> successConsumer;
public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer) {
this.successConsumer = successConsumer;
}
@Override
public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
successDataList.add(goodsImportExcel);
System.out.println("数据:"+goodsImportExcel);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (CollectionUtils.isNotEmpty(successDataList)) {
successConsumer.accept(successDataList);
}
}
}
执行方法
package com.example.mybatismysql8demo.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file,Integer type) {
if (!file.isEmpty()) {
//文件名称
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名称长度
int last = file.getOriginalFilename().length();
//判断文件格式是否正确
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上传文件格式错误");
}
} else {
throw new IllegalArgumentException("文件不能为空");
}
try (InputStream inputStream = file.getInputStream()) {
if (type == 1){
simpleRead(inputStream);
}else if (type == 2){
synchronousRead(inputStream);
}else {
repeatedRead(inputStream);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
/**
* 最简单的读的监听器
*/
public void simpleRead(InputStream inputStream){
//获取正确数据
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(
// 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
successArrayList::addAll))
// 设置sheet,默认读取第一个
.sheet()
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.doReadSync();
System.out.println(successArrayList);
}
/**
* 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
*/
public void synchronousRead(InputStream inputStream){
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
// 设置sheet,默认读取第一个
.sheet()
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.doReadSync();
System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
}
/**
* 读取多个sheet
*/
public void repeatedRead(InputStream inputStream){
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//使用模型来读取Excel(多个sheet)
ExcelReader reader = EasyExcel.read(inputStream).build();
//多个sheet
List<ReadSheet> sheetList = new ArrayList<>();
for (int i = 0; i < reader.getSheets().size(); i++){
// 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet = EasyExcel.readSheet(i)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(successArrayList::addAll))
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.build();
sheetList.add(readSheet);
}
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
reader.read(sheetList);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
reader.finish();
System.out.println(successArrayList);
}
}
结果打印
数据:GoodsImportExcel(goodsName=苹果, price=10, num=11)
数据:GoodsImportExcel(goodsName=香蕉, price=8, num=12)
数据:GoodsImportExcel(goodsName=梨子, price=11.0, num=30)
数据:GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)
[GoodsImportExcel(goodsName=苹果, price=10, num=11), GoodsImportExcel(goodsName=香蕉, price=8, num=12), GoodsImportExcel(goodsName=梨子, price=11.0, num=30), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)]
导入模版
第二种:数据校验
自定义注解
package com.example.mybatismysql8demo.config;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LengthValid {
int length() default 0;
String msg() default "";
int cell() default 0;
}
实体类
package com.example.mybatismysql8demo.excel;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.example.mybatismysql8demo.config.LengthValid;
import lombok.Data;
import java.math.BigDecimal;
@Data
//忽视无注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@LengthValid(length = 5,msg = "商品名称长度超出5个字符串!",cell = 1)
@ExcelProperty(value = {"商品信息","商品名称"},index = 0)
public String goodsName;
@ExcelProperty(value = {"商品信息","商品价格"},index = 1)
public BigDecimal price;
@ExcelProperty(value = {"商品信息","商品数量"},index = 2)
public Integer num;
private String errorMsg;
}
监听器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.api.Assert;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* 读取excel数据
*/
@Slf4j
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {
/**单次处理上限100条记录*/
private static final int BATCH_COUNT = 100;
/**临时存储正常数据集合*/
private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
/**临时存错误储数据集合*/
private List<GoodsImportExcel> errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
/**自定义消费者函数接口用于自定义监听器中数据组装*/
private final Consumer<List<GoodsImportExcel>> successConsumer;
private final Consumer<List<GoodsImportExcel>> errorConsumer;
public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer, Consumer<List<GoodsImportExcel>> errorConsumer) {
this.successConsumer = successConsumer;
this.errorConsumer = errorConsumer;
}
/**手机号格式异常日志处理*/
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("异常信息:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号,如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}else if (exception instanceof IllegalArgumentException){
throw new IllegalArgumentException(exception.getMessage());
}
}
/**
* 在这里进行模板的判断
* @param headMap 存放着导入表格的表头,键是索引,值是名称
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
//只校验第三行表头是否正确
Integer rowNum = context.getCurrentRowNum();
if (rowNum == 2) {
// 获取数据实体的字段列表
Field[] fields = GoodsImportExcel.class.getDeclaredFields();
// 遍历字段进行判断
for (Field field : fields) {
// 获取当前字段上的ExcelProperty注解信息
ExcelProperty fieldAnnotation = field.getAnnotation(ExcelProperty.class);
// 判断当前字段上是否存在ExcelProperty注解
if (fieldAnnotation != null) {
String value = fieldAnnotation.value()[1];
// 存在ExcelProperty注解则根据注解的value值到表格中对比是否存在对应的表头
if(!headMap.containsValue(value)){
// 如果表格不包含模版类字段中的表头,则抛出异常不再往下执行
throw new RuntimeException("模板错误,请检查导入模板");
}
}
}
}
}
/**每行读取监听触发逻辑*/
@SneakyThrows
@Override
public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
//获取总行数
Integer rowNumber = analysisContext.readSheetHolder().getApproximateTotalRowNumber();
//行数
int row = analysisContext.readRowHolder().getRowIndex();
log.info("第" + row + "行数据进行处理");
// 手机号格式校验
validParam(goodsImportExcel,row);
//正常数据
successDataList.add(goodsImportExcel);
// 按照指定条数对导入数据进行分批处理
if (successDataList.size() >= BATCH_COUNT) {
successConsumer.accept(successDataList);
successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
private void validParam(GoodsImportExcel goodsImportExcel, int row) throws IllegalAccessException {
// 参数校验
Field[] fields = goodsImportExcel.getClass().getDeclaredFields();
for (Field field : fields) {
//设置可访问
field.setAccessible(true);
//判断字段是否添加校验
boolean valid = field.isAnnotationPresent(LengthValid.class);
if (valid) {
//获取注解信息
LengthValid annotation = field.getAnnotation(LengthValid.class);
//行数列数
String msg = "第" + row + "行的第" + annotation.cell() + "列:";
//值
String value = (String) field.get(goodsImportExcel);
if(value.length() > annotation.length()){
//错误信息
goodsImportExcel.setErrorMsg(msg + annotation.msg());
//错误数据
errorDataList.add(goodsImportExcel);
// 按照指定条数对导入数据进行分批处理
if (errorDataList.size() >= BATCH_COUNT) {
errorConsumer.accept(errorDataList);
errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
}
throw new RuntimeException(msg + annotation.msg());
}
}
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (CollectionUtils.isNotEmpty(successDataList)) {
successConsumer.accept(successDataList);
}
if (CollectionUtils.isNotEmpty(errorDataList)) {
errorConsumer.accept(errorDataList);
}
}
/**
* 额外信息(批注、超链接、合并单元格信息读取)
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
log.info("读取到了一条额外信息:{}", JSONObject.toJSONString(extra));
switch (extra.getType()) {
case COMMENT:
log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
break;
case HYPERLINK:
if ("Sheet1!A1".equals(extra.getText())) {
log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
} else if ("Sheet2!A1".equals(extra.getText())) {
log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}," + "内容是:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex(), extra.getText());
} else {
Assert.fail("Unknown hyperlink!");
}
break;
case MERGE:
log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex());
break;
default:
}
}
/**
*监听器的hasNext()方法时没有注意到默认返回的是false,导致一进监听器就判断已经没有下一条记录,直接跳出监听器,然后导入就完成,也不会报错,改成返回true即可解决
*/
@Override
public boolean hasNext(AnalysisContext analysisContext) {
return true;
}
}
执行方法
package com.example.mybatismysql8demo.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file,Integer type) {
if (!file.isEmpty()) {
//文件名称
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名称长度
int last = file.getOriginalFilename().length();
//判断文件格式是否正确
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上传文件格式错误");
}
} else {
throw new IllegalArgumentException("文件不能为空");
}
try (InputStream inputStream = file.getInputStream()) {
if (type == 1){
simpleRead(inputStream);
}else if (type == 2){
synchronousRead(inputStream);
}else {
repeatedRead(inputStream);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
/**
* 最简单的读的监听器
*/
public void simpleRead(InputStream inputStream){
//获取正确数据
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//获取错误数据
ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(
// 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
successArrayList::addAll, errorArrayList::addAll))
// 设置sheet,默认读取第一个
.sheet()
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.doReadSync();
System.out.println(successArrayList);
System.out.println(errorArrayList);
}
/**
* 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
*/
public void synchronousRead(InputStream inputStream){
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
.head(GoodsImportExcel.class)
// 设置sheet,默认读取第一个
.sheet()
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.doReadSync();
System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
}
/**
* 读取多个sheet
*/
public void repeatedRead(InputStream inputStream){
ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
//获取错误数据
ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
//使用模型来读取Excel(多个sheet)
ExcelReader reader = EasyExcel.read(inputStream).build();
//多个sheet
List<ReadSheet> sheetList = new ArrayList<>();
for (int i = 0; i < reader.getSheets().size(); i++){
// 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet = EasyExcel.readSheet(i)
.head(GoodsImportExcel.class)
.registerReadListener(new DemoDataListener(successArrayList::addAll, errorArrayList::addAll))
// 设置标题(字段列表)所在行数
.headRowNumber(2)
.build();
sheetList.add(readSheet);
}
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
reader.read(sheetList);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
reader.finish();
System.out.println(successArrayList);
System.out.println(errorArrayList);
}
}
结果打印
[GoodsImportExcel(goodsName=苹果, price=10, num=11, errorMsg=null), GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40, errorMsg=null)]
[GoodsImportExcel(goodsName=梨子1111, price=11.0, num=30, errorMsg=第2行的第1列:商品名称长度超出5个字符串!)]
导入模版
第三种:读取存在合并
监听器
package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Excel模板的读取监听类
* @author gd
*/
public class ImportExcelListener<T> extends AnalysisEventListener<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelListener.class);
/**
* 解析的数据
*/
private final List<T> list = new ArrayList<>();
/**
* 正文起始行
*/
private final Integer headRowNumber;
/**
* 合并单元格
*/
private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
public ImportExcelListener(Integer headRowNumber) {
this.headRowNumber = headRowNumber;
}
/**
* 这个每一条数据解析都会来调用
*/
@Override
public void invoke(T data, AnalysisContext context) {
LOGGER.info("数据处理: " + JSON.toJSONString(data));
list.add(data);
}
/**
* 所有数据解析完成了 都会来调用
* @param context context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
LOGGER.info("所有数据解析完成!");
}
/**
* 返回解析出来的List
*/
public List<T> getData() {
return list;
}
/**
* 读取额外信息:合并单元格
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
LOGGER.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
switch (extra.getType()) {
case COMMENT:
LOGGER.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
break;
case MERGE: {
LOGGER.info(
"额外信息是合并单元格,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), extra.getLastColumnIndex());
if (extra.getRowIndex() >= headRowNumber) {
extraMergeInfoList.add(extra);
}
break;
}
default:
}
}
/**
* 返回解析出来的合并单元格List
*/
public List<CellExtra> getExtraMergeInfoList() {
return extraMergeInfoList;
}
}
合并数据处理工具类
package com.example.mybatismysql8demo.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.util.CollectionUtils;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.ImportExcelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
public class ImportExcelMergeUtil<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelMergeUtil.class);
/**
* 返回解析后的List
*
* @param: fileName 文件名
* @param: clazz Excel对应属性名
* @param: sheetNo 要解析的sheet
* @param: headRowNumber 正文起始行
* @return java.util.List<T> 解析后的List
*/
public void getList(InputStream inputStream, Class<GoodsImportExcel> clazz, Integer sheetNo, Integer headRowNumber,List<T> successList,List<T> errorList) {
ImportExcelListener<T> listener = new ImportExcelListener<>(headRowNumber);
try {
EasyExcel.read(inputStream, clazz, listener).extraRead(CellExtraTypeEnum.MERGE).sheet(sheetNo).headRowNumber(headRowNumber).doRead();
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
List<CellExtra> extraMergeInfoList = listener.getExtraMergeInfoList();
//解析数据
List<T> list;
if (CollectionUtils.isEmpty(extraMergeInfoList)) {
list = (listener.getData());
}else {
list = explainMergeData(listener.getData(), extraMergeInfoList, headRowNumber);
}
//数据处理
for (T v : list) {
if(validParam(v)){
errorList.add(v);
}else {
successList.add(v);
}
}
}
private Boolean validParam(T object){
// 参数校验
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
//设置可访问
field.setAccessible(true);
//判断字段是否添加校验
boolean valid = field.isAnnotationPresent(LengthValid.class);
if (valid) {
try {
//获取注解信息
LengthValid annotation = field.getAnnotation(LengthValid.class);
//值
String value = (String) field.get(object);
if(value.length() > annotation.length()){
//错误信息(需要设置字段为public)
Field errorMsg = object.getClass().getField("errorMsg");
if (errorMsg.get(object) == null){
errorMsg.set(object, annotation.msg());
}else {
errorMsg.set(object,errorMsg.get(object) + "," + annotation.msg());
}
return true;
}
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 处理合并单元格
* @param data 解析数据
* @param extraMergeInfoList 合并单元格信息
* @param headRowNumber 起始行
* @return 填充好的解析数据
*/
private List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
//循环所有合并单元格信息
extraMergeInfoList.forEach(cellExtra -> {
int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
int firstColumnIndex = cellExtra.getFirstColumnIndex();
int lastColumnIndex = cellExtra.getLastColumnIndex();
//获取初始值
Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
//设置值
for (int i = firstRowIndex; i <= lastRowIndex; i++) {
for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
setInitValueToList(initValue, i, j, data);
}
}
});
return data;
}
/**
* 设置合并单元格的值
*
* @param filedValue 值
* @param rowIndex 行
* @param columnIndex 列
* @param data 解析数据
*/
private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
T object = data.get(rowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
//提升反射性能,关闭安全检查
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.index() == columnIndex) {
try {
field.set(object, filedValue);
break;
} catch (IllegalAccessException e) {
LOGGER.error("设置合并单元格的值异常:"+e.getMessage());
}
}
}
}
}
/**
* 获取合并单元格的初始值
* rowIndex对应list的索引
* columnIndex对应实体内的字段
* @param firstRowIndex 起始行
* @param firstColumnIndex 起始列
* @param data 列数据
* @return 初始值
*/
private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
Object filedValue = null;
T object = data.get(firstRowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
//提升反射性能,关闭安全检查
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.index() == firstColumnIndex) {
try {
filedValue = field.get(object);
break;
} catch (IllegalAccessException e) {
LOGGER.error("获取合并单元格的初始值异常:"+e.getMessage());
}
}
}
}
return filedValue;
}
}
执行方法
package com.example.mybatismysql8demo.controller;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.utils.ImportExcelMergeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.*;
@Slf4j
@RestController
public class EasyExcelController {
@PostMapping("/easyExcelImport")
public void importExcel(MultipartFile file) {
if (!file.isEmpty()) {
//文件名称
int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
//文件名称长度
int last = file.getOriginalFilename().length();
//判断文件格式是否正确
String fileName = file.getOriginalFilename().substring(begin, last);
if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
throw new IllegalArgumentException("上传文件格式错误");
}
} else {
throw new IllegalArgumentException("文件不能为空");
}
ImportExcelMergeUtil<GoodsImportExcel> helper = new ImportExcelMergeUtil<>();
List<GoodsImportExcel> successList = new ArrayList<>();
List<GoodsImportExcel> errorList = new ArrayList<>();
try {
helper.getList(file.getInputStream(), GoodsImportExcel.class,0,2,successList,errorList);
System.out.println("正确数据:"+successList);
System.out.println("错误数据:"+errorList);
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果打印
正确数据:[GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null)]
错误数据:[GoodsImportExcel(goodsName=苹果11111, price=10, num=11, errorMsg=商品名称长度超出5个字符串!), GoodsImportExcel(goodsName=苹果11111, price=9.0, num=20, errorMsg=商品名称长度超出5个字符串!)]
导入模版