From 82d823e68de51298d9fc178c28cabe42d2be4b24 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Thu, 24 Sep 2026 05:36:41 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E9=A1=B9=E5=AF=BC=E5=85=A5=E6=9C=AA=E6=95=B4=E6=89=B9=E5=9B=9E?= =?UTF-8?q?=E6=BB=9A=EF=BC=8C=E5=A4=B1=E8=B4=A5=E5=8E=9F=E5=9B=A0=E5=86=99?= =?UTF-8?q?=E5=9B=9E=E5=8E=9F=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:FeeItemServiceImpl.importFeeItem 虽有 @Transactional,但循环内吞掉 异常后正常 return,Spring 照常提交——失败行之前的正确行已落库。 改动: - 服务层:错误收集完后抛 ImportFailureException(org.springblade.common.excel) 触发整批回滚;同时把失败原因写回原行对象 excel.setErrorMessage—— 抛出异常时携带的是原表 data,导出工具从行对象的 errorMessage 字段取原因, 只写在 FeeItemImportFailureExcel 副本上会导致失败明细原因为空。 - 控制器:改 catch 异常导出(exportFailureReasonOnly)+ 补上传文件校验, 对齐 PortTerminalController 写法。 验证:mvn install -DskipTests 通过;费用项回归 20/20 (tests/fixtures/fee-item/,含 02_一行错误多行正确_应零写入)。 --- .../system/controller/FeeItemController.java | 18 +++++++++++++----- .../service/impl/FeeItemServiceImpl.java | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java index b8eae386..2bf7ea2d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/FeeItemController.java @@ -36,6 +36,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springblade.common.excel.ImportFailureExcelUtil; +import org.springblade.common.excel.ImportFailureException; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.excel.util.ExcelUtil; import org.springblade.core.mp.support.Condition; @@ -47,7 +48,6 @@ import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.excel.FeeItemExcel; import org.springblade.system.excel.FeeItemExportExcel; -import org.springblade.system.excel.FeeItemImportFailureExcel; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.system.pojo.vo.FeeItemVO; import org.springblade.system.service.IFeeItemService; @@ -138,10 +138,18 @@ public class FeeItemController extends BladeController { @ApiOperationSupport(order = 6) @Operation(summary = "导入费用项", description = "传入excel") public R importFeeItem(MultipartFile file, HttpServletResponse response) { - List data = ExcelUtil.read(file, FeeItemExcel.class); - List failureList = feeItemService.importFeeItem(data); - if (Func.isNotEmpty(failureList)) { - ImportFailureExcelUtil.export(response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", failureList, FeeItemExcel.class); + if (file == null || file.isEmpty()) { + return R.fail("上传文件不能为空"); + } + String fileName = Func.toStrWithEmpty(file.getOriginalFilename(), "").toLowerCase(); + if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) { + return R.fail("请上传 .xls,.xlsx 标准格式文件"); + } + try { + feeItemService.importFeeItem(ExcelUtil.read(file, FeeItemExcel.class)); + } catch (ImportFailureException exception) { + // 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。 + ImportFailureExcelUtil.exportFailureReasonOnly(response, "费用项导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), FeeItemExcel.class); return null; } return R.success("导入数据成功"); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java index a3825a46..4221d262 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/FeeItemServiceImpl.java @@ -108,6 +108,9 @@ public class FeeItemServiceImpl extends BaseServiceImpl if (Func.isEmpty(data)) { throw new ServiceException("导入数据不能为空"); } + // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。 + // 注意不能只往 failureList 里塞数据就 return——异常被吞掉后方法会正常返回, + // Spring 会照常提交事务,@Transactional 起不到回滚作用。 List failureList = new ArrayList<>(); for (int index = 0; index < data.size(); index++) { FeeItemExcel excel = data.get(index); @@ -118,9 +121,18 @@ public class FeeItemServiceImpl extends BaseServiceImpl save(feeItem); } catch (Exception exception) { String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败"; + // 除收集到 failureList 外,还必须把原因写回原行对象: + // 抛出异常时携带的是原表 data,导出工具从行对象的 errorMessage 字段取原因, + // 只写在副本上会导致失败明细里错误行的原因为空。 + excel.setErrorMessage("第" + (index + 2) + "行:" + message); failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行:" + message)); } } + if (Func.isNotEmpty(failureList)) { + // 抛出携带失败明细的异常,触发事务回滚。 + // 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。 + throw new org.springblade.common.excel.ImportFailureException(data); + } return failureList; }