🐛 修复常用货物导入未整批回滚,失败明细原因写回原行
问题:CommonCargoServiceImpl.importCommonCargo 循环内吞掉异常后正常 return, 方法正常返回导致 Spring 照常提交事务。方法虽有 @Transactional(rollbackFor = Exception.class),但异常被吞后注解救不了——失败行之前的正确行已经落库。 改动: - 服务层:错误收集完毕后若 failureList 非空,抛 ImportFailureException (org.springblade.common.excel,#2 已迁入 blade-common)触发整批回滚。 - 修复失败明细原因为空的问题:catch 里除写入 failureExcel 副本外, 还必须把原因写回原行对象 excel.setErrorMessage(...)——抛出异常时携带的是 原表 data,导出工具从行对象的 errorMessage 字段取原因,只写副本会丢。 - 控制器:改为 catch 异常 → 取 failureList 导出 → return null,并补上传 文件校验,对齐 PortTerminalController 写法。 验证: - mvn install -DskipTests 全量编译通过。 - 常用货物回归 25/25 通过(新增 tests/fixtures/common-cargo/, scripts/api-common-cargo-import-test.sh),含 02_一行错误多行正确_应零写入。 - 港口码头基线回归 45/45 通过,确认无连带影响。
This commit is contained in:
+22
-4
@@ -38,9 +38,9 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.transport.excel.CommonCargoExcel;
|
||||
import org.springblade.transport.excel.CommonCargoExportExcel;
|
||||
import org.springblade.transport.excel.CommonCargoImportFailureExcel;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springblade.transport.pojo.entity.CommonCargo;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
@@ -110,14 +110,32 @@ public class CommonCargoController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "导入常用货物", description = "传入excel")
|
||||
public R importCommonCargo(MultipartFile file, HttpServletResponse response) {
|
||||
List<CommonCargoImportFailureExcel> failureList = commonCargoService.importCommonCargo(ExcelUtil.read(file, CommonCargoExcel.class));
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
ImportFailureExcelUtil.export(response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CommonCargoImportFailureExcel.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 {
|
||||
commonCargoService.importCommonCargo(ExcelUtil.read(file, CommonCargoExcel.class));
|
||||
} catch (ImportFailureException exception) {
|
||||
// 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。
|
||||
exportFailure(response, exception.getFailureList());
|
||||
return null;
|
||||
}
|
||||
return R.success("导入数据成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。
|
||||
* <p>
|
||||
* 失败数据仅标红出错单元格与失败原因列,表头保持默认样式。
|
||||
*/
|
||||
private void exportFailure(HttpServletResponse response, List<?> failureList) {
|
||||
ImportFailureExcelUtil.exportFailureReasonOnly(response, "常用货物导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CommonCargoExcel.class);
|
||||
}
|
||||
|
||||
@GetMapping("/export-template")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "导出模板")
|
||||
|
||||
+14
@@ -25,6 +25,7 @@ package org.springblade.transport.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.common.excel.ImportFailureException;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
@@ -143,6 +144,10 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
if (Func.isEmpty(data)) {
|
||||
throw new ServiceException("导入数据不能为空");
|
||||
}
|
||||
// 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。
|
||||
// 注意:本方法虽已标注 @Transactional,但循环内把异常吞掉后正常 return,
|
||||
// Spring 会认为一切成功并提交事务——注解救不了被吞掉的异常。
|
||||
// 必须把错误明细收集完后抛异常,回滚才会真正发生。
|
||||
List<CommonCargoImportFailureExcel> failureList = new ArrayList<>();
|
||||
for (int index = 0; index < data.size(); index++) {
|
||||
CommonCargoExcel excel = data.get(index);
|
||||
@@ -170,12 +175,21 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
commonCargo.setDataSource("批量导入");
|
||||
submit(commonCargo, false);
|
||||
} catch (Exception exception) {
|
||||
// 除收集到 failureList 外,还必须把原因写回原行对象:
|
||||
// 抛出异常时携带的是原表 data,导出工具从行对象的 errorMessage 字段取原因,
|
||||
// 只写在副本上会导致失败明细里错误行的原因为空。
|
||||
excel.setErrorMessage(exception.getMessage());
|
||||
CommonCargoImportFailureExcel failureExcel = new CommonCargoImportFailureExcel();
|
||||
BeanUtil.copyProperties(excel, failureExcel);
|
||||
failureExcel.setErrorMessage(exception.getMessage());
|
||||
failureList.add(failureExcel);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(failureList)) {
|
||||
// 抛出携带失败明细的异常,触发事务回滚。
|
||||
// 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。
|
||||
throw new ImportFailureException(data);
|
||||
}
|
||||
return failureList;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user