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

如果存在插入失败的情况

相关推荐
JAVA面经实录9173 小时前
Java企业级工程化·终极完整版背诵手册(无遗漏、全覆盖、面试+落地通用)
java·开发语言·面试
许彰午5 小时前
CacheSQL(二):主从复制——OpLog 环形缓冲区与故障自动恢复
java·数据库·缓存
Bat U6 小时前
JavaEE|多线程初阶(七)
java·开发语言
掌心向暖RPA自动化9 小时前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa
日取其半万世不竭9 小时前
Minecraft Java版社区服务器搭建教程(Linux,适合新手)
java·linux·服务器
TeamDev10 小时前
JxBrowser 9.0.0 版本发布啦!
java·前端·混合应用·jxbrowser·浏览器控件·跨平台渲染·原声输入
AI人工智能+电脑小能手10 小时前
【大白话说Java面试题】【Java基础篇】第24题:Java面向对象有哪些特征
java·开发语言·后端·面试
AI人工智能+电脑小能手11 小时前
【大白话说Java面试题】【Java基础篇】第25题:JDK1.8的新特性有哪些
java·开发语言·后端·面试
likerhood11 小时前
SLF4J: Failed to load class “StaticLoggerBinder“ 解决
java·log4j·maven
早日退休!!!11 小时前
大模型推理瓶颈七层分析模型
java·服务器·数据库