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.前端效果展示

如果存在插入失败的情况

相关推荐
武子康1 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
YuTaoShao4 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw4 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨4 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
双力臂4045 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
Edingbrugh.南空5 小时前
Aerospike与Redis深度对比:从架构到性能的全方位解析
java·开发语言·spring
QQ_4376643146 小时前
C++11 右值引用 Lambda 表达式
java·开发语言·c++
永卿0016 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式
誰能久伴不乏6 小时前
Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
java·服务器·前端
itLaity6 小时前
基于Kafka实现简单的延时队列
spring boot·分布式·kafka