easyExcel 导入时,校验每个单元格数据

目录

1、定义excel导入文件对应的数据接收类

2、定义属性转换器

3、定义数据解析监听器

4、解析文件


1、定义excel导入文件对应的数据接收类
java 复制代码
package com.ruoyi.project.domain.dto;

import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.project.impot.convert.HeadUserExistConverter;
import com.ruoyi.project.impot.convert.ProjectNameConverter;
import com.ruoyi.project.impot.convert.ProjectNoConverter;
import lombok.Data;

import java.math.BigDecimal;

/**
 * 项目管理对象 t_project
 * 
 * @author chance
 * @date 2024-07-24
 */
@Data
public class ImportProjectDto {
    private static final long serialVersionUID = 1L;

    /**
     * $column.columnComment
     */
    private Long id;

    /**
     * 项目名称
     */
    @ExcelProperty(value = "项目名称",converter = ProjectNameConverter.class)
    private String projectName;

    /**
     * 项目编号
     */
    @ExcelProperty(value = "项目编号",converter = ProjectNoConverter.class)
    private String projectNo;

    /**
     * 期初概算
     */
    @ExcelProperty("期初概算")
    private BigDecimal initialBudgetEstimate;

    /**
     * 总概算
     */
    @ExcelProperty("总概算")
    private BigDecimal generalEstimate;

    /**
     * 总概算
     */
    @ExcelProperty("调整概算")
    private BigDecimal changeAmount;

    @ExcelProperty(value = "负责人",converter = HeadUserExistConverter.class)
    private String headUser;

    /**
     * 负责人id
     */
    private Integer headUserId;
}

解释: 类中对"项目名称"、"项目编号"、 "负责人" 指定了转换器

复制代码
ProjectNameConverter ,ProjectNoConverter ,HeadUserExistConverter
2、定义属性转换器

ProjectNameConverter.java 中的逻辑:

1、获取数据库中的项目名称集合(此集合是为了校验文件中项目名称是否和数据库中的项目名称重复)

2、定义excel中项目名称集合(每处理解析一行记录,则将项目名称加入集合,此集合是为了校验excel文件中项目名称是否重复)

如果1,2中的集合包含当前处理的数据记录,则抛出异常,这里异常会在后续自定义的listener中处理

java 复制代码
package com.ruoyi.project.impot.convert;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.project.mapper.ProjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.util.LinkedList;
import java.util.List;

@Slf4j
public class ProjectNameConverter implements Converter<String> {

    private List<String> dbProjectNameList = new LinkedList<>();
    private List<String> fileProjectNameList = new LinkedList<>();
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param context
     * @return
     */
    @Override
    public String convertToJavaData(ReadConverterContext<?> context)   {
        log.info("dbProjectNameList.size{}",dbProjectNameList.size());
        log.info("fileProjectNameList.size{}",fileProjectNameList.size());
        if(CollectionUtils.isEmpty(dbProjectNameList)){
            dbProjectNameList = SpringUtils.getBean(ProjectMapper.class).selectProjectNameList();
        }
        ReadCellData  readCellData= context.getReadCellData() ;
        String value = StringUtils.trim(readCellData.getStringValue());
        if (StringUtils.isNotEmpty(value)) {
            if(dbProjectNameList.contains(value)){
                throw new ExcelDataConvertException(readCellData.getRowIndex(),readCellData.getColumnIndex(),
                        readCellData, context.getContentProperty(), "【"+ value + "】项目名称系统已存在");
            }
            if(fileProjectNameList.contains(value)){
                throw new ExcelDataConvertException(readCellData.getRowIndex(),readCellData.getColumnIndex(),
                        readCellData, context.getContentProperty(),"文件中" + "【"+ value + "】项目名称编码重复");
            }
            fileProjectNameList.add(value);
            return value;
        }
        return null;
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
        return new WriteCellData<>(context.getValue());
    }

}
3、定义数据解析监听器
复制代码
ProjectImportListener

这里的onException 方法会监听到转换器中抛出的异常,这里可以收集每条错误数据的原因,

其他方法不做赘述了。

java 复制代码
package com.ruoyi.project.impot.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.project.domain.Project;
import com.ruoyi.project.domain.dto.ImportProjectDto;
import com.ruoyi.project.mapper.ProjectMapper;
import com.ruoyi.project.mapper.ProjectHeadUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 读取转换异常
 *
 * @author Jiaju Zhuang
 */
@Slf4j
public class ProjectImportListener implements ReadListener<ImportProjectDto> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;

    private List<ImportProjectDto> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    private List<String> errorMsg = new LinkedList<>();

    private List<SysUser> userList = Lists.newArrayList();

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @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());
            errorMsg.add(String.format("第%s行,第%s列解析异常,数据为:%s,原因:%s", excelDataConvertException.getRowIndex()+1,
                    excelDataConvertException.getColumnIndex()+1,
                    excelDataConvertException.getCellData().getStringValue(),
                    excelDataConvertException.getCause().getMessage()));
        }
    }

    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }

    @Override
    public void invoke(ImportProjectDto data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
//        if (cachedDataList.size() >= BATCH_COUNT) {
//            saveData();
//            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        if (errorMsg.size() > 0 ){
            log.info("有%s条数据校验不通过,请检查", errorMsg.size());
            return;
        }
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());

        ProjectMapper mapper =SpringUtils.getBean(ProjectMapper.class);
        ProjectHeadUserMapper userMapper =SpringUtils.getBean(ProjectHeadUserMapper.class);
        userList = userMapper.getUserList();
        Map<String,Long>  userNameIdMap = userList.stream().collect(Collectors.toMap(SysUser::getUserName, SysUser::getUserId));
        for (ImportProjectDto dto: cachedDataList) {
            Project project = new Project();
            BeanUtils.copyProperties(dto, project);
            project.setCreateBy(SecurityUtils.getUsername());
            project.setCreateTime(new Date());
            project.setGeneralEstimate(project.getInitialBudgetEstimate());
            project.setHeadUserId(userNameIdMap.get(dto.getHeadUser()).intValue());
            mapper.insertProject(project);
        }
        log.info("存储数据库成功!");
    }

    public List<String> getErrorMsg() {
        return errorMsg;
    }
}
4、解析文件
java 复制代码
/**
     * @param file
     * @description: 导入项目
     * @return: com.ruoyi.common.core.domain.AjaxResult
     * @author 30864
     * @date: 2024/7/30 20:24
     */
    @PostMapping("/importExcel")
    @PreAuthorize("@ss.hasPermi('project:project:import')")
    public AjaxResult importExcel(MultipartFile file) throws IOException {
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet
        ProjectImportListener listener = new ProjectImportListener();
        EasyExcel.read(file.getInputStream(), ImportProjectDto.class, listener)
                .sheet().headRowNumber(1).doRead();
        List<String> errorMsg = listener.getErrorMsg();
        if (CollectionUtils.isNotEmpty(errorMsg)) {
            return AjaxResult.error(JSON.toJSONString(errorMsg), errorMsg);
        }
        return AjaxResult.success();
    }

总结:

此解析方法可以校验excel 中的每个单元格,且可以在listener 中获取到一行数据时,校验同行数据各属性间是否匹配,收集到错误数据,可以定位到行列号,具体实现可以自定义。

参考官网:读Excel | Easy Excel 官网 (alibaba.com)

相关推荐
徐小黑ACG12 分钟前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
0白露1 小时前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.2 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐2 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
战族狼魂3 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
Tttian6224 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
xyliiiiiL4 小时前
ZGC初步了解
java·jvm·算法
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch5 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
独好紫罗兰5 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法