前言

这段时间做了excel 数据导入,用的阿里巴巴的easyExcel 的代码。下面是官网和githab 代码地址。需要将github 上的代码拉下来,方便查看demo.关于Easyexcel | Easy ExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目,在尽可能节约内存的情况下支持读写百M的Excel。https://easyexcel.opensource.alibaba.com/docs/current/

GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具快速、简洁、解决大文件内存溢出的java处理Excel工具. Contribute to alibaba/easyexcel development by creating an account on GitHub.https://github.com/alibaba/easyexcel

 仔细查看上面的代码,模仿上面的代码就可以写出来了。

操作步骤

一,创建对象

对象不能使用@Accessors(chain = true) 注解,不然会出现读取不到数据的情况

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}

二,随便创建一个简单的类,不需要任何配置。

只需要根据官网demo来实现或者继承他们自己的内部类即可,创建的类放哪里都行

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {    

}

三,了解继承类的各个方法

3.1 invokeHead 方法是用来读取表头的。

@Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {}

使用invokeHead 方法需要我们先定义哪几行是表头,下面代码中的.headRowNumber(3) 可以定义哪几行是表头。3待办前三行都是表头,invokeHead 方法会读取前三行,我们可以根据invokeHead 来校验表头

EasyExcel.read(file.getInputStream(), YclJzLqJcVo.class, yclJzLqExcelDataListener).sheet().headRowNumber(3).doRead();

3.2 invoke 是用来读取数据内容的,在invokeHead 方法运行完后就会运行invoke 方法。来读取数据内容。

@Override
public void invoke(YclJzLqJcVo data, AnalysisContext context) {}

3.3  所有数据解析完成了 都会来调用

@Override
public void doAfterAllAnalysed(AnalysisContext context) {}

四,导入并且返回异常数据提示信息

4.1 实体类设置 

@ExcelProperty(index = 1, value = "进场日期")

 @ExcelProperty注解中的index 属性代表该字段读取的excel 上的第几列,index =1 代表该字段读取excel 上的第二列,index =0 读取第一列。

easyexce axios导出 easyexcel导入数据_数据

package easttrans.pitchdatacore.domain.core;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 
 * </p>
 *
 * @author guankong
 * @since 2022-10-26
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="改性沥青模板映射类", description="")
public class YclGxLqJcVo extends CommonVo implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ExcelProperty(index = 0, value = "序号")
    @ApiModelProperty(value = "序号")
    private Integer serialNum;

    @ExcelProperty(index = 1, value = "进场日期")
    @ApiModelProperty(value = "进场日期")
    private Date enterDate;

    @ExcelProperty(index = 2, value = "品牌及标号")
    @ApiModelProperty(value = "品牌及标号")
    private String brand;

    @ExcelProperty(index = 3, value = "堆放地点")
    @ApiModelProperty(value = "堆放地点")
    private String place;

    @ExcelProperty(index = 4, value = "用途")
    @ApiModelProperty(value = "用途面层")
    private String ratio;

    @ExcelProperty(index = 5, value = "质保单编号")
    @ApiModelProperty(value = "质保单编号")
    private String qaNum;

    @ExcelProperty(index = 6, value = "同批数量(t)")
    @ApiModelProperty(value = "同批数量(t)")
    private Double count;

    @ExcelProperty(index = 7, value = "委托单或任务单编号")
    @ApiModelProperty(value = "委托单或任务单编号")
    private String taskNum;

    @ExcelProperty(index = 8, value = "针入度0.1mm")
    @ApiModelProperty(value = "针入度0.1mm")
    private Double zrd;


}

4.2  拦截器设置

这里AnalysisEventListener<YclJzLqJcVo> 可以设置成泛型AnalysisEventListener<T>

@Slf4j
public final  class YclJzLqExcelDataListener extends AnalysisEventListener<YclJzLqJcVo> {

    private final YclLqJcVo yclLqJcVo;

    private YclLqJcService yclLqJcService;
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    private List<YclJzLqJcVo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 存储异常提醒消息
     */
    private Map<String, Object> result = new HashMap<>();
    /**
     * excel表头是否正确,false 有误,true 正确。
     */
    private boolean format = false;

