JAVA实现easyExcel批量导入

注解类型描述
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 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。
  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class。
  • clazzhead二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。
  • autoTrim 字符串、表头等数据自动trim
  • password 读的时候是否需要使用密码

ReadWorkbook(理解成excel对象)参数

  • excelType 当前excel的类型 默认会自动判断

  • inputStreamfile二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用file参数。因为使用了inputStream easyexcel会帮忙创建临时文件,最终还是file

  • fileinputStream二选一。读取文件的文件。

  • 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的编码,建议使用这个来指定读取哪个Sheet
  • sheetName 根据名字去匹配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;}@Overridepublic void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {successDataList.add(goodsImportExcel);System.out.println("数据:"+goodsImportExcel);}@Overridepublic 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 同步读取会自动finishList<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();//多个sheetList<ReadSheet> sheetList = new ArrayList<>();for (int i = 0; i < reader.getSheets().size(); i++){// 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的ListenerReadSheet 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;}/**手机号格式异常日志处理*/@Overridepublic 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*/@Overridepublic 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@Overridepublic 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());}}}}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {if (CollectionUtils.isNotEmpty(successDataList)) {successConsumer.accept(successDataList);}if (CollectionUtils.isNotEmpty(errorDataList)) {errorConsumer.accept(errorDataList);}}/*** 额外信息(批注、超链接、合并单元格信息读取)*/@Overridepublic 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即可解决*/@Overridepublic 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 同步读取会自动finishList<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();//多个sheetList<ReadSheet> sheetList = new ArrayList<>();for (int i = 0; i < reader.getSheets().size(); i++){// 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的ListenerReadSheet 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;}/*** 这个每一条数据解析都会来调用*/@Overridepublic void invoke(T data, AnalysisContext context) {LOGGER.info("数据处理: " + JSON.toJSONString(data));list.add(data);}/*** 所有数据解析完成了 都会来调用* @param context context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {LOGGER.info("所有数据解析完成!");}/*** 返回解析出来的List*/public List<T> getData() {return list;}/*** 读取额外信息:合并单元格*/@Overridepublic 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个字符串!)]

导入模版
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity 实现原神中的元素反应

一、元素反应 原神中共有七种元素&#xff0c;分别是水、火、冰、岩、风、雷、草。这七种元素能互相作用 Demo下载&#xff1a;Download 元素反应表格图示&#xff0c;可能不够精准 /火水雷冰草岩风绽放原激化火/蒸发超载融化燃烧结晶扩散烈绽放/水蒸发/感电冻结/碎冰绽放结晶…

mysql优化面试总结

mysql优化 和 mysql优化之索引 两篇文章有大量的实验性的内容&#xff0c;我暂时没时间理解&#xff0c;把八股部分总结到这篇文章中&#xff0c;方便记忆 我们为什么要对sql进行优化 我们开发项目上线初期&#xff0c;由于业务数据量相对较少&#xff0c;一些SQL的执行效率对…

计算机服务器中了mkp勒索病毒怎么办,mkp勒索病毒解密数据恢复流程

网络技术的不断应用与发展&#xff0c;为企业的生产运营带来了极大便利&#xff0c;越来越多的企业依赖网络开展各项工作业务&#xff0c;网络也大大提升了企业的生产运营效率&#xff0c;但网络是一把双刃剑&#xff0c;在为企业提供便利的同时&#xff0c;也为企业的数据安全…

泽攸科技无掩膜光刻机在MEMS压力传感器制造中的应用

在当今的科技快速发展时代&#xff0c;微电子机械系统&#xff08;MEMS&#xff09;技术已成为推动现代传感器技术革新的关键力量。MEMS压力传感器&#xff0c;作为其中的重要分支&#xff0c;广泛应用于生物医学、航空航天、汽车工业等多个领域。随着对传感器性能要求的不断提…

后台架构总结

前言 疫情三年&#xff0c;全国各地的健康码成为了每个人的重要生活组成部分。虽然过去一年&#xff0c;但是回想起来任然历历在目。 今天我就通过当时基于小程序的健康码架构&#xff0c;来给大家讲一下如何基于java&#xff0c;springboot等技术来快速搭建一个后台业务系统…

Docker基本操作 挂载数据卷

在创建一个容器的时候让容器挂载到一个数据卷: 命令:docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx 这里的数据卷如果没有提前创好会自动创建 下边是命令解析 将容器挂载到一个数据卷之后 可以在查看数据卷的目录 在数据卷的目录可以找到容器的内容…

自然语言处理 (NLP) 和文本分析

自然语言处理 (NLP) 和文本分析&#xff1a;NLP 在很多领域都有着广泛的应用&#xff0c;如智能助手、语言翻译、舆情分析等。热门问题包括情感分析、命名实体识别、文本生成等。 让我们一起来详细举例子的分析讲解一下自然语言处理&#xff08;NLP&#xff09;和文本分析的应用…

电子式汽车机油压力传感器的接线方法及特点

