diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java index 0de1e680..c0ab7473 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RegionController.java @@ -46,6 +46,7 @@ import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.DateUtil; +import org.springblade.core.tool.utils.Func; import org.springblade.system.pojo.entity.Region; import org.springblade.system.excel.RegionExcel; import org.springblade.system.excel.RegionExportExcel; @@ -191,9 +192,18 @@ public class RegionController extends BladeController { @ApiOperationSupport(order = 10) @Operation(summary = "导入行政区划", description = "传入excel") public R importRegion(MultipartFile file, Integer isCovered, HttpServletResponse response) { - List failureList = regionService.importRegion(ExcelUtil.read(file, RegionExcel.class), Objects.equals(isCovered, 1)); - if (!failureList.isEmpty()) { - org.springblade.common.excel.ImportFailureExcelUtil.export(response, "行政区划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, RegionExcel.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 { + regionService.importRegion(ExcelUtil.read(file, RegionExcel.class), Objects.equals(isCovered, 1)); + } catch (org.springblade.common.excel.ImportFailureException exception) { + // 任一行失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。 + org.springblade.common.excel.ImportFailureExcelUtil.exportFailureReasonOnly(response, "行政区划导入失败明细" + DateUtil.time(), "导入失败明细", exception.getFailureList(), RegionExcel.class); return null; } return R.success("操作成功"); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RegionServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RegionServiceImpl.java index 77ceabc1..a251af29 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RegionServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RegionServiceImpl.java @@ -41,6 +41,7 @@ import org.springblade.system.mapper.RegionMapper; import org.springblade.system.service.IRegionService; import org.springblade.system.pojo.vo.RegionVO; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Date; @@ -60,6 +61,17 @@ public class RegionServiceImpl extends ServiceImpl impleme @Override public boolean submit(Region region) { + return submit(region, true); + } + + /** + * 提交区划,clearCache 控制是否在保存成功后立即清理惰性树缓存。 + *

+ * 批量导入路径传 false:导入内部会收集全部错误,只有整批成功才统一清缓存; + * 若逐行清缓存,失败批次中已成功行会把缓存清掉/重算成中间状态, + * 与随后的事务回滚形成「库回滚了、缓存却是脏的」。 + */ + private boolean submit(Region region, boolean clearCache) { Date now = new Date(); Long currentUserId = AuthUtil.getUserId(); boolean isNew = StringUtil.isBlank(region.getOriginalCode()); @@ -138,7 +150,7 @@ public class RegionServiceImpl extends ServiceImpl impleme region.setVillageName(name); } boolean result = StringUtil.isNotBlank(region.getOriginalCode()) ? this.updateById(region) : this.save(region); - if (result) { + if (result && clearCache) { clearLazyTree(); } return result; @@ -189,12 +201,15 @@ public class RegionServiceImpl extends ServiceImpl impleme } @Override + @Transactional(rollbackFor = Exception.class) public List importRegion(List data, Boolean isCovered) { if (Func.isEmpty(data)) { throw new ServiceException("导入数据不能为空"); } + // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。 + // 注意不能只往 errorList 里塞数据就 return——异常被吞掉后方法会正常返回, + // Spring 会照常提交事务,@Transactional 起不到回滚作用。 List errorList = new ArrayList<>(); - boolean cacheChanged = false; for (int index = 0; index < data.size(); index++) { RegionExcel excel = data.get(index); try { @@ -210,15 +225,20 @@ public class RegionServiceImpl extends ServiceImpl impleme if (Boolean.TRUE.equals(isCovered) && this.getById(region.getCode()) != null) { region.setOriginalCode(region.getCode()); } - cacheChanged = this.submit(region) || cacheChanged; + // 导入路径不逐行清缓存,缓存只在整批成功后统一清一次。 + this.submit(region, false); } catch (Exception exception) { excel.setErrorMessage(exception.getMessage()); errorList.add(excel); } } - if (cacheChanged) { - clearLazyTree(); + if (Func.isNotEmpty(errorList)) { + // 抛出携带失败明细的异常,触发事务回滚。缓存清理在异常抛出后不可达, + // 只会发生在成功路径上——避免「库回滚了、惰性树缓存却按失败批次 + // 的中间状态被清掉/重算」的脏缓存。 + throw new org.springblade.common.excel.ImportFailureException(data); } + clearLazyTree(); return errorList; }