赞
踩
注解 | 类型 | 描述 |
---|---|---|
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
字符串、表头等数据自动trimReadWorkbook(理解成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个字符串!)]
导入模版
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。