电子式机油压力传感器由厚膜压力传感器芯片、信号处理电路、外壳、固定电路板装置和两根引线&#xff08;信号线和报警线&#xff09;组成。信号处理电路由电源电路、传感器补偿电路、调零电路、电压放大电路、电流放大电路、滤波电路和报警电路组成。 厚膜压力传感器是20世纪…

Deckset for Mac激活版:MD文档转幻灯片软件

Deckset for Mac是一款专为Mac用户打造的Markdown文档转幻灯片软件。它凭借简洁直观的界面和强大的功能&#xff0c;成为许多用户的心头好。 Deckset for Mac激活版下载 Deckset支持Markdown语法&#xff0c;让用户在编辑文档时无需分心于复杂的格式设置&#xff0c;只需专注于…

探索的时光 (整数三分)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 5 3 2 1 2 3 输出 28 思路&#xff1a; 根据题意&#xff0c;已经给出了运算函数 当我们看到这些函数的时候&#xff0c;联想一下&#xff0c;它们的单调性&#xff0c;以…

Spark集群配置Hive

Spark搭建过程 Spark集群搭建-CSDN博客 1.首先先下载并解压对应的hive压缩包&#xff0c;要选择适配自己系统的&#xff0c;我这个用的是3.1.2 2.配置环境变量 vim ~/.bashrc export HIVE_HOME/usr/local/hive export PATH$PATH:$HIVE_HOME/bin source ~/.bashrc 3.修改hiv…

半导体制造工艺之分类浅述

半导体制造工艺分为逻辑制程(也叫逻辑工艺)和特殊制程(也叫特色工艺)。 1、逻辑工艺概述 随着集成电路行业沿着摩尔定律不断发展,晶体管数量增加的同时,工艺节点不断缩小。先进逻辑工艺是相对的概念,2005年全球先进逻辑工艺的工艺节点在65/55纳米,现在则变为3纳米。中…

达梦(DM) SQL聚集函数及日期运算操作

达梦DM SQL聚集函数及日期运算操作 聚集函数MAX、MIN、SUM、AVG、COUNT使用分析函数 sum (…) over (order by…) 可以生成累计和更改累计和的值计算出现次数最多的值 日期运算加减日、月、年加减时、分、秒日期间隔之时、分、秒日期间隔之日、月、年求两个日期间的工作天数确定…

Microsoft Access Database使用

“小规模数据用Excel&#xff0c;大规模数据用Access。” 当涉及到大规模数据时&#xff0c;使用excel非常的卡顿&#xff0c;使用access就不会出现这个问题。 一、常用操作 1.新建一个数据库 直接右键&#xff0c;新建 access数据库 2.excel内容导入到access中&#xff08;成…

阿里云服务器(Ubuntu22)上的MySQL8更改为大小写不敏感

因为windows上默认的mysql8.0是大小写不敏感的&#xff0c;部署到服务器上之后发现ubuntu默认的是大小写敏感&#xff0c;所以为了不更改代码&#xff0c;需要将mysql数据库设置为大小写不敏感的。 &#xff01;&#xff01;&#xff01;重要一定要做好数据库的备份&#xff0…

零基础HTML教程(31)--HTML5多媒体

文章目录 1. 背景2. audio音频3. video视频4. audio与video常用属性5. 小结 1. 背景 在H5之前&#xff0c;我们要在网页上播放音频、视频&#xff0c;需要借助第三方插件。 这些插件里面最火的就是Flash了&#xff0c;使用它有几个问题&#xff1a; 首先要单独安装Flash&…

机器学习:逻辑回归

概念 首先&#xff0c;逻辑回归属于分类算法&#xff0c;是线性分类器。我们可以认为逻辑回归是在多元线性回归的基础上把结果给映射到0-1的区间内&#xff0c;hθ&#xff08;x&#xff09;越接近1越有可能是正例&#xff0c;反之&#xff0c;越接近0越有可能是负例。那么&am…

前端如何将接口传来的列表数据(数组)直接下载成csv文件

前言&#xff1a;最近遇到一个需求&#xff0c;需要实现一个下载表格数据的操作&#xff0c;一般来说是前端请求后端的下载接口&#xff0c;将文件流下载下来&#xff0c;但是因为这个项目任务时间比较紧&#xff0c;后端没时间做下载接口&#xff0c;所以暂时由前端直接调列表…

JAVA基础---Stream流

Stream流出现背景 背景 在Java8之前&#xff0c;通常用 fori、for each 或者 Iterator 迭代来重排序合并数据&#xff0c;或者通过重新定义 Collections.sorts的 Comparator 方法来实现&#xff0c;这两种方式对 大数量系统来说&#xff0c;效率不理想。 Java8 中添加了一个…

张大哥笔记:服务器有挖矿木马程序,该如何处理?

这篇文章发表于2021年&#xff0c;今天借这个平台再发布一下&#xff0c;希望对大家有所帮助&#xff01; 今天收到一个粉丝求助&#xff0c;说收到了阿里云官方短信通知提示有挖矿程序&#xff0c;要求立即整改&#xff0c;否则会关停服务器&#xff0c;以下是我和他的对话内…