Commit 81daeb9660e6641cc547c245eb689f4a75a51052

Authored by zichun
1 parent 8f457010

feat(mod): module update parent validation REQ-MOD-002

backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java
@@ -21,6 +21,7 @@ import java.util.Set; @@ -21,6 +21,7 @@ import java.util.Set;
21 public class ModuleServiceImpl implements ModuleService { 21 public class ModuleServiceImpl implements ModuleService {
22 22
23 private static final Set<String> DISPLAY_TYPES = Set.of("手机端", "前端业务", "系统配置", "接口"); 23 private static final Set<String> DISPLAY_TYPES = Set.of("手机端", "前端业务", "系统配置", "接口");
  24 + private static final int MAX_PARENT_DEPTH = 50;
24 25
25 private final ModuleMapper moduleMapper; 26 private final ModuleMapper moduleMapper;
26 private final TenantProperties tenant; 27 private final TenantProperties tenant;
@@ -76,6 +77,7 @@ public class ModuleServiceImpl implements ModuleService { @@ -76,6 +77,7 @@ public class ModuleServiceImpl implements ModuleService {
76 if (!DISPLAY_TYPES.contains(dto.getSDisplayType())) { 77 if (!DISPLAY_TYPES.contains(dto.getSDisplayType())) {
77 throw new BizException(40010, "显示类型枚举不合法"); 78 throw new BizException(40010, "显示类型枚举不合法");
78 } 79 }
  80 + validateParent(id, dto.getIParentId());
79 81
80 Module entity = new Module(); 82 Module entity = new Module();
81 entity.setIIncrement(id); 83 entity.setIIncrement(id);
@@ -90,4 +92,26 @@ public class ModuleServiceImpl implements ModuleService { @@ -90,4 +92,26 @@ public class ModuleServiceImpl implements ModuleService {
90 moduleMapper.updateById(entity); 92 moduleMapper.updateById(entity);
91 return id; 93 return id;
92 } 94 }
  95 +
  96 + private void validateParent(Integer id, Integer parentId) {
  97 + if (parentId == null) {
  98 + return;
  99 + }
  100 + if (parentId.equals(id)) {
  101 + throw new BizException(40021, "父模块不能指向自身");
  102 + }
  103 + if (!moduleMapper.existsActiveById(parentId)) {
  104 + throw new BizException(40021, "父模块不存在或已删除");
  105 + }
  106 + Integer cur = moduleMapper.selectParentIdById(parentId);
  107 + for (int depth = 0; cur != null; depth++) {
  108 + if (cur.equals(id)) {
  109 + throw new BizException(40021, "父模块链构成环路");
  110 + }
  111 + if (depth >= MAX_PARENT_DEPTH) {
  112 + throw new BizException(40021, "父模块链超过最大层级");
  113 + }
  114 + cur = moduleMapper.selectParentIdById(cur);
  115 + }
  116 + }
93 } 117 }
backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java
@@ -184,6 +184,60 @@ class ModuleServiceImplTest { @@ -184,6 +184,60 @@ class ModuleServiceImplTest {
184 assertThat(captor.getValue().getBShowPermission()).isFalse(); 184 assertThat(captor.getValue().getBShowPermission()).isFalse();
185 } 185 }
186 186
  187 + @Test
  188 + void updateWithInvalidDisplayType_throws40010() {
  189 + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10));
  190 + UpdateModuleDTO dto = baseUpdateDto();
  191 + dto.setSDisplayType("未知");
  192 +
  193 + assertThatThrownBy(() -> service.update(10, dto))
  194 + .isInstanceOf(BizException.class)
  195 + .hasFieldOrPropertyWithValue("code", 40010);
  196 + verify(moduleMapper, never()).updateById(any(Module.class));
  197 + }
  198 +
  199 + @Test
  200 + void updateWithSelfParentId_throws40021() {
  201 + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10));
  202 + UpdateModuleDTO dto = baseUpdateDto();
  203 + dto.setIParentId(10);
  204 +
  205 + assertThatThrownBy(() -> service.update(10, dto))
  206 + .isInstanceOf(BizException.class)
  207 + .hasFieldOrPropertyWithValue("code", 40021)
  208 + .hasMessageContaining("自身");
  209 + verify(moduleMapper, never()).updateById(any(Module.class));
  210 + }
  211 +
  212 + @Test
  213 + void updateWithMissingParent_throws40021() {
  214 + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10));
  215 + when(moduleMapper.existsActiveById(42)).thenReturn(false);
  216 + UpdateModuleDTO dto = baseUpdateDto();
  217 + dto.setIParentId(42);
  218 +
  219 + assertThatThrownBy(() -> service.update(10, dto))
  220 + .isInstanceOf(BizException.class)
  221 + .hasFieldOrPropertyWithValue("code", 40021)
  222 + .hasMessageContaining("父模块不存在");
  223 + verify(moduleMapper, never()).updateById(any(Module.class));
  224 + }
  225 +
  226 + @Test
  227 + void updateWithCyclicParent_throws40021() {
  228 + when(moduleMapper.selectById(10)).thenReturn(stubExistingModule(10));
  229 + when(moduleMapper.existsActiveById(20)).thenReturn(true);
  230 + when(moduleMapper.selectParentIdById(20)).thenReturn(10);
  231 + UpdateModuleDTO dto = baseUpdateDto();
  232 + dto.setIParentId(20);
  233 +
  234 + assertThatThrownBy(() -> service.update(10, dto))
  235 + .isInstanceOf(BizException.class)
  236 + .hasFieldOrPropertyWithValue("code", 40021)
  237 + .hasMessageContaining("环路");
  238 + verify(moduleMapper, never()).updateById(any(Module.class));
  239 + }
  240 +
187 private UpdateModuleDTO baseUpdateDto() { 241 private UpdateModuleDTO baseUpdateDto() {
188 UpdateModuleDTO dto = new UpdateModuleDTO(); 242 UpdateModuleDTO dto = new UpdateModuleDTO();
189 dto.setSDisplayType("手机端"); 243 dto.setSDisplayType("手机端");