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();
}
@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条数据的导入,而是将错误的详细信息返回给用户看
@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.前端效果展示
如果存在插入失败的情况