Commit 81daeb9660e6641cc547c245eb689f4a75a51052
1 parent
8f457010
feat(mod): module update parent validation REQ-MOD-002
Showing
2 changed files
with
78 additions
and
0 deletions
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("手机端"); |