From 81daeb9660e6641cc547c245eb689f4a75a51052 Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 29 Apr 2026 17:49:13 +0800 Subject: [PATCH] feat(mod): module update parent validation REQ-MOD-002 --- backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java | 24 ++++++++++++++++++++++++ backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 0 deletions(-) diff --git a/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java b/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java index f0180e1..aa78dfa 100644 --- a/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java +++ b/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java @@ -21,6 +21,7 @@ import java.util.Set; public class ModuleServiceImpl implements ModuleService { private static final Set DISPLAY_TYPES = Set.of("手机端", "前端业务", "系统配置", "接口"); + private static final int MAX_PARENT_DEPTH = 50; private final ModuleMapper moduleMapper; private final TenantProperties tenant; @@ -76,6 +77,7 @@ public class ModuleServiceImpl implements ModuleService { if (!DISPLAY_TYPES.contains(dto.getSDisplayType())) { throw new BizException(40010, "显示类型枚举不合法"); } + validateParent(id, dto.getIParentId()); Module entity = new Module(); entity.setIIncrement(id); @@ -90,4 +92,26 @@ public class ModuleServiceImpl implements ModuleService { moduleMapper.updateById(entity); return id; } + + private void validateParent(Integer id, Integer parentId) { + if (parentId == null) { + return; + } + if (parentId.equals(id)) { + throw new BizException(40021, "父模块不能指向自身"); + } + if (!moduleMapper.existsActiveById(parentId)) { + throw new BizException(40021, "父模块不存在或已删除"); + } + Integer cur = moduleMapper.selectParentIdById(parentId); + for (int depth = 0; cur != null; depth++) { + if (cur.equals(id)) { + throw new BizException(40021, "父模块链构成环路"); + } + if (depth >= MAX_PARENT_DEPTH) { + throw new BizException(40021, "父模块链超过最大层级"); + } + cur = moduleMapper.selectParentIdById(cur); + } + } } diff --git a/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java b/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java index ec69741..b7c40d9 100644 --- a/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java +++ b/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java @@ -184,6 +184,60 @@ class ModuleServiceImplTest { assertThat(captor.getValue().getBShowPermission()).isFalse(); } + @Test + void updateWithInvalidDisplayType_throws40010() { + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10)); + UpdateModuleDTO dto = baseUpdateDto(); + dto.setSDisplayType("未知"); + + assertThatThrownBy(() -> service.update(10, dto)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40010); + verify(moduleMapper, never()).updateById(any(Module.class)); + } + + @Test + void updateWithSelfParentId_throws40021() { + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10)); + UpdateModuleDTO dto = baseUpdateDto(); + dto.setIParentId(10); + + assertThatThrownBy(() -> service.update(10, dto)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40021) + .hasMessageContaining("自身"); + verify(moduleMapper, never()).updateById(any(Module.class)); + } + + @Test + void updateWithMissingParent_throws40021() { + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10)); + when(moduleMapper.existsActiveById(42)).thenReturn(false); + UpdateModuleDTO dto = baseUpdateDto(); + dto.setIParentId(42); + + assertThatThrownBy(() -> service.update(10, dto)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40021) + .hasMessageContaining("父模块不存在"); + verify(moduleMapper, never()).updateById(any(Module.class)); + } + + @Test + void updateWithCyclicParent_throws40021() { + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10)); + when(moduleMapper.existsActiveById(20)).thenReturn(true); + when(moduleMapper.selectParentIdById(20)).thenReturn(10); + UpdateModuleDTO dto = baseUpdateDto(); + dto.setIParentId(20); + + assertThatThrownBy(() -> service.update(10, dto)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40021) + .hasMessageContaining("环路"); + verify(moduleMapper, never()).updateById(any(Module.class)); + } + private UpdateModuleDTO baseUpdateDto() { UpdateModuleDTO dto = new UpdateModuleDTO(); dto.setSDisplayType("手机端"); -- libgit2 0.22.2