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;
    }

    @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个字符串!)]

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

投资蓄能之际,九安医疗如何进一步稳固主业“压舱石”?

体外诊断行业的消费环境变化&#xff0c;正从相关企业的发展中体现。 据梳理&#xff0c;随着疫情检测需求回落&#xff0c;2023年以来&#xff0c;菲鹏生物、雅睿生物、中翰生物等体外诊断公司&#xff0c;陆续主动撤回上市申请。 而已上市公司也正处于周期性调整阶段。4月2…

MobileNetV4 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2404.10518 代码地址&#xff1a;https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py 解决了什么问题&#xff1f; 边端设备的高效神经网络不仅能带来实时交互的体验&#xff0c…

(学习日记)2024.05.10:UCOSIII第六十四节:常用的结构体(os.h文件)第三部分

之前的章节都是针对某个或某些知识点进行的专项讲解&#xff0c;重点在功能和代码解释。 回到最初开始学μC/OS-III系统时&#xff0c;当时就定下了一个目标&#xff0c;不仅要读懂&#xff0c;还要读透&#xff0c;改造成更适合中国宝宝体质的使用方式。在学完野火的教程后&a…

设计模式——保护性暂停

同步模式之保护性暂停 文章目录 同步模式之保护性暂停定义实现应用带超时版 GuardedObject扩展——原理之join扩展——多任务版 GuardedObject 定义 即 Guarded Suspension&#xff0c;用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程&…

【逆向百例】百度翻译js逆向

关注它&#xff0c;不迷路。 本文章中所有内容仅供学习交流&#xff0c;不可用于任何商业用途和非法用途&#xff0c;否则后果自负&#xff01; 前言 目标 分析某度翻译接口&#xff0c;使用python获取翻译结果&#xff0c;并用pyinstaller打包成单文件可执行程序。 工具 ch…

python自定义交叉熵损失,再和pytorch api对比

背景 我们知道&#xff0c;交叉熵本质上是两个概率分布之间差异的度量&#xff0c;公式如下 其中概率分布P是基准&#xff0c;我们知道H(P,Q)>0&#xff0c;那么H(P,Q)越小&#xff0c;说明Q约接近P。 损失函数本质上也是为了度量模型和完美模型的差异&#xff0c;因此可以…

理解红黑树结构

红黑树的特性 节点是红色或黑色根是黑色叶子节点&#xff08;外部节点&#xff0c;空节点&#xff09;都是黑色&#xff0c;这里的叶子节点指的是最底层的空节点&#xff08;外部节点&#xff09;&#xff0c;下图中的那些null节点才是叶子节点&#xff0c;null节点的父节点在…

偏微分方程算法之五点菱形差分法

目录 一、研究目标 二、理论推导 三、算例实现 四、结论 一、研究目标 上个专栏我们介绍了双曲型偏微分方程的主要算法及实现。从今天开始&#xff0c;我们在新的专栏介绍另一种形式偏微分方程-椭圆型的解法。 研究目标选取经典的二维椭圆型方程&#xff08;也称泊松Poisso…

选对伪原创改写软件,文章写作不犯难!

文章写作在当下火热的自媒体的行业中是一项非常重要的技能&#xff0c;只要是参与做自媒体的朋友&#xff0c;想要在内容输出方面不出现困难的情况&#xff0c;那么文章写作的技能基本要具备&#xff0c;但是我们依然能看到有很多不擅长写作的朋友也做起了自媒体&#xff0c;并…

高扬程水泵的性能与应用领域 /恒峰智慧科技

在现代社会中&#xff0c;科技的发展为我们的生活带来了无数便利和可能性。其中&#xff0c;高扬程水泵作为一种高效能的水泵&#xff0c;其独特的设计使其在各个领域都有着广泛的应用&#xff0c;尤其是在森林消防中。 一、高扬程水泵的性能 1. 高扬程&#xff1a;高扬程水泵…

SpringCloud-Seata分布式事务的环境搭建搭建

目录 一、版本说明 二、建立Seata Server数据库&#xff08;TC-带头大哥的数据库&#xff09; 三、业务库建表 四、安装Seata-Server 4.1 虚拟机里新建一个/opt/seate/seata-server文件夹&#xff0c;在seate文件夹下新建一个docker-compose.yml 文件 4.2 运行容器 4.3 在na…

vue为遍历生成的表单设置ref属性

最近在写表单重置的时候出现了问题&#xff0c;在this.$refs[formName].resetFields();的时候卡了很久。 经过网上的搜索终于解决的问题&#xff01; 对于不需要遍历的表单 这是vue代码&#xff1a; <el-dialog title"段落描述" :visible.sync"dialogFormV…

从零开始的Dify大模型应用开发指南

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)

【Paddle】PCA线性代数基础及领域应用 写在最前面一、PCA线性代数基础1. PCA的算法原理2. PCA的线性代数基础2.1 标准差 Standard Deviation2.2 方差 Variance2.3 协方差 Covariance2.4 协方差矩阵 The Covariance Matrix2.5 paddle代码demo①&#xff1a;计算协方差矩阵2.6 特…

【方法】PPT文件如何撤销密码保护?

对于重要的PPT文件&#xff0c;很多人会设置密码保护&#xff0c;那后续不需要保护的时候&#xff0c;要怎么撤销密码呢&#xff1f; 首先&#xff0c;我们要看下想要撤销的是什么密码&#xff0c;以及在记得密码或者忘记密码的情况下&#xff0c;处理方式也不同&#xff0c;下…

保证接口幂等性的多种实现方式(数据库方案)

1. 幂等性的概念 接口幂等性是指在软件工程和Web服务领域中&#xff0c;一个接口&#xff08;通常是HTTP API&#xff09;无论被调用一次还是多次&#xff0c;其对系统产生的副作用应该是相同的&#xff0c;即结果保持一致&#xff0c;不会因为多次请求而有所不同。换句话说&am…

nginx--安装

yum安装 官方包链接&#xff1a;nginx: Linux packages 官方yum源链接&#xff1a;nginx: Linux packages 配置yum源 [rootlocalhost ~]# yum install -y nginx [nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gp…

嵌入式学习62-C++

知识零碎&#xff1a; 析构函数语法&#xff1a; ~类名(){} 1.析构函数&#xff0c;没有返回值也不写void 2.析构函数是构造函数的反过程 构造函数 在执行过程中的三个过程…

【C++初阶】string

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

客户端连接ZK失败处理方案

文章目录 背景介绍报错信息处理方案第一步、查看zookeeper启动是否正常第二步、检查本地网络是否正常第三步、检查本地JDK版本 对于zookeeper服务注册中心&#xff0c;在前期【 Dubbo框架注册中心-Zookeeper搭建】博客中有环境搭建部署介绍&#xff0c;感兴趣可以参考安装。 背…
最新文章