    /**
     * 头信息
     */
    Map<Integer, String> headMap = new HashMap<>();

    /**
     * 返回提示语
     */
    public List<ImportTips> tips = new ArrayList<>();

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param
     */
    public YclJzLqExcelDataListener(YclLqJcService yclLqJcService,YclLqJcVo yclLqJcVo) {
        this.yclLqJcService = yclLqJcService;
        this.yclLqJcVo = yclLqJcVo;
    }


    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context)   {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        int col = 0,row =0;
        String title = "";
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
            col = excelDataConvertException.getColumnIndex();
            row = excelDataConvertException.getRowIndex();
            title = this.headMap.get(col);

        }
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
    }


    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        // 如果想转成 Map<Integer,String>
        // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
        // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
        Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context);
        String s = integerStringMap.get(0);
        if (s != null && s.equals(Constants.JZ_Asp_Mob_Ins_Account)){
            //如果表头包含该内容,则为true
            format = true;
        }
        
    }

    @Override
    public void invoke(YclJzLqJcVo data, AnalysisContext context) {
        result.put("format",format);
        if (!format){
            //format 为false 时
            throw new ExcelAnalysisStopException(Constants.formatTemplateNoTrue);
        }

        //查询改性沥青数据库,判断该数据库中的报告编号是否与cachedDataList 中的报告编号重复,重复不导入
        LambdaQueryWrapper<YclLqJcVo> yclJzLqJcVoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        yclJzLqJcVoLambdaQueryWrapper.eq(YclLqJcVo::getReportNum,data.getReportNum());
        yclJzLqJcVoLambdaQueryWrapper.eq(YclLqJcVo::getType,Constants.JZLQ_TYPE);

        List<YclLqJcVo> list = yclLqJcService.list(yclJzLqJcVoLambdaQueryWrapper);
        if (list.size()>0){
            //表示有数据,该数据不能新增进数据库
            saveTips(context.readRowHolder().getRowIndex(),Constants.Duplicate_Import_Information,tips);
            return;
        }

        
        cachedDataList.add(data);
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();

        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
    
        yclLqJcService.saveBatch(collect);
        log.info("存储数据库成功!");
    }

    /**
     * 返回数据
     * @return 返回读取的数据集合
     **/
    public Map<String,Object> getResult(){
        return result;
    }

    /**
     * 设置读取的数据集合
     * @param result 设置读取的数据集合
     **/
    public void setResult(Map<String,Object> result) {
        this.result = result;
    }

  

}

4.3 controller 层设置

@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    @ApiOperation(value = "沥青进场检验台账导入数据", notes = "沥青进场检验台账导入数据")
    public EastTransResult importExcel(@RequestParam("file") MultipartFile file, YclLqJcVo yclLqJcVo) throws IOException {
        EastTransResult<Object> eastTransResult = new EastTransResult<>();
        //两个判断,第一个是表头,第二个是内容(哪一行的数据不对)
            //type 等于2 为改性沥青,先判断第一行导入模板的表头是否准确
            YclGxLqExcelDataListener yclGxLqExcelDataListener = new YclGxLqExcelDataListener(yclLqJcService, yclLqJcVo);
            EasyExcel.read(file.getInputStream(), YclGxLqJcVo.class, yclGxLqExcelDataListener).sheet().headRowNumber(3).doRead();
            Map<String, Object> result = yclGxLqExcelDataListener.getResult();
            boolean format = (boolean) result.get("format");
            eastTransResult = new EastTransResult(200, null, "请求成功");
            if (!format) {
                //表头错误
                eastTransResult = new EastTransResult(403, null, "该模板不正确");
            }

        }
        return eastTransResult;
    }

返回的数据是 Map<String, Object> result ,可以看下listener 类里面的result 是怎么定义的,先创建了一个 

private Map<String, Object> result = new HashMap<>();

 然后设置了该result 的get,set 方法。

public Map<String,Object> getResult(){
    return result;
}

public void setResult(Map<String,Object> result) {
    this.result = result;
}

 然后可以在listener 类里面result.put("key","val");

后面在controller 中获取该类listener 的getResult 方法就可以获取这个map 了。

如果数据导入太慢,可以使用多线程