Easy-excel监听器中对批量上传的工单做错误收集

Easy-excel监听器中对批量上传的工单做错误收集

为什么要做"错误收集"?

一、为什么要做"错误收集"?

1. 用户体验更好

  • 如果某一行数据出错就直接中断整个导入流程,用户需要反复上传才能排查所有问题。
  • 错误收集可以让用户一次性看到 哪些行成功、哪些行失败、失败原因是什么

2. 提升调试效率

  • 开发者或运维人员可以通过错误信息快速定位问题数据,比如:
    • 必填字段为空
    • 数据格式错误(如日期格式不正确)
    • 外键不存在(如设备编号找不到)
    • 数值超出范围等

3. 支持部分成功

  • 不影响其他正常数据的导入,只记录错误数据并返回给用户修改。

1.监听器中将错误信息收集到errorMessages集合

java 复制代码
@Slf4j
public class WorkOrderInfoListener implements ReadListener<WorkOrderInfoVo> {
    // 错误信息收集
    private final List<String> errorMessages = new ArrayList<>();

    // 合同名称列表
    private final List<String> contractNames;

    private final Set<String> faultLocationList;

    // 服务依赖由外部传入
    private final IWorkOrderInfoService workOrderInfoService;
    private final IContractInfoService contractInfoService;

    /**
     * 构造函数传入 service,并初始化合同名称
     */
    public WorkOrderInfoListener(IWorkOrderInfoService workOrderInfoService,
                                 IContractInfoService contractInfoService) {
        this.workOrderInfoService = workOrderInfoService;
        this.contractInfoService = contractInfoService;
        this.contractNames = loadContractNames();
        this.faultLocationList = workOrderInfoService.getFaultLocationList(null);
    }

    private List<String> loadContractNames() {
        List<ContractSelectVo> contractSelectVoList = contractInfoService.getNames();
        return contractSelectVoList.stream()
            .map(ContractSelectVo::getContractName)
            .toList();
    }

    @Override
    public void invoke(WorkOrderInfoVo workOrderInfoVo, AnalysisContext analysisContext) {
        try {
            WorkOrderInfo info = new WorkOrderInfo();
            BeanUtils.copyProperties(workOrderInfoVo, info, "id");

            String contractName = info.getContractName();

            if (StringUtils.isBlank(contractName)) {
                int rowNum = analysisContext.readRowHolder().getRowIndex();
                String errorMsg = String.format("第 %d 行数据导入失败:合同名称为空 故障地点: %s",
                    rowNum, workOrderInfoVo.getFaultLocation());
                errorMessages.add(errorMsg);
                return;
            }

            if (!contractNames.contains(contractName)) {
                int rowNum = analysisContext.readRowHolder().getRowIndex();
                String errorMsg = String.format("第 %d 行数据导入失败:合同名称不存在:%s 故障地点: %s",
                    rowNum, contractName, workOrderInfoVo.getFaultLocation());
                errorMessages.add(errorMsg);
                return;
            }
            // 不允许重复点位上报
            if (faultLocationList.contains(info.getFaultLocation())) {
                int rowNum = analysisContext.readRowHolder().getRowIndex();
                String errorMsg = String.format("第 %d 行数据导入失败:该点位正在维修 故障地点: %s",
                    rowNum, workOrderInfoVo.getFaultLocation());
                errorMessages.add(errorMsg);
                return;
            }

            String unit = contractInfoService.getIoCompany(contractName);
            if (unit != null) {
                info.setMaintenanceUnit(unit);
            }
            info.setRepairTime(new Date());


            WorkOrderInfoBo convert = BeanUtil.copyProperties(info, WorkOrderInfoBo.class);
            workOrderInfoService.insertByExcel(convert);

        } catch (Exception e) {
            int rowNum = analysisContext.readRowHolder().getRowIndex();
            String errorMsg = String.format("第 %d 行数据导入失败:系统异常 - %s 故障地点: %s",
                rowNum, e.getMessage(), workOrderInfoVo.getFaultLocation());
            errorMessages.add(errorMsg);
        }

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 所有数据处理完成,无需特殊操作
    }

    /**
     * 获取所有错误信息
     */
    public List<String> getErrorMessages() {
        return Collections.unmodifiableList(errorMessages);
    }

    /**
     * 是否存在错误
     */
    public boolean hasErrors() {
        return !errorMessages.isEmpty();
    }

    /**
     * 清空错误信息
     */
    public void clearErrors() {
        errorMessages.clear();
    }
}

2.控制层设置返回逻辑

如果错误信息存在则返回错误信息给前端展示,比如总共20条数据如果 第2 第14条数据上报时出了问题不会影响其余的18条数据的导入,而是将错误的详细信息返回给用户看

java 复制代码
    @PostMapping("/uploadWorkOrderInfo")
    public R<List<String>> uploadWorkOrderInfo(MultipartFile file, HttpServletResponse response) throws IOException {
        long t1 = System.currentTimeMillis();
        // TODO 文件校验
        WorkOrderInfoListener listener = new WorkOrderInfoListener(workOrderInfoService, contractInfoService);

        try {
            // 开始读取 Excel 文件
            EasyExcel.read(file.getInputStream(), WorkOrderInfoVo.class, listener)
                .sheet()
                .doRead();

            List<String> errorMessages = listener.getErrorMessages();

            // ✅ 提前检查错误信息,存在则汇总返回给前端
            if (CollectionUtil.isNotEmpty(errorMessages)) {
                // 返回错误信息列表
                return R.ok("部分数据导入失败", errorMessages);
            }

            // ⬇️只有在没有错误的情况下才执行以下内容
            long t2 = System.currentTimeMillis();
            String message = "导入全部数据成功! 共用时:"+(t2-t1)+"ms";
            List<String> successMessages = new ArrayList<>();
            successMessages.add(message);
            return R.ok(message,successMessages);
        } catch (Exception e) {
            log.error("文件导入失败: ", e);
            return R.fail("文件导入失败: " + e.getMessage());
        }
    }

3.前端效果展示

如果存在插入失败的情况

相关推荐
华子w908925859几秒前
基于 SpringBoot+Vue.js+ElementUI 的 “花开富贵“ 花园管理系统设计与实现7000字论文
vue.js·spring boot·elementui
小时候的阳光35 分钟前
SpringBoot3 spring.factories 自动配置功能不生效?
spring boot·spring·失效·factories·imports
小莫分享1 小时前
github 镜像节点
java
链上Sniper1 小时前
智能合约状态快照技术:实现 EVM 状态的快速同步与回滚
java·大数据·linux·运维·web3·区块链·智能合约
大只鹅1 小时前
Springboot3整合ehcache3缓存--XML配置和编程式配置
spring boot·缓存
缘来是庄2 小时前
设计模式之建造者模式
java·设计模式·建造者模式
小湘西2 小时前
Apache HttpClient 的请求模型和 I/O 类型
java·http·apache
沃夫上校2 小时前
Feign调Post接口异常:Incomplete output stream
java·后端·微服务
q567315232 小时前
Java Selenium反爬虫技术方案
java·爬虫·selenium
张小洛2 小时前
Spring IOC容器核心阶段解密:★Bean实例化全流程深度剖析★
java·后端·spring·ioc容器·bean实例化