Easy-excel监听器中对批量上传的工单做错误收集
Easy-excel监听器中对批量上传的工单做错误收集
为什么要做"错误收集"?
-
一、为什么要做“错误收集”?
1. 用户体验更好
- 如果某一行数据出错就直接中断整个导入流程,用户需要反复上传才能排查所有问题。
- 错误收集可以让用户一次性看到 哪些行成功、哪些行失败、失败原因是什么。
2. 提升调试效率
- 开发者或运维人员可以通过错误信息快速定位问题数据,比如:
- 必填字段为空
- 数据格式错误(如日期格式不正确)
- 外键不存在(如设备编号找不到)
- 数值超出范围等
3. 支持部分成功
- 不影响其他正常数据的导入,只记录错误数据并返回给用户修改。
1.监听器中将错误信息收集到errorMessages集合
@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();}@Overridepublic 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);}}@Overridepublic 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条数据的导入,而是将错误的详细信息返回给用户看
@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.前端效果展示
如果存在插入失败的情况