diff --git a/server/src/main/java/cn/nianxx/thhotel/platform/access/service/impl/AdminAccessManagementServiceImpl.java b/server/src/main/java/cn/nianxx/thhotel/platform/access/service/impl/AdminAccessManagementServiceImpl.java index 8b02c60..39d5fb8 100644 --- a/server/src/main/java/cn/nianxx/thhotel/platform/access/service/impl/AdminAccessManagementServiceImpl.java +++ b/server/src/main/java/cn/nianxx/thhotel/platform/access/service/impl/AdminAccessManagementServiceImpl.java @@ -1,5 +1,6 @@ package cn.nianxx.thhotel.platform.access.service.impl; +import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus; import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus; import cn.nianxx.thhotel.platform.access.common.request.AdminRoleCreateRequest; import cn.nianxx.thhotel.platform.access.common.request.AdminRolePermissionAssignmentRequest; @@ -315,12 +316,13 @@ public class AdminAccessManagementServiceImpl implements AdminAccessManagementSe if (permissionIds == null || permissionIds.isEmpty()) { return; } - Set existingPermissionIds = accessRepository.listAllPermissions().stream() + Set activePermissionIds = accessRepository.listAllPermissions().stream() + .filter(permission -> PlatformPermissionStatus.ACTIVE.name().equals(permission.getPermissionStatus())) .map(PlatformPermissionEntity::getId) .filter(Objects::nonNull) .collect(Collectors.toSet()); - if (!existingPermissionIds.containsAll(permissionIds)) { - throw invalidRequest("存在不存在的权限 ID。"); + if (!activePermissionIds.containsAll(permissionIds)) { + throw invalidRequest("存在不存在或未启用的权限 ID。"); } } diff --git a/server/src/main/java/cn/nianxx/thhotel/platform/identity/service/impl/AdminUserManagementServiceImpl.java b/server/src/main/java/cn/nianxx/thhotel/platform/identity/service/impl/AdminUserManagementServiceImpl.java index 5e6d859..9d20de6 100644 --- a/server/src/main/java/cn/nianxx/thhotel/platform/identity/service/impl/AdminUserManagementServiceImpl.java +++ b/server/src/main/java/cn/nianxx/thhotel/platform/identity/service/impl/AdminUserManagementServiceImpl.java @@ -1,5 +1,6 @@ package cn.nianxx.thhotel.platform.identity.service.impl; +import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus; import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformUserRoleEntity; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; @@ -479,11 +480,12 @@ public class AdminUserManagementServiceImpl implements AdminUserManagementServic if (roleIds == null || roleIds.isEmpty()) { return; } - Set existingRoleIds = accessRepository.listRolesByIds(roleIds).stream() + Set activeRoleIds = accessRepository.listRolesByIds(roleIds).stream() + .filter(role -> PlatformRoleStatus.ACTIVE.name().equals(role.getRoleStatus())) .map(PlatformRoleEntity::getId) .collect(Collectors.toSet()); - if (!existingRoleIds.containsAll(roleIds)) { - throw invalidRequest("存在不存在的角色 ID。"); + if (!activeRoleIds.containsAll(roleIds)) { + throw invalidRequest("存在不存在或未启用的角色 ID。"); } } diff --git a/server/src/main/java/cn/nianxx/thhotel/platform/navigation/service/impl/AdminMenuManagementServiceImpl.java b/server/src/main/java/cn/nianxx/thhotel/platform/navigation/service/impl/AdminMenuManagementServiceImpl.java index 1e3e227..1284cd5 100644 --- a/server/src/main/java/cn/nianxx/thhotel/platform/navigation/service/impl/AdminMenuManagementServiceImpl.java +++ b/server/src/main/java/cn/nianxx/thhotel/platform/navigation/service/impl/AdminMenuManagementServiceImpl.java @@ -1,5 +1,6 @@ package cn.nianxx.thhotel.platform.navigation.service.impl; +import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; import cn.nianxx.thhotel.platform.audit.common.dto.PlatformAdminAuditLogDraft; import cn.nianxx.thhotel.platform.audit.service.PlatformAdminAuditLogService; @@ -44,7 +45,8 @@ public class AdminMenuManagementServiceImpl implements AdminMenuManagementServic "/system/users", "/system/roles", "/system/menus", - "/system/hotels"); + "/system/hotels", + "/system/audits"); private final PlatformNavigationRepository navigationRepository; private final PlatformAccessRepository accessRepository; @@ -231,8 +233,10 @@ public class AdminMenuManagementServiceImpl implements AdminMenuManagementServic menu.setIconKey(trimToNull(iconKey)); String normalizedPermissionCode = trimToNull(permissionCode); if (normalizedPermissionCode != null - && accessRepository.findPermissionByCode(normalizedPermissionCode).isEmpty()) { - throw invalidRequest("菜单绑定的权限码不存在。"); + && accessRepository.findPermissionByCode(normalizedPermissionCode) + .filter(permission -> PlatformPermissionStatus.ACTIVE.name().equals(permission.getPermissionStatus())) + .isEmpty()) { + throw invalidRequest("菜单绑定的权限码不存在或未启用。"); } menu.setPermissionCode(normalizedPermissionCode); menu.setSortOrder(sortOrder == null ? 0 : sortOrder); diff --git a/server/src/test/java/cn/nianxx/thhotel/platform/system/control/AdminReadonlyControllerTest.java b/server/src/test/java/cn/nianxx/thhotel/platform/system/control/AdminReadonlyControllerTest.java index 57e7399..2d9e81b 100644 --- a/server/src/test/java/cn/nianxx/thhotel/platform/system/control/AdminReadonlyControllerTest.java +++ b/server/src/test/java/cn/nianxx/thhotel/platform/system/control/AdminReadonlyControllerTest.java @@ -9,7 +9,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import cn.nianxx.thhotel.ThHotelApplication; +import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionStatus; import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleCode; +import cn.nianxx.thhotel.platform.access.common.enums.PlatformRoleStatus; import cn.nianxx.thhotel.platform.access.domain.PlatformPermissionEntity; import cn.nianxx.thhotel.platform.access.domain.PlatformRoleEntity; import cn.nianxx.thhotel.platform.access.repository.PlatformAccessRepository; @@ -264,6 +266,58 @@ class AdminReadonlyControllerTest { .andExpect(status().isUnauthorized()); } + @Test + void shouldRejectDisabledRoleAssignmentToUser() throws Exception { + String token = tokenFrom(login("m006-admin", "Admin@123456")); + PlatformRoleEntity viewerRole = accessRepository.findRoleByCode(PlatformRoleCode.RESERVATION_VIEWER.name()) + .orElseThrow(); + String username = "m006-role-user-" + System.nanoTime(); + MvcResult createUserResult = mockMvc.perform(post("/api/admin/users") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "username": "%s", + "initial_password": "User@123456", + "display_name": "角色校验用户", + "user_status": "ACTIVE", + "role_ids": ["%s"], + "hotel_ids": ["HOTEL-TEST"], + "default_hotel_id": "HOTEL-TEST" + } + """.formatted(username, viewerRole.getId()))) + .andExpect(status().isOk()) + .andReturn(); + String userId = objectMapper.readTree(createUserResult.getResponse().getContentAsString()).path("id").asText(); + + MvcResult disabledRoleResult = mockMvc.perform(post("/api/admin/roles") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "role_code": "%s", + "role_name": "M006 停用角色", + "role_status": "DISABLED" + } + """.formatted("M006_DISABLED_ROLE_" + System.nanoTime()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.role_status").value(PlatformRoleStatus.DISABLED.name())) + .andReturn(); + String disabledRoleId = objectMapper.readTree(disabledRoleResult.getResponse().getContentAsString()) + .path("id").asText(); + + mockMvc.perform(put("/api/admin/users/{userId}/roles", userId) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "role_ids": ["%s"] + } + """.formatted(disabledRoleId))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST")); + } + @Test void shouldManageCustomRoleAndRejectBuiltinRoleMutation() throws Exception { String token = tokenFrom(login("m006-admin", "Admin@123456")); @@ -297,6 +351,18 @@ class AdminReadonlyControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.permissions[0].permission_code").value("SYSTEM_USER_MANAGE")); + PlatformPermissionEntity disabledPermission = createDisabledPermission("M006_DISABLED_PERMISSION_"); + mockMvc.perform(put("/api/admin/roles/{roleId}/permissions", roleId) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "permission_ids": ["%s"] + } + """.formatted(disabledPermission.getId()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST")); + PlatformRoleEntity builtinRole = accessRepository.findRoleByCode(PlatformRoleCode.SYSTEM_ADMIN.name()) .orElseThrow(); mockMvc.perform(put("/api/admin/roles/{roleId}", builtinRole.getId().toString()) @@ -344,7 +410,7 @@ class AdminReadonlyControllerTest { { "menu_name": "未来菜单更新", "menu_type": "PAGE", - "route_path": "/system/users", + "route_path": "/system/audits", "permission_code": "SYSTEM_USER_MANAGE", "visible": true, "menu_status": "ACTIVE" @@ -353,6 +419,25 @@ class AdminReadonlyControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.menu_code").value(menuCode)) .andExpect(jsonPath("$.known_route").value(true)); + + PlatformPermissionEntity disabledPermission = createDisabledPermission("M006_MENU_DISABLED_PERMISSION_"); + mockMvc.perform(post("/api/admin/menus") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "menu_code": "%s", + "menu_name": "停用权限菜单", + "menu_type": "PAGE", + "route_path": "/system/users", + "permission_code": "%s", + "visible": true, + "menu_status": "ACTIVE" + } + """.formatted("M006_MENU_DISABLED_" + System.nanoTime(), + disabledPermission.getPermissionCode()))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error_code").value("ADMIN_INVALID_REQUEST")); } @Test @@ -415,4 +500,18 @@ class AdminReadonlyControllerTest { JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString()); return json.path("access_token").asText(); } + + private PlatformPermissionEntity createDisabledPermission(String codePrefix) { + LocalDateTime now = LocalDateTime.now(); + PlatformPermissionEntity permission = new PlatformPermissionEntity(); + permission.setPermissionCode(codePrefix + System.nanoTime()); + permission.setPermissionName("M006 停用权限"); + permission.setPermissionGroup("SYSTEM"); + permission.setPermissionStatus(PlatformPermissionStatus.DISABLED.name()); + permission.setSystemBuiltin(false); + permission.setCreatedAt(now); + permission.setUpdatedAt(now); + accessRepository.insertPermission(permission); + return permission; + } }