From 39ac73dc668c03b354661856cf24ba9b02001d88 Mon Sep 17 00:00:00 2001 From: Timi Date: Sat, 8 Aug 2026 09:48:00 +0800 Subject: [PATCH] refactor permission system --- .../api/annotation/RequireCoreRole.java | 29 -- .../RequireCoreRoleInterceptor.java | 46 -- .../imyeyu/api/bean/CorePermissionCode.java | 6 +- .../com/imyeyu/api/bean/CoreRoleCode.java | 5 +- .../java/com/imyeyu/api/config/WebConfig.java | 3 - .../controller/MultilingualController.java | 6 - .../implement/AttachmentServiceImplement.java | 3 + .../modules/gao/bean/GaoPermissionCode.java | 49 +- .../api/modules/gao/bean/GaoRoleCode.java | 5 +- .../gao/controller/GaoCustomerController.java | 3 - .../gao/controller/GaoEventController.java | 3 - .../controller/GaoEventRecordController.java | 3 - .../gao/controller/GaoRoleController.java | 8 +- .../gao/controller/GaoStoreController.java | 8 +- .../gao/controller/GaoUserController.java | 1 - .../GaoCustomerServiceImplement.java | 3 + .../implement/GaoResourceScopeResolver.java | 113 +++++ .../implement/GaoStoreServiceImplement.java | 3 + .../implement/LoggerServiceImplement.java | 29 +- .../api/modules/user/bean/AuthOwnerType.java | 23 + .../user/bean/BuiltinPermissionCode.java | 21 + .../modules/user/bean/BuiltinRoleCode.java | 14 + .../user/controller/PermissionController.java | 21 +- .../user/controller/RoleController.java | 115 ++++- .../user/controller/UserController.java | 8 +- .../api/modules/user/entity/Permission.java | 16 + .../imyeyu/api/modules/user/entity/Role.java | 31 ++ .../user/entity/RolePermissionDelegation.java | 45 ++ ...lation.java => RolePermissionInherit.java} | 32 +- .../modules/user/mapper/PermissionMapper.java | 6 +- .../api/modules/user/mapper/RoleMapper.java | 11 +- .../RolePermissionDelegationMapper.java | 27 ++ .../mapper/RolePermissionInheritMapper.java | 32 ++ .../user/mapper/RolePermissionMapper.java | 9 +- .../user/mapper/RoleRelationMapper.java | 25 - .../modules/user/mapper/UserRoleMapper.java | 2 +- .../service/AuthorizationBoundaryService.java | 79 ++++ .../service/AuthorizationScopeService.java | 42 ++ .../service/ModuleResourceScopeResolver.java | 39 ++ .../user/service/PermissionService.java | 7 + .../user/service/RoleHierarchyService.java | 39 ++ .../service/RolePermissionInheritService.java | 31 ++ .../user/service/RoleRelationService.java | 31 -- .../api/modules/user/service/RoleService.java | 46 ++ ...AuthorizationBoundaryServiceImplement.java | 378 +++++++++++++++ .../AuthorizationScopeServiceImplement.java | 178 +++++++ .../implement/PermissionServiceImplement.java | 16 + .../RoleHierarchyServiceImplement.java | 109 +++++ ...olePermissionInheritServiceImplement.java} | 20 +- .../implement/RoleServiceImplement.java | 437 ++++++++++++++++-- .../implement/UserRoleServiceImplement.java | 21 +- .../com/imyeyu/api/util/InitBuiltinUser.java | 151 +++++- ...V26__add_authorization_boundary_fields.sql | 39 ++ .../V27__add_role_permission_delegation.sql | 11 + ...cate_permission_inherit_and_delegation.sql | 40 ++ .../mapper/timi-server/PermissionMapper.xml | 30 ++ .../mapper/timi-server/RoleMapper.xml | 31 ++ .../RolePermissionDelegationMapper.xml | 53 +++ ...er.xml => RolePermissionInheritMapper.xml} | 41 +- .../timi-server/RolePermissionMapper.xml | 2 +- 60 files changed, 2300 insertions(+), 335 deletions(-) delete mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java delete mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoResourceScopeResolver.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/AuthOwnerType.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionDelegation.java rename src/main/java/com/imyeyu/api/modules/user/entity/{RoleRelation.java => RolePermissionInherit.java} (53%) create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionDelegationMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionInheritMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/AuthorizationBoundaryService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/AuthorizationScopeService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/ModuleResourceScopeResolver.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RoleHierarchyService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RolePermissionInheritService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationScopeServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/RoleHierarchyServiceImplement.java rename src/main/java/com/imyeyu/api/modules/user/service/implement/{RoleRelationServiceImplement.java => RolePermissionInheritServiceImplement.java} (68%) create mode 100644 src/main/resources/db/migration/timiserver/V26__add_authorization_boundary_fields.sql create mode 100644 src/main/resources/db/migration/timiserver/V27__add_role_permission_delegation.sql create mode 100644 src/main/resources/db/migration/timiserver/V28__deduplicate_permission_inherit_and_delegation.sql create mode 100644 src/main/resources/mapper/timi-server/PermissionMapper.xml create mode 100644 src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml rename src/main/resources/mapper/timi-server/{RoleRelationMapper.xml => RolePermissionInheritMapper.xml} (53%) diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java deleted file mode 100644 index 6e3da68..0000000 --- a/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.imyeyu.api.annotation; - -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.bean.CoreRoleCode; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/// -/// 要求 CORE 模块角色 -/// -/// @author Codex -/// @since 2026-07-26 -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface RequireCoreRole { - - /// 所需角色 - /// - /// @return 角色列表 - CoreRoleCode[] value(); - - /// 匹配模式 - /// - /// @return 匹配模式 - RoleMatchMode mode() default RoleMatchMode.ALL; -} diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java deleted file mode 100644 index 3342560..0000000 --- a/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.imyeyu.api.annotation; - -import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; -import com.imyeyu.api.bean.CoreRoleCode; -import com.imyeyu.api.modules.user.service.RoleChecker; -import com.imyeyu.java.TimiJava; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerInterceptor; - -/// -/// CORE 角色注解处理器 -/// -/// @author Codex -/// @since 2026-07-27 -@Component -public class RequireCoreRoleInterceptor implements HandlerInterceptor { - - private final RoleChecker roleChecker; - - public RequireCoreRoleInterceptor(RoleChecker roleChecker) { - this.roleChecker = roleChecker; - } - - @Override - public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { - if (!(handler instanceof HandlerMethod handlerMethod)) { - return true; - } - RequireCoreRole annotation = handlerMethod.getMethodAnnotation(RequireCoreRole.class); - if (annotation == null || TimiJava.isEmpty(annotation.value())) { - return true; - } - String[] roleCodeList = java.util.Arrays.stream(annotation.value()).map(CoreRoleCode::name).toArray(String[]::new); - if (annotation.mode() == RoleMatchMode.ANY) { - roleChecker.checkAny(ModuleCode.CORE, roleCodeList); - } else { - roleChecker.checkAll(ModuleCode.CORE, roleCodeList); - } - return true; - } -} diff --git a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java index 2de1037..fba6068 100644 --- a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java +++ b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java @@ -25,9 +25,9 @@ public enum CorePermissionCode implements BuiltinPermissionCode { ROLE_PERMISSION_READ("ROLE_PERMISSION:READ", "角色权限读取", false, true), ROLE_PERMISSION_DELETE("ROLE_PERMISSION:DELETE", "角色权限删除", false, true), - ROLE_RELATION_CREATE("ROLE_RELATION:CREATE", "角色关联创建", false, true), - ROLE_RELATION_READ("ROLE_RELATION:READ", "角色关联读取", false, true), - ROLE_RELATION_DELETE("ROLE_RELATION:DELETE", "角色关联删除", false, true), + ROLE_PERMISSION_INHERIT_CREATE("ROLE_PERMISSION_INHERIT:CREATE", "角色权限继承创建", false, true), + ROLE_PERMISSION_INHERIT_READ("ROLE_PERMISSION_INHERIT:READ", "角色权限继承读取", false, true), + ROLE_PERMISSION_INHERIT_DELETE("ROLE_PERMISSION_INHERIT:DELETE", "角色权限继承删除", false, true), SETTING_CREATE("SETTING:CREATE", "设置创建", false, true), SETTING_READ("SETTING:READ", "设置读取", false, true), diff --git a/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java index 64aa870..7e246b9 100644 --- a/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java +++ b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java @@ -13,9 +13,7 @@ public enum CoreRoleCode implements BuiltinRoleCode { SYSTEM("系统", true, null), - ADMIN("管理员", false, SYSTEM.name()), - - USER_ROLE_ADMIN("授权管理", false, ADMIN.name()); + ADMIN("管理员", false, SYSTEM.name()); final String defaultName; final boolean protectedRole; @@ -40,4 +38,5 @@ public enum CoreRoleCode implements BuiltinRoleCode { public String getParentCode() { return parentCode; } + } diff --git a/src/main/java/com/imyeyu/api/config/WebConfig.java b/src/main/java/com/imyeyu/api/config/WebConfig.java index d457568..6be5503 100644 --- a/src/main/java/com/imyeyu/api/config/WebConfig.java +++ b/src/main/java/com/imyeyu/api/config/WebConfig.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.imyeyu.api.annotation.EnableSettingInterceptor; import com.imyeyu.api.annotation.RequestRateLimitInterceptor; import com.imyeyu.api.annotation.RequireCorePermissionInterceptor; -import com.imyeyu.api.annotation.RequireCoreRoleInterceptor; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor; import com.imyeyu.api.modules.gao.annotation.RequireGaoRoleInterceptor; import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor; @@ -44,7 +43,6 @@ public class WebConfig implements WebMvcConfigurer { private final JournalAPIInterceptor journalAPIInterceptor; private final EnableSettingInterceptor enableSettingInterceptor; private final RequireGaoRoleInterceptor requireGaoRoleInterceptor; - private final RequireCoreRoleInterceptor requireCoreRoleInterceptor; private final RequestRateLimitInterceptor requestRateLimitInterceptor; private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor; private final RequireCorePermissionInterceptor requireCorePermissionInterceptor; @@ -59,7 +57,6 @@ public class WebConfig implements WebMvcConfigurer { registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH); registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH); registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**"); - registry.addInterceptor(requireCoreRoleInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireGaoRoleInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**"); diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java index db002a3..1fb0994 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java @@ -2,9 +2,7 @@ package com.imyeyu.api.modules.common.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.annotation.RequireCorePermission; -import com.imyeyu.api.annotation.RequireCoreRole; import com.imyeyu.api.bean.CorePermissionCode; -import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.spring.annotation.AOPLog; @@ -40,7 +38,6 @@ public class MultilingualController { */ @JsonView(ResponseView.Admin.class) @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_READ) @PostMapping("/detail") public Multilingual detail(@RequestParam String id) { @@ -56,7 +53,6 @@ public class MultilingualController { @AOPLog @JsonView(ResponseView.Admin.class) @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_CREATE) @PostMapping("/create") public Multilingual create(@RequestBody Multilingual multilingual) { @@ -71,7 +67,6 @@ public class MultilingualController { */ @AOPLog @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_UPDATE) @PostMapping("/update") public void update(@RequestBody Multilingual multilingual) { @@ -85,7 +80,6 @@ public class MultilingualController { */ @AOPLog @RequestRateLimit - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.MULTILINGUAL_DELETE) @PostMapping("/delete") public void delete(@RequestParam String id) { diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java index f1b1bf5..701d4db 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java @@ -41,6 +41,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -347,6 +348,8 @@ public class AttachmentServiceImplement extends AbstractEntityService list) { + list = TimiJava.defaultIfNull(list, new ArrayList<>()); + // 数据库现有附件 List dbList = mapper.selectAllByBizId(bizType, bizId); Set dbIds = dbList.stream().map(Attachment::getId).collect(Collectors.toSet()); diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java index 845e349..06456a4 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -21,45 +21,45 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { STORE_DELETE("STORE:DELETE", "门店删除", false, true), - CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"), + CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建", false, false), - CUSTOMER_READ("CUSTOMER:READ", "客户读取"), + CUSTOMER_READ("CUSTOMER:READ", "客户读取", false, false), - CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true), + CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true, false), - CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改"), + CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改", false, false), - CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"), + CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除", false, false), - EVENT_CREATE("EVENT:CREATE", "登记事件创建"), + EVENT_CREATE("EVENT:CREATE", "登记事件创建", false, false), - EVENT_READ("EVENT:READ", "登记事件读取"), + EVENT_READ("EVENT:READ", "登记事件读取", false, false), - EVENT_UPDATE("EVENT:UPDATE", "登记事件修改"), + EVENT_UPDATE("EVENT:UPDATE", "登记事件修改", false, false), - EVENT_DELETE("EVENT:DELETE", "登记事件删除"), + EVENT_DELETE("EVENT:DELETE", "登记事件删除", false, false), - EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建"), + EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建", false, false), - EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取"), + EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取", false, false), - EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true), + EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true, false), - EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改"), + EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改", false, false), - EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除"), + EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除", false, false), - STAT_READ("STAT:READ", "统计读取"), + STAT_READ("STAT:READ", "统计读取", false, false), - QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建"), + QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建", false, false), - USER_CREATE("USER:CREATE", "用户创建", true), + USER_CREATE("USER:CREATE", "用户创建", true, false), - USER_READ("USER:READ", "用户读取", true), + USER_READ("USER:READ", "用户读取", true, false), - USER_UPDATE("USER:UPDATE", "用户修改", true), + USER_UPDATE("USER:UPDATE", "用户修改", true, false), - USER_DELETE("USER:DELETE", "用户删除", true); + USER_DELETE("USER:DELETE", "用户删除", true, false); final String value; final String defaultName; @@ -72,14 +72,6 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { @Getter final boolean grantToGlobalManager; - GaoPermissionCode(String value, String defaultName) { - this(value, defaultName, false, false); - } - - GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) { - this(value, defaultName, grantToStoreManager, false); - } - GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) { this.value = value; this.defaultName = defaultName; @@ -111,4 +103,5 @@ public enum GaoPermissionCode implements BuiltinPermissionCode { public boolean isGrantToAdmin() { return false; } + } diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java index 04e0b9b..3bd2282 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java @@ -16,9 +16,7 @@ public enum GaoRoleCode implements BuiltinRoleCode { GLOBAL_MANAGER("全局管理员", null), /// 店长 - STORE_MANAGER("店长", GLOBAL_MANAGER.name()), - - EMPLOYEE("员工", STORE_MANAGER.name()); + STORE_MANAGER("店长", GLOBAL_MANAGER.name()); final String defaultName; final String parentCode; @@ -42,4 +40,5 @@ public enum GaoRoleCode implements BuiltinRoleCode { public String getParentCode() { return parentCode; } + } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java index 21a7452..e36ffbc 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java @@ -2,11 +2,9 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.entity.GaoCustomer; @@ -46,7 +44,6 @@ import java.util.Map; /// @author 夜雨 /// @since 2026-07-27 14:12 @RestController -@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) @RequiredArgsConstructor @RequestMapping("/gao/customer") public class GaoCustomerController { diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java index cdd4456..72234c3 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java @@ -1,9 +1,7 @@ package com.imyeyu.api.modules.gao.controller; import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.entity.GaoEvent; @@ -30,7 +28,6 @@ import java.util.Map; /// @author 夜雨 /// @since 2026-07-27 14:13 @RestController -@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) @RequiredArgsConstructor @RequestMapping("/gao/event") public class GaoEventController { diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java index 571ff8e..2a97e6c 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventRecordController.java @@ -2,11 +2,9 @@ package com.imyeyu.api.modules.gao.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; -import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.entity.GaoCustomer; @@ -48,7 +46,6 @@ import java.util.Map; /// @author 夜雨 /// @since 2026-07-27 14:11 @RestController -@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) @RequiredArgsConstructor @RequestMapping("/gao/event/record") public class GaoEventRecordController { diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java index cbc4d27..c9a1839 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java @@ -4,7 +4,8 @@ import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.user.entity.Role; -import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; import lombok.RequiredArgsConstructor; @@ -24,12 +25,13 @@ import java.util.List; @RequestMapping("/gao/role") public class GaoRoleController { - private final RoleService roleService; + private final UserLoginService userLoginService; + private final AuthorizationScopeService authorizationScopeService; @AOPLog @RequestRateLimit @PostMapping("/available/list") public List availableList() { - return roleService.listByModuleCode(ModuleCode.GAO); + return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO); } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java index 05ddbb9..21b0d7e 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java @@ -1,6 +1,5 @@ package com.imyeyu.api.modules.gao.controller; -import com.imyeyu.api.bean.RoleMatchMode; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; @@ -34,13 +33,13 @@ import java.util.List; @RestController @RequiredArgsConstructor @RequestMapping("/gao/store") -@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) public class GaoStoreController { private final GaoStoreService service; private final UserLoginService userLoginService; @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.STORE_READ) @PostMapping("/list") @@ -49,6 +48,7 @@ public class GaoStoreController { } @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.STORE_READ) @PostMapping("/detail") @@ -66,6 +66,7 @@ public class GaoStoreController { } @AOPLog + @RequireGaoRole(GaoRoleCode.STORE_MANAGER) @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) @PostMapping("/update") @@ -74,6 +75,7 @@ public class GaoStoreController { } @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.STORE_DELETE) @PostMapping("/delete") @@ -82,6 +84,7 @@ public class GaoStoreController { } @AOPLog + @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER) @RequestRateLimit @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) @PostMapping("/sort") @@ -104,7 +107,6 @@ public class GaoStoreController { @AOPLog @RequestRateLimit - @RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) @PostMapping("/user") public GaoStore getLoginStore() { return service.getByUserId(userLoginService.getRequireLoginUserId()); diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java index 13f26b8..0452932 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java @@ -39,7 +39,6 @@ import java.util.Map; /// @author 夜雨 /// @since 2026-07-30 14:15 @RestController -@RequireGaoRole(GaoRoleCode.STORE_MANAGER) @RequiredArgsConstructor @RequestMapping("/gao/user") public class GaoUserController { diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index 66cf3e5..8b258ee 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -65,6 +65,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList(); + if (TimiJava.isEmpty(roleIdList)) { + return false; + } + // 身份角色只依赖系统、模块级继承,不能依赖当前正在计算的门店作用域 + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(roleIdList); + return roleMapper.selectByIdList(allRoleIdList).stream().anyMatch(role -> ModuleCode.GAO == role.getModuleCode() && roleCode.name().equals(role.getCode())); + } + + private boolean hasCoreRole(String userId, CoreRoleCode roleCode) { + if (TimiJava.isEmpty(userId)) { + return false; + } + List roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList(); + if (TimiJava.isEmpty(roleIdList)) { + return false; + } + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(roleIdList); + return roleMapper.selectByIdList(allRoleIdList).stream().anyMatch(role -> ModuleCode.CORE == role.getModuleCode() && roleCode.name().equals(role.getCode())); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java index 4c52c6f..6c1dbb1 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java @@ -50,6 +50,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService implements LoggerService { @@ -31,19 +36,13 @@ public class LoggerServiceImplement extends AbstractEntityService createSync(logger)); } @Override public void createSync(Logger logger) { - // TODO 异步请求上下文已丢失 -// if (TimiJava.isEmpty(logger.getUserId())) { -// User loginUser = userLoginService.getLoginUser(); -// if (TimiJava.isNotEmpty(loginUser)) { -// logger.setUserId(loginUser.getId()); -// } -// } -// logger.setIp(TimiJava.defaultIfEmpty(logger.getIp(), TimiSpring.getRequestIP())); + fillContext(logger); super.create(logger); } @@ -53,4 +52,18 @@ public class LoggerServiceImplement extends AbstractEntityService list(@RequestBody Page page) { return service.page(page); } + @RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ) + @PostMapping("/grantable/list") + public List listGrantable(@RequestParam ModuleCode moduleCode) { + return authorizationScopeService.listGrantablePermission(userLoginService.getRequireLoginUserId(), moduleCode); + } + @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.PERMISSION_READ) @PostMapping("/create") public void create(@RequestBody Permission permission) { @@ -49,7 +57,6 @@ public class PermissionController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.PERMISSION_CREATE) @PostMapping("/create/batch") public void createBatch(@RequestBody BatchCreateReq req) { @@ -57,7 +64,6 @@ public class PermissionController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.PERMISSION_UPDATE) @PostMapping("/update") public void update(@RequestBody Permission permission) { @@ -65,7 +71,6 @@ public class PermissionController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.PERMISSION_DELETE) @PostMapping("/delete") public void delete(@RequestParam("id") String id) { diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java index cfe1425..d0f09f6 100644 --- a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java +++ b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java @@ -1,18 +1,19 @@ package com.imyeyu.api.modules.user.controller; import com.imyeyu.api.annotation.RequireCorePermission; -import com.imyeyu.api.annotation.RequireCoreRole; import com.imyeyu.api.bean.CorePermissionCode; -import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.bean.ModuleCode; -import com.imyeyu.api.bean.RoleMatchMode; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.service.PermissionService; import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserRoleService; import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequiredToken; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; import lombok.RequiredArgsConstructor; @@ -23,7 +24,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * 角色 SDK 控制器 @@ -39,30 +43,52 @@ public class RoleController { private final RoleService service; private final UserRoleService userRoleService; + private final UserLoginService userLoginService; private final PermissionService permissionService; - @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(CorePermissionCode.ROLE_READ) @PostMapping("/list") public PageResult list(@RequestBody Page page) { + Set manageableRoleIdSet = listManageableRoleId(); PageResult result = service.page(page); if (result.getList() != null) { + result.setList(result.getList().stream().filter(role -> manageableRoleIdSet.contains(role.getId())).toList()); result.getList().forEach(this::fillRoleDetail); } return result; } - @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) + @RequireCorePermission(CorePermissionCode.ROLE_READ) + @PostMapping("/manageable/list") + public List listManageableRole(@RequestParam ModuleCode moduleCode) { + return service.listManageableRole(moduleCode); + } + + @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) + @PostMapping("/grantable/list") + public List listGrantableRole(@RequestParam ModuleCode moduleCode) { + return service.listGrantableRole(moduleCode); + } + + @RequiredToken + @PostMapping("/current/detail/list") + public List listCurrentRoleDetail(@RequestParam(required = false) ModuleCode moduleCode) { + String loginUserId = userLoginService.getRequireLoginUserId(); + List roleList = moduleCode == null ? userRoleService.listRoleByUserId(loginUserId) : userRoleService.listRoleByUserId(moduleCode, loginUserId); + roleList.forEach(this::fillRoleDetail); + return roleList; + } + @RequireCorePermission(CorePermissionCode.ROLE_READ) @PostMapping("/detail") public Role detail(@RequestParam String id) { Role role = service.get(id); + requireManageableRole(role.getId()); fillRoleDetail(role); return role; } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.ROLE_CREATE) @PostMapping("/create") public void create(@RequestBody Role role) { @@ -70,49 +96,110 @@ public class RoleController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.ROLE_UPDATE) @PostMapping("/update") public void update(@RequestBody Role role) { service.update(role); } + @RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ) + @PostMapping("/permission/grantable/list") + public List listGrantableRolePermission(@RequestParam String roleId) { + requireManageableRole(roleId); + return service.listGrantableRolePermission(roleId); + } + + @RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ) + @PostMapping("/delegation/list") + public List listDelegationPermission(@RequestParam String roleId) { + requireManageableRole(roleId); + return service.listDelegationPermissionByRoleId(roleId); + } + + @RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ) + @PostMapping("/delegation/grantable/list") + public List listGrantableDelegationPermission(@RequestParam String roleId) { + requireManageableRole(roleId); + return service.listGrantableDelegationPermission(roleId); + } + + @AOPLog + @RequireCorePermission(value = {CorePermissionCode.ROLE_PERMISSION_CREATE, CorePermissionCode.ROLE_PERMISSION_DELETE}) + @PostMapping("/delegation/update") + public void updateRoleDelegation(@RequestBody Role role) { + service.updateRoleDelegation(role); + } + + @RequireCorePermission(CorePermissionCode.ROLE_READ) + @PostMapping("/parent/grantable/list") + public List listGrantableParentRole(@RequestParam String roleId) { + requireManageableRole(roleId); + return service.listGrantableParentRole(roleId); + } + + @AOPLog + @RequireCorePermission(CorePermissionCode.ROLE_UPDATE) + @PostMapping("/parent/update") + public void updateRoleParent(@RequestBody Role role) { + service.updateRoleParent(role); + } + @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequireCorePermission(CorePermissionCode.ROLE_DELETE) @PostMapping("/delete") public void delete(@RequestParam String id) { service.delete(id); } - @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) @PostMapping("/authorized/list") public List listUserRole(@RequestParam(required = false) ModuleCode moduleCode, @RequestParam String userId) { + Set manageableRoleIdSet = listManageableRoleId(); if (moduleCode != null) { - return userRoleService.listRoleByUserId(moduleCode, userId); + return userRoleService.listRoleByUserId(moduleCode, userId).stream().filter(role -> manageableRoleIdSet.contains(role.getId())).toList(); } - return userRoleService.listRoleByUserId(userId); + return userRoleService.listRoleByUserId(userId).stream().filter(role -> manageableRoleIdSet.contains(role.getId())).toList(); } @AOPLog - @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(value = {CorePermissionCode.USER_ROLE_CREATE, CorePermissionCode.USER_ROLE_DELETE}) @PostMapping("/authorized/create") public void authorizeUserRole(@RequestBody UserRoleAuthorizeRequest req) { userRoleService.authorizeUserRole(req); } - @RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY) @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) @PostMapping("/authorized/permission") public List listAuthorizedPermission(@RequestParam String userId) { - return userRoleService.listAllPermissionByUserId(userId); + Set manageableRoleIdSet = listManageableRoleId(); + List roleIdList = userRoleService.listRoleByUserId(userId) + .stream() + .filter(role -> manageableRoleIdSet.contains(role.getId())) + .map(Role::getId) + .toList(); + return roleIdList.isEmpty() ? List.of() : permissionService.listByRoleId(service.listAllRoleIdIncludingChildren(roleIdList)); } private void fillRoleDetail(Role role) { role.setPermissionList(permissionService.listByRoleId(role.getId())); role.setAllPermissionList(service.listPermissionByRoleId(role.getId())); role.setChildRoleList(service.listChildByParentRoleId(role.getId())); + if (role.getParentRoleId() != null) { + role.setParentRole(service.get(role.getParentRoleId())); + } + role.setDelegationPermissionList(service.listDelegationPermissionByRoleId(role.getId())); + } + + private Set listManageableRoleId() { + return Arrays.stream(ModuleCode.values()) + .flatMap(moduleCode -> service.listManageableRole(moduleCode).stream()) + .map(Role::getId) + .collect(Collectors.toSet()); + } + + private void requireManageableRole(String roleId) { + if (!listManageableRoleId().contains(roleId)) { + throw new TimiException(TimiCode.PERMISSION_ERROR); + } } } diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java index 014ce9a..a38226e 100644 --- a/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java +++ b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java @@ -2,9 +2,7 @@ package com.imyeyu.api.modules.user.controller; import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.annotation.RequireCorePermission; -import com.imyeyu.api.annotation.RequireCoreRole; import com.imyeyu.api.bean.CorePermissionCode; -import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.user.entity.User; @@ -56,7 +54,6 @@ public class UserController { private final UserLoginService userLoginService; private final AttachmentService attachmentService; - @RequireCoreRole(CoreRoleCode.ADMIN) @RequestRateLimit @RequireCorePermission(CorePermissionCode.USER_READ) @PostMapping("/list") @@ -65,7 +62,6 @@ public class UserController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequestRateLimit @RequireCorePermission(CorePermissionCode.USER_READ) @RequestMapping("/detail") @@ -74,7 +70,6 @@ public class UserController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequestRateLimit @RequireCorePermission(CorePermissionCode.USER_CREATE) @PostMapping("/create") @@ -83,7 +78,6 @@ public class UserController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequestRateLimit @RequireCorePermission(CorePermissionCode.USER_UPDATE) @PostMapping("/update") @@ -92,7 +86,6 @@ public class UserController { } @AOPLog - @RequireCoreRole(CoreRoleCode.ADMIN) @RequestRateLimit @RequestMapping("/delete") public void delete(@RequestParam String id) { @@ -125,6 +118,7 @@ public class UserController { @AOPLog @JsonView(ResponseView.Public.class) @RequestRateLimit + @CaptchaValid @PostMapping("/login") public LoginResponse login(@RequestBody @Valid CaptchaData req) { return userLoginService.login(req.getData()); diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java b/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java index f7935f0..5079827 100644 --- a/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java +++ b/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java @@ -2,6 +2,7 @@ package com.imyeyu.api.modules.user.entity; import com.imyeyu.api.annotation.MultilingualField; import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; @@ -29,6 +30,21 @@ public class Permission extends UUIDEntity { /** 说明 */ private String description; + /// true 为系统内置权限 + private Boolean builtin; + + /// true 为受保护权限 + private Boolean protectedPermission; + + /// 归属类型 + private AuthOwnerType ownerType; + + /// 归属 ID + private String ownerId; + + /// 创建人用户 ID + private String createdBy; + @Transient @MultilingualField("nameLangId") private String name; diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/Role.java b/src/main/java/com/imyeyu/api/modules/user/entity/Role.java index abc40a1..bdfa982 100644 --- a/src/main/java/com/imyeyu/api/modules/user/entity/Role.java +++ b/src/main/java/com/imyeyu/api/modules/user/entity/Role.java @@ -2,6 +2,7 @@ package com.imyeyu.api.modules.user.entity; import com.imyeyu.api.annotation.MultilingualField; import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; @@ -32,6 +33,24 @@ public class Role extends UUIDEntity { /** 说明 */ private String description; + /// true 为系统内置角色 + private Boolean builtin; + + /// true 为受保护角色 + private Boolean protectedRole; + + /// 管理父角色 ID + private String parentRoleId; + + /// 归属类型 + private AuthOwnerType ownerType; + + /// 归属 ID + private String ownerId; + + /// 创建人用户 ID + private String createdBy; + @Transient @MultilingualField("nameLangId") private String name; @@ -48,6 +67,14 @@ public class Role extends UUIDEntity { @Transient protected List childRoleList; + /// 管理父角色 + @Transient + protected Role parentRole; + + /// 角色允许继续转授的权限列表 + @Transient + protected List delegationPermissionList; + /** 权限 ID 列表 */ @Transient protected Set permissionIdList; @@ -55,4 +82,8 @@ public class Role extends UUIDEntity { /** 子角色 ID 列表 */ @Transient protected Set childRoleIdList; + + /// 转授权限 ID 列表 + @Transient + protected Set delegationPermissionIdList; } diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionDelegation.java b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionDelegation.java new file mode 100644 index 0000000..141b5fa --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionDelegation.java @@ -0,0 +1,45 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/// 角色转授权限 +/// +/// @author Codex +/// @since 2026-08-08 +@Data +public class RolePermissionDelegation implements IDEntity, Creatable, Deletable { + + /// 主键 + @Id + @AutoUUID + protected String id; + + /// 角色 ID + protected String roleId; + + /// 权限 ID + protected String permissionId; + + /// 创建人用户 ID + protected String createdBy; + + /// 创建时间 + protected Long createdAt; + + /// 删除时间 + @DeleteColumn + protected Long deletedAt; + + @Transient + protected Role role; + + @Transient + protected Permission permission; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionInherit.java similarity index 53% rename from src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java rename to src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionInherit.java index 2073fa9..a61be41 100644 --- a/src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java +++ b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermissionInherit.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.user.entity; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; import com.imyeyu.spring.annotation.table.AutoUUID; import com.imyeyu.spring.annotation.table.DeleteColumn; import com.imyeyu.spring.annotation.table.Id; @@ -8,30 +9,37 @@ import com.imyeyu.spring.entity.Deletable; import com.imyeyu.spring.entity.IDEntity; import lombok.Data; -/** - * 角色关联 - * - * @author 夜雨 - * @since 2026-05-13 14:40 - */ +/// 角色权限继承 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:40 @Data -public class RoleRelation implements IDEntity, Creatable, Deletable { +public class RolePermissionInherit implements IDEntity, Creatable, Deletable { - /** 主键 */ + /// 主键 @Id @AutoUUID private String id; - /** 上级角色 ID */ + /// 继承方角色 ID private String parentRoleId; - /** 子级角色 ID */ + /// 被继承角色 ID private String childRoleId; - /** 创建时间 */ + /// 归属类型 + private AuthOwnerType ownerType; + + /// 归属 ID + private String ownerId; + + /// 创建人用户 ID + private String createdBy; + + /// 创建时间 private Long createdAt; - /** 删除时间 */ + /// 删除时间 @DeleteColumn private Long deletedAt; } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java index 769f38f..692f9c0 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java @@ -2,6 +2,8 @@ package com.imyeyu.api.modules.user.mapper; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.api.bean.ModuleCode; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -13,5 +15,7 @@ import java.util.List; */ public interface PermissionMapper extends BaseMapper { - List selectByIdList(List list); + List selectByIdList(@Param("idList") List idList); + + List selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode); } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java index 931b5ab..505f517 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java @@ -3,6 +3,7 @@ package com.imyeyu.api.modules.user.mapper; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; import java.util.Collection; import java.util.List; @@ -15,7 +16,13 @@ import java.util.List; */ public interface RoleMapper extends BaseMapper { - List selectByIdList(Collection idList); + Role selectById(String id); - List selectByModuleCode(ModuleCode moduleCode); + List selectByIdList(@Param("idList") Collection idList); + + List selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode); + + List selectByParentRoleId(@Param("parentRoleId") String parentRoleId); + + List selectAll(); } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionDelegationMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionDelegationMapper.java new file mode 100644 index 0000000..5dd8f19 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionDelegationMapper.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.RolePermissionDelegation; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// 角色转授权限 Mapper +/// +/// @author Codex +/// @since 2026-08-08 +public interface RolePermissionDelegationMapper extends BaseMapper { + + default List selectByRoleId(String roleId) { + return selectByRoleIdList(List.of(roleId)); + } + + List selectByRoleIdList(@Param("roleIdList") List roleIdList); + + List selectPermissionByRoleIdList(@Param("roleIdList") List roleIdList); + + void createBatch(@Param("rolePermissionDelegationList") List rolePermissionDelegationList); + + void deleteByPermissionIdList(@Param("roleId") String roleId, @Param("permissionIdList") List permissionIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionInheritMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionInheritMapper.java new file mode 100644 index 0000000..f51f429 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionInheritMapper.java @@ -0,0 +1,32 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.bean.AuthOwnerType; +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// 角色权限继承 Mapper +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface RolePermissionInheritMapper extends BaseMapper { + + List selectByParentRoleId(@Param("parentRoleId") String parentRoleId); + + List selectChildRoleIdListByParentRoleId(@Param("parentRoleId") String parentRoleId); + + List selectByParentRoleIdList(@Param("parentRoleIdList") List parentRoleIdList); + + List selectChildRoleIdListByParentRoleIdList(@Param("parentRoleIdList") List parentRoleIdList); + + void createBatch(@Param("rolePermissionInheritList") List rolePermissionInheritList); + + void deleteByChildRoleIdList( + @Param("parentRoleId") String parentRoleId, + @Param("childRoleIdList") List childRoleIdList, + @Param("ownerType") AuthOwnerType ownerType, + @Param("ownerId") String ownerId + ); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java index f9e28d0..94b0536 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java @@ -3,6 +3,7 @@ package com.imyeyu.api.modules.user.mapper; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.RolePermission; import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -18,11 +19,11 @@ public interface RolePermissionMapper extends BaseMapper return selectByRoleIdList(List.of(roleId)); } - List selectByRoleIdList(List roleIdList); + List selectByRoleIdList(@Param("roleIdList") List roleIdList); - List selectPermissionByRoleIdList(List roleIdList); + List selectPermissionByRoleIdList(@Param("roleIdList") List roleIdList); - void createBatch(List rolePermissionList); + void createBatch(@Param("rolePermissionList") List rolePermissionList); - void deleteByPermissionIdList(String roleId, List permissionIdList); + void deleteByPermissionIdList(@Param("roleId") String roleId, @Param("permissionIdList") List permissionIdList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java deleted file mode 100644 index 546f55b..0000000 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.user.mapper; - -import com.imyeyu.api.modules.user.entity.RoleRelation; -import com.imyeyu.spring.mapper.BaseMapper; - -import java.util.List; - -/** - * 角色关联 Mapper - * - * @author 夜雨 - * @since 2026-05-13 14:46 - */ -public interface RoleRelationMapper extends BaseMapper { - - List selectByParentRoleId(String parentRoleId); - - List selectChildRoleIdListByParentRoleId(String parentRoleId); - - List selectChildRoleIdListByParentRoleIdList(List parentRoleIdList); - - void createBatch(List roleRelationList); - - void deleteByChildRoleIdList(String parentRoleId, List childRoleIdList); -} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java index 9314e55..704c6ff 100644 --- a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java @@ -19,5 +19,5 @@ public interface UserRoleMapper extends BaseMapper { List selectByUserIdList(@Param("userIdList") Collection userIdList); - void deleteByRoleIdList(String userId, List roleIdList); + void deleteByRoleIdList(@Param("userId") String userId, @Param("roleIdList") List roleIdList); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationBoundaryService.java b/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationBoundaryService.java new file mode 100644 index 0000000..a02fb39 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationBoundaryService.java @@ -0,0 +1,79 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.Role; + +import java.util.Collection; + +/// 授权边界服务 +/// +/// @author Codex +/// @since 2026-08-07 +public interface AuthorizationBoundaryService { + + /// 校验是否可以授予用户角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param targetUserId 目标用户 ID + /// @param roleIdList 角色 ID 列表 + void checkCanGrantRoles(String operatorUserId, String targetUserId, Collection roleIdList); + + /// 校验是否可以移除用户角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param targetUserId 目标用户 ID + /// @param roleIdList 角色 ID 列表 + void checkCanRevokeRoles(String operatorUserId, String targetUserId, Collection roleIdList); + + /// 校验是否可以创建角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param role 角色 + void checkCanCreateRole(String operatorUserId, Role role); + + /// 校验是否可以修改角色基础信息 + /// + /// @param operatorUserId 操作者用户 ID + /// @param role 角色 + void checkCanUpdateRole(String operatorUserId, Role role); + + /// 校验是否可以删除角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param roleId 角色 ID + void checkCanDeleteRole(String operatorUserId, String roleId); + + /// 校验是否可以授予角色权限 + /// + /// @param operatorUserId 操作者用户 ID + /// @param roleId 角色 ID + /// @param permissionIdList 权限 ID 列表 + void checkCanGrantPermissions(String operatorUserId, String roleId, Collection permissionIdList); + + /// 校验是否可以移除角色权限 + /// + /// @param operatorUserId 操作者用户 ID + /// @param roleId 角色 ID + /// @param permissionIdList 权限 ID 列表 + void checkCanRevokePermissions(String operatorUserId, String roleId, Collection permissionIdList); + + /// 校验是否可以修改角色转授权限 + /// + /// @param operatorUserId 操作者用户 ID + /// @param roleId 角色 ID + /// @param permissionIdList 转授权限 ID 列表 + void checkCanUpdateRoleDelegation(String operatorUserId, String roleId, Collection permissionIdList); + + /// 校验是否可以更新角色管理父级 + /// + /// @param operatorUserId 操作者用户 ID + /// @param roleId 角色 ID + /// @param parentRoleId 管理父角色 ID + void checkCanUpdateRoleParent(String operatorUserId, String roleId, String parentRoleId); + + /// 校验是否可以更新角色继承关系 + /// + /// @param operatorUserId 操作者用户 ID + /// @param parentRoleId 父角色 ID + /// @param childRoleIdList 子角色 ID 列表 + void checkCanUpdateRolePermissionInherits(String operatorUserId, String parentRoleId, Collection childRoleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationScopeService.java b/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationScopeService.java new file mode 100644 index 0000000..614b3f5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/AuthorizationScopeService.java @@ -0,0 +1,42 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; + +import java.util.List; + +/// 授权范围服务 +/// +/// @author Codex +/// @since 2026-08-07 +public interface AuthorizationScopeService { + + /// 查询可授权角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listGrantableRole(String operatorUserId, ModuleCode moduleCode); + + /// 查询可授权权限 + /// + /// @param operatorUserId 操作者用户 ID + /// @param moduleCode 模块代码 + /// @return 权限列表 + List listGrantablePermission(String operatorUserId, ModuleCode moduleCode); + + /// 查询可管理角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listManageableRole(String operatorUserId, ModuleCode moduleCode); + + /// 判断是否为系统管理员 + /// + /// @param operatorUserId 操作者用户 ID + /// @return true 为系统管理员 + boolean isSystemAdmin(String operatorUserId); + +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/ModuleResourceScopeResolver.java b/src/main/java/com/imyeyu/api/modules/user/service/ModuleResourceScopeResolver.java new file mode 100644 index 0000000..907be80 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/ModuleResourceScopeResolver.java @@ -0,0 +1,39 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; +import com.imyeyu.api.modules.user.entity.Role; + +/// 模块资源范围解析器 +/// +/// @author Codex +/// @since 2026-08-07 +public interface ModuleResourceScopeResolver { + + /// 模块代码 + /// + /// @return 模块代码 + ModuleCode moduleCode(); + + /// 判断是否可以管理目标用户 + /// + /// @param operatorUserId 操作者用户 ID + /// @param targetUserId 目标用户 ID + /// @return true 为可以管理 + boolean canManageUser(String operatorUserId, String targetUserId); + + /// 判断是否可以管理角色 + /// + /// @param operatorUserId 操作者用户 ID + /// @param role 角色 + /// @return true 为可以管理 + boolean canManageRole(String operatorUserId, Role role); + + /// 判断是否可以使用指定归属范围 + /// + /// @param operatorUserId 操作者用户 ID + /// @param ownerType 归属类型 + /// @param ownerId 归属 ID + /// @return true 为可以使用 + boolean canUseOwnerScope(String operatorUserId, AuthOwnerType ownerType, String ownerId); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java b/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java index 3dac03e..e35d792 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java @@ -2,6 +2,7 @@ package com.imyeyu.api.modules.user.service; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq; +import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.spring.service.BaseService; import java.util.List; @@ -30,4 +31,10 @@ public interface PermissionService extends BaseService { /// @param roleIdList 角色 ID 列表 /// @return 权限列表 List listByRoleId(List roleIdList); + + /// 查询模块权限 + /// + /// @param moduleCode 模块代码 + /// @return 权限列表 + List listByModuleCode(ModuleCode moduleCode); } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleHierarchyService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleHierarchyService.java new file mode 100644 index 0000000..b076674 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleHierarchyService.java @@ -0,0 +1,39 @@ +package com.imyeyu.api.modules.user.service; + +import java.util.Collection; +import java.util.List; + +/// 角色继承服务 +/// +/// @author Codex +/// @since 2026-08-07 +public interface RoleHierarchyService { + + /// 递归查询角色及其系统、模块作用域内的所有子角色 ID + /// + /// 此方法不依赖用户资源范围,不会展开门店、用户等需要操作者上下文的作用域关系 + /// + /// @param roleIdList 角色 ID 列表 + /// @return 角色 ID 列表 + List listAllRoleIdIncludingChildren(Collection roleIdList); + + /// 递归查询用户作用域内的角色及其所有子角色 ID + /// + /// @param userId 用户 ID + /// @param roleIdList 角色 ID 列表 + /// @return 角色 ID 列表 + List listAllRoleIdIncludingChildren(String userId, Collection roleIdList); + + /// 校验角色继承关系不存在环 + /// + /// @param parentRoleId 父角色 ID + /// @param childRoleIdList 子角色 ID 列表 + void checkNoCycle(String parentRoleId, Collection childRoleIdList); + + /// 判断目标角色是否为子孙角色 + /// + /// @param parentRoleId 父角色 ID + /// @param childRoleId 子角色 ID + /// @return true 为子孙角色 + boolean isDescendant(String parentRoleId, String childRoleId); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RolePermissionInheritService.java b/src/main/java/com/imyeyu/api/modules/user/service/RolePermissionInheritService.java new file mode 100644 index 0000000..a68614a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/RolePermissionInheritService.java @@ -0,0 +1,31 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// 角色权限继承服务 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface RolePermissionInheritService extends BaseService { + + /// 查询直属权限继承 + /// + /// @param parentRoleId 继承方角色 ID + /// @return 权限继承列表 + List listByParentRoleId(String parentRoleId); + + /// 查询直属被继承角色 ID 列表 + /// + /// @param parentRoleId 继承方角色 ID + /// @return 被继承角色 ID 列表 + List listChildRoleIdByParentRoleId(String parentRoleId); + + /// 递归展开全部被继承角色 ID + /// + /// @param parentRoleIdList 继承方角色 ID 列表 + /// @return 全部被继承角色 ID 列表 + List listAllChildRoleIdByParentRoleIdList(List parentRoleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java deleted file mode 100644 index 199adba..0000000 --- a/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.imyeyu.api.modules.user.service; - -import com.imyeyu.api.modules.user.entity.RoleRelation; -import com.imyeyu.spring.service.BaseService; - -import java.util.List; - -/// 角色继承关系服务 -/// -/// @author 夜雨 -/// @since 2026-05-13 14:46 -public interface RoleRelationService extends BaseService { - - /// 查询直属子角色关联 - /// - /// @param parentRoleId 父角色 ID - /// @return 角色关联列表 - List listByParentRoleId(String parentRoleId); - - /// 查询直属子角色 ID 列表 - /// - /// @param parentRoleId 父角色 ID - /// @return 子角色 ID 列表 - List listChildRoleIdByParentRoleId(String parentRoleId); - - /// 递归展开全部子角色 ID - /// - /// @param parentRoleIdList 父角色 ID 列表 - /// @return 全部子角色 ID 列表 - List listAllChildRoleIdByParentRoleIdList(List parentRoleIdList); -} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java index 06bf481..f3c5332 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java @@ -22,6 +22,18 @@ public interface RoleService extends BaseService { List listByIdList(Collection idList); + /// 查询当前用户可管理的模块角色 + /// + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listManageableRole(ModuleCode moduleCode); + + /// 查询当前用户可授权给账号的模块角色 + /// + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listGrantableRole(ModuleCode moduleCode); + /// 查询直属子角色 /// /// @param parentRoleId 父角色 ID @@ -34,6 +46,40 @@ public interface RoleService extends BaseService { /// @return 权限列表 List listPermissionByRoleId(String roleId); + /// 查询可配置给目标角色的直接权限 + /// + /// @param roleId 角色 ID + /// @return 权限列表 + List listGrantableRolePermission(String roleId); + + /// 查询角色转授权限 + /// + /// @param roleId 角色 ID + /// @return 权限列表 + List listDelegationPermissionByRoleId(String roleId); + + /// 查询可配置给目标角色的转授权限 + /// + /// @param roleId 角色 ID + /// @return 权限列表 + List listGrantableDelegationPermission(String roleId); + + /// 修改角色转授权限 + /// + /// @param role 角色 + void updateRoleDelegation(Role role); + + /// 查询可配置给目标角色的管理父级角色 + /// + /// @param roleId 角色 ID + /// @return 角色列表 + List listGrantableParentRole(String roleId); + + /// 修改角色管理父级 + /// + /// @param role 角色 + void updateRoleParent(Role role); + /// 递归获取角色及其所有子角色的 ID 列表 /// /// @param idList 角色 ID 列表 diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java new file mode 100644 index 0000000..e0c947c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationBoundaryServiceImplement.java @@ -0,0 +1,378 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.PermissionMapper; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionDelegationMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; +import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.AuthorizationBoundaryService; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.ModuleResourceScopeResolver; +import com.imyeyu.api.modules.user.service.RoleHierarchyService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// 授权边界服务实现 +/// +/// @author Codex +/// @since 2026-08-07 +@Service +@RequiredArgsConstructor +public class AuthorizationBoundaryServiceImplement implements AuthorizationBoundaryService { + + private final RoleMapper roleMapper; + private final UserRoleMapper userRoleMapper; + private final PermissionMapper permissionMapper; + private final RolePermissionMapper rolePermissionMapper; + private final RolePermissionDelegationMapper rolePermissionDelegationMapper; + private final AuthorizationScopeService authorizationScopeService; + private final RoleHierarchyService roleHierarchyService; + private final List moduleResourceScopeResolverList; + + @Override + public void checkCanGrantRoles(String operatorUserId, String targetUserId, Collection roleIdList) { + if (TimiJava.isEmpty(roleIdList)) { + return; + } + checkCorePermission(operatorUserId, CorePermissionCode.USER_ROLE_CREATE); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + List roleList = roleMapper.selectByIdList(roleIdList); + checkRoleIdListComplete(roleIdList, roleList); + for (Role role : roleList) { + checkCanManageUserInModule(operatorUserId, targetUserId, role.getModuleCode()); + checkGrantableRole(operatorUserId, role); + } + } + + @Override + public void checkCanRevokeRoles(String operatorUserId, String targetUserId, Collection roleIdList) { + if (TimiJava.isEmpty(roleIdList)) { + return; + } + checkCorePermission(operatorUserId, CorePermissionCode.USER_ROLE_DELETE); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + List roleList = roleMapper.selectByIdList(roleIdList); + checkRoleIdListComplete(roleIdList, roleList); + for (Role role : roleList) { + checkCanManageUserInModule(operatorUserId, targetUserId, role.getModuleCode()); + checkManageableRole(operatorUserId, role); + } + } + + @Override + public void checkCanCreateRole(String operatorUserId, Role role) { + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_CREATE); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + TimiException.required(role.getModuleCode(), "not found role.moduleCode"); + TimiException.required(role.getOwnerType(), "not found role.ownerType"); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getBuiltin()), "不能创建内置角色"); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getProtectedRole()), "不能创建受保护角色"); + checkOwnerScope(operatorUserId, role.getModuleCode(), role.getOwnerType(), role.getOwnerId()); + } + + @Override + public void checkCanUpdateRole(String operatorUserId, Role role) { + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_UPDATE); + Role dbRole = roleMapper.selectById(role.getId()); + TimiException.required(dbRole, "not found role"); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + checkManageableRole(operatorUserId, dbRole); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getBuiltin()), "不能设置内置角色"); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getProtectedRole()), "不能设置受保护角色"); + if (role.getModuleCode() != null) { + TimiException.requiredTrue(role.getModuleCode() == dbRole.getModuleCode(), "不能修改角色模块"); + } + if (role.getOwnerType() != null) { + checkOwnerScope(operatorUserId, dbRole.getModuleCode(), role.getOwnerType(), role.getOwnerId()); + } + } + + @Override + public void checkCanDeleteRole(String operatorUserId, String roleId) { + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_DELETE); + Role role = roleMapper.selectById(roleId); + TimiException.required(role, "not found role"); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + checkManageableRole(operatorUserId, role); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getBuiltin()), "不能删除内置角色"); + TimiException.requiredTrue(!Boolean.TRUE.equals(role.getProtectedRole()), "不能删除受保护角色"); + } + + @Override + public void checkCanGrantPermissions(String operatorUserId, String roleId, Collection permissionIdList) { + if (TimiJava.isEmpty(permissionIdList)) { + return; + } + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_PERMISSION_CREATE); + Role role = roleMapper.selectById(roleId); + TimiException.required(role, "not found role"); + boolean systemAdmin = authorizationScopeService.isSystemAdmin(operatorUserId); + if (!systemAdmin) { + checkManageableRole(operatorUserId, role); + } + List permissionList = permissionMapper.selectByIdList(permissionIdList.stream().toList()); + checkPermissionIdListComplete(permissionIdList, permissionList); + Set grantablePermissionIdSet = listRoleDirectGrantablePermissionId(operatorUserId, role); + for (Permission permission : permissionList) { + TimiException.requiredTrue(grantablePermissionIdSet.contains(permission.getId()), "权限不在上级下放边界内:%s".formatted(permission.getCode())); + } + } + + @Override + public void checkCanRevokePermissions(String operatorUserId, String roleId, Collection permissionIdList) { + if (TimiJava.isEmpty(permissionIdList)) { + return; + } + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_PERMISSION_DELETE); + Role role = roleMapper.selectById(roleId); + TimiException.required(role, "not found role"); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + checkManageableRole(operatorUserId, role); + } + + @Override + public void checkCanUpdateRoleDelegation(String operatorUserId, String roleId, Collection permissionIdList) { + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_PERMISSION_CREATE); + Role role = roleMapper.selectById(roleId); + TimiException.required(role, "not found role"); + boolean systemAdmin = authorizationScopeService.isSystemAdmin(operatorUserId); + if (!systemAdmin) { + checkManageableRole(operatorUserId, role); + } + if (TimiJava.isEmpty(permissionIdList)) { + return; + } + List permissionList = permissionMapper.selectByIdList(permissionIdList.stream().toList()); + checkPermissionIdListComplete(permissionIdList, permissionList); + Set allPermissionIdSet = listRoleAllPermissionId(operatorUserId, roleId); + boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId()); + Set parentDelegationPermissionIdSet = listParentDelegationPermissionId(roleId); + if (!isCoreDelegationRoot(role)) { + TimiException.requiredTrue(hasParentRole, "目标角色缺少管理父级"); + } + Set operatorGrantablePermissionIdSet = listGrantablePermissionId(operatorUserId, permissionList); + for (Permission permission : permissionList) { + TimiException.requiredTrue(role.getModuleCode() == permission.getModuleCode() || isCoreDelegationRoot(role) || isCoreAuthorizationPermission(permission), "不能配置其他模块转授权限"); + TimiException.requiredTrue(isCoreDelegationRoot(role) || allPermissionIdSet.contains(permission.getId()), "转授权限必须先属于目标角色有效权限:%s".formatted(permission.getCode())); + if (hasParentRole) { + TimiException.requiredTrue(parentDelegationPermissionIdSet.contains(permission.getId()), "转授权限超过上级边界:%s".formatted(permission.getCode())); + } + if (!systemAdmin) { + TimiException.requiredTrue(operatorGrantablePermissionIdSet.contains(permission.getId()), "权限不在可转授范围内:%s".formatted(permission.getCode())); + } + } + } + + @Override + public void checkCanUpdateRoleParent(String operatorUserId, String roleId, String parentRoleId) { + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_UPDATE); + Role targetRole = roleMapper.selectById(roleId); + TimiException.required(targetRole, "not found role"); + boolean systemAdmin = authorizationScopeService.isSystemAdmin(operatorUserId); + if (!systemAdmin) { + checkManageableRole(operatorUserId, targetRole); + } + if (TimiJava.isEmpty(parentRoleId)) { + TimiException.requiredTrue(isCoreDelegationRoot(targetRole), "非根角色必须设置管理父级"); + return; + } + Role parentRole = roleMapper.selectById(parentRoleId); + TimiException.required(parentRole, "not found parent role"); + TimiException.requiredTrue(!roleId.equals(parentRoleId), "管理父级不能指向自身"); + TimiException.requiredTrue(!isManagementDescendant(roleId, parentRoleId), "管理父级不能指向自己的下级"); + if (!systemAdmin && !isOperatorRole(operatorUserId, parentRole)) { + checkManageableRole(operatorUserId, parentRole); + } + } + + @Override + public void checkCanUpdateRolePermissionInherits(String operatorUserId, String parentRoleId, Collection childRoleIdList) { + if (TimiJava.isEmpty(childRoleIdList)) { + return; + } + checkCorePermission(operatorUserId, CorePermissionCode.ROLE_PERMISSION_INHERIT_CREATE); + Role parentRole = roleMapper.selectById(parentRoleId); + TimiException.required(parentRole, "not found parent role"); + roleHierarchyService.checkNoCycle(parentRoleId, childRoleIdList); + if (authorizationScopeService.isSystemAdmin(operatorUserId)) { + return; + } + if (!isOperatorRole(operatorUserId, parentRole)) { + checkManageableRole(operatorUserId, parentRole); + } + List childRoleList = roleMapper.selectByIdList(childRoleIdList); + checkRoleIdListComplete(childRoleIdList, childRoleList); + for (Role childRole : childRoleList) { + TimiException.requiredTrue(parentRole.getModuleCode() == childRole.getModuleCode(), "不能继承其他模块角色"); + checkManageableRole(operatorUserId, childRole); + } + } + + private void checkGrantableRole(String operatorUserId, Role role) { + Set grantableRoleIdSet = authorizationScopeService.listGrantableRole(operatorUserId, role.getModuleCode()) + .stream() + .map(Role::getId) + .collect(Collectors.toSet()); + TimiException.requiredTrue(grantableRoleIdSet.contains(role.getId()), "角色不在可授权范围内:%s".formatted(role.getCode())); + } + + private void checkManageableRole(String operatorUserId, Role role) { + Set manageableRoleIdSet = authorizationScopeService.listManageableRole(operatorUserId, role.getModuleCode()) + .stream() + .map(Role::getId) + .collect(Collectors.toSet()); + TimiException.requiredTrue(manageableRoleIdSet.contains(role.getId()), "角色不在可管理范围内:%s".formatted(role.getCode())); + } + + private Set listRoleAllPermissionId(String operatorUserId, String roleId) { + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, List.of(roleId)); + return rolePermissionMapper.selectPermissionByRoleIdList(allRoleIdList) + .stream() + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private Set listGrantablePermissionId(String operatorUserId, List permissionList) { + if (TimiJava.isEmpty(permissionList)) { + return Set.of(); + } + return permissionList.stream() + .map(Permission::getModuleCode) + .distinct() + .flatMap(moduleCode -> authorizationScopeService.listGrantablePermission(operatorUserId, moduleCode).stream()) + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private Set listRoleDirectGrantablePermissionId(String operatorUserId, Role role) { + boolean systemAdmin = authorizationScopeService.isSystemAdmin(operatorUserId); + if (systemAdmin && isCoreDelegationRoot(role) && TimiJava.isEmpty(role.getParentRoleId())) { + return permissionMapper.selectAll().stream().map(Permission::getId).collect(Collectors.toSet()); + } + TimiException.requiredTrue(TimiJava.isNotEmpty(role.getParentRoleId()), "目标角色缺少管理父级"); + Set parentDelegationPermissionIdSet = listParentDelegationPermissionId(role.getId()); + if (systemAdmin) { + return parentDelegationPermissionIdSet; + } + List parentDelegationPermissionList = TimiJava.isEmpty(parentDelegationPermissionIdSet) + ? List.of() + : permissionMapper.selectByIdList(parentDelegationPermissionIdSet.stream().toList()); + Set operatorGrantablePermissionIdSet = listGrantablePermissionId(operatorUserId, parentDelegationPermissionList); + return parentDelegationPermissionIdSet.stream() + .filter(operatorGrantablePermissionIdSet::contains) + .collect(Collectors.toSet()); + } + + private Set listParentDelegationPermissionId(String roleId) { + Role role = roleMapper.selectById(roleId); + if (role == null || TimiJava.isEmpty(role.getParentRoleId())) { + return Set.of(); + } + return rolePermissionDelegationMapper.selectPermissionByRoleIdList(List.of(role.getParentRoleId())) + .stream() + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private boolean isOperatorRole(String operatorUserId, Role role) { + List roleIdList = userRoleMapper.selectByUserIdList(List.of(operatorUserId)).stream().map(UserRole::getRoleId).toList(); + if (TimiJava.isEmpty(roleIdList)) { + return false; + } + return roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, roleIdList).contains(role.getId()); + } + + private boolean isCoreDelegationRoot(Role role) { + return role != null + && ModuleCode.CORE == role.getModuleCode() + && (CoreRoleCode.SYSTEM.name().equals(role.getCode()) || CoreRoleCode.ADMIN.name().equals(role.getCode())); + } + + private boolean isManagementDescendant(String parentRoleId, String childRoleId) { + List childRoleList = roleMapper.selectByParentRoleId(parentRoleId); + for (Role childRole : childRoleList) { + if (childRoleId.equals(childRole.getId()) || isManagementDescendant(childRole.getId(), childRoleId)) { + return true; + } + } + return false; + } + + private boolean isCoreAuthorizationPermission(Permission permission) { + return permission != null && ModuleCode.CORE == permission.getModuleCode(); + } + + private void checkCanManageUserInModule(String operatorUserId, String targetUserId, ModuleCode moduleCode) { + ModuleResourceScopeResolver resolver = getResolver(moduleCode); + TimiException.requiredTrue(resolver == null || resolver.canManageUser(operatorUserId, targetUserId), "目标用户不在可管理范围内"); + } + + private void checkOwnerScope(String operatorUserId, ModuleCode moduleCode, AuthOwnerType ownerType, String ownerId) { + ModuleResourceScopeResolver resolver = getResolver(moduleCode); + TimiException.requiredTrue(resolver == null || resolver.canUseOwnerScope(operatorUserId, ownerType, ownerId), "归属范围不在可管理范围内"); + } + + private void checkCorePermission(String operatorUserId, CorePermissionCode permissionCode) { + TimiException.requiredTrue(hasPermission(operatorUserId, ModuleCode.CORE, permissionCode.getValue()), "权限不足:%s".formatted(permissionCode.getValue())); + } + + private boolean hasPermission(String userId, ModuleCode moduleCode, String permissionCode) { + List roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList(); + if (TimiJava.isEmpty(roleIdList)) { + return false; + } + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userId, roleIdList); + return rolePermissionMapper.selectPermissionByRoleIdList(allRoleIdList) + .stream() + .anyMatch(permission -> moduleCode == permission.getModuleCode() && permissionCode.equals(permission.getCode())); + } + + private ModuleResourceScopeResolver getResolver(ModuleCode moduleCode) { + return moduleResourceScopeResolverList.stream() + .filter(resolver -> moduleCode == resolver.moduleCode()) + .findFirst() + .orElse(null); + } + + private void checkRoleIdListComplete(Collection idList, List roleList) { + Set foundIdSet = roleList.stream().map(Role::getId).collect(Collectors.toSet()); + Set idSet = new HashSet<>(idList); + TimiException.requiredTrue(foundIdSet.containsAll(idSet), "存在无效角色 ID"); + } + + private void checkPermissionIdListComplete(Collection idList, List permissionList) { + Set foundIdSet = permissionList.stream().map(Permission::getId).collect(Collectors.toSet()); + Set idSet = new HashSet<>(idList); + TimiException.requiredTrue(foundIdSet.containsAll(idSet), "存在无效权限 ID"); + } + +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationScopeServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationScopeServiceImplement.java new file mode 100644 index 0000000..069b51c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/AuthorizationScopeServiceImplement.java @@ -0,0 +1,178 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.PermissionMapper; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionDelegationMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; +import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.ModuleResourceScopeResolver; +import com.imyeyu.api.modules.user.service.RoleHierarchyService; +import com.imyeyu.java.TimiJava; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// 授权范围服务实现 +/// +/// @author Codex +/// @since 2026-08-07 +@Service +@RequiredArgsConstructor +public class AuthorizationScopeServiceImplement implements AuthorizationScopeService { + + private final RoleMapper roleMapper; + private final UserRoleMapper userRoleMapper; + private final PermissionMapper permissionMapper; + private final RolePermissionMapper rolePermissionMapper; + private final RolePermissionDelegationMapper rolePermissionDelegationMapper; + private final RoleHierarchyService roleHierarchyService; + private final List moduleResourceScopeResolverList; + + @Override + public List listGrantableRole(String operatorUserId, ModuleCode moduleCode) { + List roleList = listManageableRole(operatorUserId, moduleCode); + if (isSystemAdmin(operatorUserId)) { + return roleList; + } + Set operatorDelegationPermissionIdSet = listOperatorDelegationPermissionId(operatorUserId); + return roleList.stream() + .filter(role -> operatorDelegationPermissionIdSet.containsAll(listRoleAllPermissionId(operatorUserId, role.getId()))) + .toList(); + } + + @Override + public List listGrantablePermission(String operatorUserId, ModuleCode moduleCode) { + List permissionList = permissionMapper.selectByModuleCode(moduleCode); + if (isSystemAdmin(operatorUserId)) { + return permissionList; + } + Set delegationPermissionIdSet = listOperatorDelegationPermissionId(operatorUserId, moduleCode); + return permissionList.stream() + .filter(permission -> !Boolean.TRUE.equals(permission.getProtectedPermission())) + .filter(permission -> delegationPermissionIdSet.contains(permission.getId())) + .toList(); + } + + @Override + public List listManageableRole(String operatorUserId, ModuleCode moduleCode) { + List roleList = roleMapper.selectByModuleCode(moduleCode); + if (isSystemAdmin(operatorUserId)) { + return roleList; + } + Set manageableRoleIdSet = listManagementDescendantRoleId(operatorUserId); + ModuleResourceScopeResolver resolver = getResolver(moduleCode); + return roleList.stream() + .filter(role -> manageableRoleIdSet.contains(role.getId())) + .filter(role -> !Boolean.TRUE.equals(role.getProtectedRole())) + .filter(role -> resolver == null || resolver.canManageRole(operatorUserId, role)) + .toList(); + } + + @Override + public boolean isSystemAdmin(String operatorUserId) { + return listAllOperatorRole(operatorUserId).stream() + .anyMatch(role -> ModuleCode.CORE == role.getModuleCode() + && CoreRoleCode.SYSTEM.name().equals(role.getCode())); + } + + private Set listManagementDescendantRoleId(String operatorUserId) { + List operatorRoleIdList = listOperatorRoleId(operatorUserId); + if (TimiJava.isEmpty(operatorRoleIdList)) { + return Set.of(); + } + Map> childRoleMap = roleMapper.selectAll() + .stream() + .filter(role -> TimiJava.isNotEmpty(role.getParentRoleId())) + .collect(Collectors.groupingBy(Role::getParentRoleId)); + Set result = new HashSet<>(); + ArrayDeque queue = new ArrayDeque<>(operatorRoleIdList); + while (!queue.isEmpty()) { + String roleId = queue.removeFirst(); + for (Role childRole : childRoleMap.getOrDefault(roleId, List.of())) { + if (result.add(childRole.getId())) { + queue.addLast(childRole.getId()); + } + } + } + return result; + } + + private List listAllOperatorRole(String operatorUserId) { + List allRoleIdList = listAllOperatorRoleId(operatorUserId); + if (TimiJava.isEmpty(allRoleIdList)) { + return List.of(); + } + Map roleMap = roleMapper.selectByIdList(allRoleIdList).stream().collect(Collectors.toMap(Role::getId, Function.identity())); + return allRoleIdList.stream().map(roleMap::get).filter(java.util.Objects::nonNull).toList(); + } + + private List listAllOperatorRoleId(String operatorUserId) { + List roleIdList = listOperatorRoleId(operatorUserId); + if (TimiJava.isEmpty(roleIdList)) { + return List.of(); + } + return roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, roleIdList); + } + + private List listOperatorRoleId(String operatorUserId) { + if (TimiJava.isEmpty(operatorUserId)) { + return List.of(); + } + return userRoleMapper.selectByUserIdList(List.of(operatorUserId)).stream().map(UserRole::getRoleId).toList(); + } + + private Set listOperatorDelegationPermissionId(String operatorUserId, ModuleCode moduleCode) { + List roleIdList = listOperatorRoleId(operatorUserId); + if (TimiJava.isEmpty(roleIdList)) { + return Set.of(); + } + return rolePermissionDelegationMapper.selectPermissionByRoleIdList(roleIdList) + .stream() + .filter(permission -> moduleCode == permission.getModuleCode()) + .map(Permission::getId) + .collect(Collectors.toCollection(HashSet::new)); + } + + private Set listOperatorDelegationPermissionId(String operatorUserId) { + List roleIdList = listOperatorRoleId(operatorUserId); + if (TimiJava.isEmpty(roleIdList)) { + return Set.of(); + } + return rolePermissionDelegationMapper.selectPermissionByRoleIdList(roleIdList) + .stream() + .map(Permission::getId) + .collect(Collectors.toCollection(HashSet::new)); + } + + private Set listRoleAllPermissionId(String operatorUserId, String roleId) { + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, List.of(roleId)); + if (TimiJava.isEmpty(allRoleIdList)) { + return Set.of(); + } + return rolePermissionMapper.selectPermissionByRoleIdList(allRoleIdList) + .stream() + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private ModuleResourceScopeResolver getResolver(ModuleCode moduleCode) { + return moduleResourceScopeResolverList.stream() + .filter(resolver -> moduleCode == resolver.moduleCode()) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java index a80a34f..4651df5 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java @@ -1,8 +1,10 @@ package com.imyeyu.api.modules.user.service.implement; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.modules.common.mapper.MultilingualMapper; import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.mapper.PermissionMapper; import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; @@ -45,6 +47,7 @@ public class PermissionServiceImplement extends AbstractEntityService listByModuleCode(ModuleCode moduleCode) { + return mapper.selectByModuleCode(moduleCode); + } + + private void fillPermissionDefault(Permission permission) { + permission.setBuiltin(TimiJava.defaultIfNull(permission.getBuiltin(), false)); + permission.setProtectedPermission(TimiJava.defaultIfNull(permission.getProtectedPermission(), false)); + permission.setOwnerType(TimiJava.defaultIfNull(permission.getOwnerType(), AuthOwnerType.MODULE)); + permission.setOwnerId(TimiJava.defaultIfEmpty(permission.getOwnerId(), permission.getModuleCode() == null ? null : permission.getModuleCode().name())); + } } diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleHierarchyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleHierarchyServiceImplement.java new file mode 100644 index 0000000..22004df --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleHierarchyServiceImplement.java @@ -0,0 +1,109 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.modules.user.bean.AuthOwnerType; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionInheritMapper; +import com.imyeyu.api.modules.user.service.ModuleResourceScopeResolver; +import com.imyeyu.api.modules.user.service.RoleHierarchyService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import org.springframework.beans.factory.ObjectProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// 角色继承服务实现 +/// +/// @author Codex +/// @since 2026-08-07 +@Service +@RequiredArgsConstructor +public class RoleHierarchyServiceImplement implements RoleHierarchyService { + + private final RolePermissionInheritMapper rolePermissionInheritMapper; + private final RoleMapper roleMapper; + private final ObjectProvider moduleResourceScopeResolverProvider; + + @Override + public List listAllRoleIdIncludingChildren(Collection roleIdList) { + return listAllRoleIdIncludingChildren(null, roleIdList); + } + + @Override + public List listAllRoleIdIncludingChildren(String userId, Collection roleIdList) { + if (TimiJava.isEmpty(roleIdList)) { + return List.of(); + } + Set allRoleIdSet = new HashSet<>(roleIdList); + List currentLevelRoleIdList = new ArrayList<>(roleIdList); + while (TimiJava.isNotEmpty(currentLevelRoleIdList)) { + List relationList = rolePermissionInheritMapper.selectByParentRoleIdList(currentLevelRoleIdList); + List parentAndChildRoleIdList = relationList.stream() + .flatMap(relation -> java.util.stream.Stream.of(relation.getParentRoleId(), relation.getChildRoleId())) + .distinct() + .toList(); + Map roleMap = roleMapper.selectByIdList(parentAndChildRoleIdList).stream().collect(Collectors.toMap(Role::getId, Function.identity())); + List childRoleIdList = relationList.stream() + .filter(relation -> isVisible(userId, relation, roleMap.get(relation.getParentRoleId()))) + .map(RolePermissionInherit::getChildRoleId) + .toList(); + if (TimiJava.isEmpty(childRoleIdList)) { + break; + } + List newChildRoleIdList = childRoleIdList.stream().filter(id -> !allRoleIdSet.contains(id)).toList(); + if (TimiJava.isEmpty(newChildRoleIdList)) { + break; + } + allRoleIdSet.addAll(newChildRoleIdList); + currentLevelRoleIdList = newChildRoleIdList; + } + return new ArrayList<>(allRoleIdSet); + } + + @Override + public void checkNoCycle(String parentRoleId, Collection childRoleIdList) { + if (TimiJava.isEmpty(childRoleIdList)) { + return; + } + for (String childRoleId : childRoleIdList) { + if (parentRoleId.equals(childRoleId) || isDescendant(childRoleId, parentRoleId)) { + throw new TimiException(TimiCode.PERMISSION_ERROR, "角色继承关系不能形成循环"); + } + } + } + + @Override + public boolean isDescendant(String parentRoleId, String childRoleId) { + return listAllRoleIdIncludingChildren(List.of(parentRoleId)).contains(childRoleId); + } + + private boolean isVisible(String userId, RolePermissionInherit relation, Role parentRole) { + if (relation.getOwnerType() == null) { + return true; + } + if (relation.getOwnerType() == AuthOwnerType.MODULE && parentRole != null && Objects.equals(relation.getOwnerId(), parentRole.getModuleCode().name())) { + return true; + } + if (TimiJava.isEmpty(userId) || parentRole == null) { + return false; + } + ModuleResourceScopeResolver resolver = moduleResourceScopeResolverProvider.orderedStream() + .filter(item -> parentRole.getModuleCode() == item.moduleCode()) + .findFirst() + .orElse(null); + return resolver != null && resolver.canUseOwnerScope(userId, relation.getOwnerType(), relation.getOwnerId()); + } +} + diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RolePermissionInheritServiceImplement.java similarity index 68% rename from src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java rename to src/main/java/com/imyeyu/api/modules/user/service/implement/RolePermissionInheritServiceImplement.java index a12f818..5446125 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RolePermissionInheritServiceImplement.java @@ -1,8 +1,8 @@ package com.imyeyu.api.modules.user.service.implement; -import com.imyeyu.api.modules.user.entity.RoleRelation; -import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; -import com.imyeyu.api.modules.user.service.RoleRelationService; +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; +import com.imyeyu.api.modules.user.mapper.RolePermissionInheritMapper; +import com.imyeyu.api.modules.user.service.RolePermissionInheritService; import com.imyeyu.java.TimiJava; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; @@ -14,34 +14,34 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -/// 角色关联服务实现 +/// 角色权限继承服务实现 /// /// @author 夜雨 /// @since 2026-05-13 14:46 @Service @RequiredArgsConstructor -public class RoleRelationServiceImplement extends AbstractEntityService implements RoleRelationService { +public class RolePermissionInheritServiceImplement extends AbstractEntityService implements RolePermissionInheritService { - private final RoleRelationMapper mapper; + private final RolePermissionInheritMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } @Override - public List listByParentRoleId(String parentRoleId) { + public List listByParentRoleId(String parentRoleId) { if (TimiJava.isEmpty(parentRoleId)) { return List.of(); } - RoleRelation example = new RoleRelation(); + RolePermissionInherit example = new RolePermissionInherit(); example.setParentRoleId(parentRoleId); return mapper.selectAllByExample(example); } @Override public List listChildRoleIdByParentRoleId(String parentRoleId) { - return listByParentRoleId(parentRoleId).stream().map(RoleRelation::getChildRoleId).distinct().toList(); + return listByParentRoleId(parentRoleId).stream().map(RolePermissionInherit::getChildRoleId).distinct().toList(); } @Override diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java index 6569e88..94a25ac 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java @@ -3,15 +3,27 @@ package com.imyeyu.api.modules.user.service.implement; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.user.bean.AuthOwnerType; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.RolePermission; -import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.api.modules.user.entity.RolePermissionDelegation; +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; +import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionDelegationMapper; import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; -import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionInheritMapper; +import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.AuthorizationBoundaryService; +import com.imyeyu.api.modules.user.service.AuthorizationScopeService; +import com.imyeyu.api.modules.user.service.ModuleResourceScopeResolver; import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleHierarchyService; import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; @@ -22,11 +34,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; /// 角色服务实现 @@ -39,10 +56,18 @@ public class RoleServiceImplement extends AbstractEntityService im private final PermissionService permissionService; private final MultilingualService multilingualService; + private final LoggerService loggerService; + private final UserLoginService userLoginService; + private final RoleHierarchyService roleHierarchyService; + private final AuthorizationBoundaryService authorizationBoundaryService; + private final AuthorizationScopeService authorizationScopeService; + private final List moduleResourceScopeResolverList; private final RoleMapper mapper; - private final RoleRelationMapper roleRelationMapper; + private final RolePermissionInheritMapper rolePermissionInheritMapper; private final RolePermissionMapper rolePermissionMapper; + private final RolePermissionDelegationMapper rolePermissionDelegationMapper; + private final UserRoleMapper userRoleMapper; @Override protected BaseMapper mapper() { @@ -52,8 +77,20 @@ public class RoleServiceImplement extends AbstractEntityService im @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void create(Role role) { + fillRoleDefault(role); + if (!Boolean.TRUE.equals(role.getBuiltin())) { + String loginUserId = userLoginService.getRequireLoginUserId(); + role.setCreatedBy(TimiJava.defaultIfEmpty(role.getCreatedBy(), loginUserId)); + authorizationBoundaryService.checkCanCreateRole(loginUserId, role); + } role.setNameLangId(multilingualService.create(role.getName())); super.create(role); + if (!Boolean.TRUE.equals(role.getBuiltin())) { + String loginUserId = userLoginService.getRequireLoginUserId(); + authorizationBoundaryService.checkCanUpdateRoleParent(loginUserId, role.getId(), role.getParentRoleId()); + authorizationBoundaryService.checkCanGrantPermissions(loginUserId, role.getId(), role.getPermissionIdList()); + authorizationBoundaryService.checkCanUpdateRolePermissionInherits(loginUserId, role.getId(), role.getChildRoleIdList()); + } if (TimiJava.isNotEmpty(role.getPermissionIdList())) { List rolePermissionList = new ArrayList<>(); @@ -70,37 +107,51 @@ public class RoleServiceImplement extends AbstractEntityService im } if (TimiJava.isNotEmpty(role.getChildRoleIdList())) { - List roleRelationList = new ArrayList<>(); + List rolePermissionInheritList = new ArrayList<>(); long now = Time.now(); + Map childRoleMap = mapper.selectByIdList(role.getChildRoleIdList()).stream().collect(Collectors.toMap(Role::getId, Function.identity())); for (String childRoleId : role.getChildRoleIdList()) { - RoleRelation roleRelation = new RoleRelation(); - roleRelation.setId(UUID.randomUUID().toString()); - roleRelation.setParentRoleId(role.getId()); - roleRelation.setChildRoleId(childRoleId); - roleRelation.setCreatedAt(now); - roleRelationList.add(roleRelation); + RolePermissionInherit rolePermissionInherit = new RolePermissionInherit(); + rolePermissionInherit.setId(UUID.randomUUID().toString()); + rolePermissionInherit.setParentRoleId(role.getId()); + rolePermissionInherit.setChildRoleId(childRoleId); + fillRelationScope(rolePermissionInherit, childRoleMap.get(childRoleId), role.getCreatedBy()); + rolePermissionInherit.setCreatedAt(now); + rolePermissionInheritList.add(rolePermissionInherit); } - roleRelationMapper.createBatch(roleRelationList); + rolePermissionInheritMapper.createBatch(rolePermissionInheritList); } } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void update(Role role) { - role.setNameLangId(multilingualService.createIfDifferent(role.getNameLangId(), role.getName())); - mapper.updateSelective(role); - { + String loginUserId = userLoginService.getRequireLoginUserId(); + Role dbRole = mapper.selectById(role.getId()); + if (dbRole != null && Boolean.TRUE.equals(dbRole.getBuiltin())) { + // 内建角色允许修改名称和说明,但角色代码必须保持不变。 + role.setCode(null); + } + if (hasRoleBaseField(role)) { + authorizationBoundaryService.checkCanUpdateRole(loginUserId, role); + role.setNameLangId(multilingualService.createIfDifferent(role.getNameLangId(), role.getName())); + mapper.updateSelective(role); + } + if (role.getPermissionIdList() != null) { Set dbIdSet = rolePermissionMapper.selectByRoleId(role.getId()).stream().map(RolePermission::getPermissionId).collect(Collectors.toSet()); - Set newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getPermissionIdList()), new HashSet<>()); + Set newIdSet = TimiJava.isEmpty(role.getPermissionIdList()) ? new HashSet<>() : new HashSet<>(role.getPermissionIdList()); Set deleteIdList = new HashSet<>(dbIdSet); deleteIdList.removeAll(newIdSet); Set addIdList = new HashSet<>(newIdSet); addIdList.removeAll(dbIdSet); + authorizationBoundaryService.checkCanRevokePermissions(loginUserId, role.getId(), deleteIdList); + authorizationBoundaryService.checkCanGrantPermissions(loginUserId, role.getId(), addIdList); if (TimiJava.isNotEmpty(deleteIdList)) { rolePermissionMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); + rolePermissionDelegationMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); } if (TimiJava.isNotEmpty(addIdList)) { List rolePermissionList = new ArrayList<>(); @@ -115,37 +166,58 @@ public class RoleServiceImplement extends AbstractEntityService im } rolePermissionMapper.createBatch(rolePermissionList); } + logRoleAuthorizationChange("USER_ROLE_PERMISSION_UPDATE", loginUserId, role.getId(), addIdList, deleteIdList); } - { - Set dbIdSet = roleRelationMapper.selectByParentRoleId(role.getId()).stream().map(RoleRelation::getChildRoleId).collect(Collectors.toSet()); - Set newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getChildRoleIdList()), new HashSet<>()); + if (role.getChildRoleIdList() != null) { + Role parentRole = mapper.selectById(role.getId()); + List dbRelationList = rolePermissionInheritMapper.selectByParentRoleId(role.getId()); + List touchableRelationList = dbRelationList.stream() + .filter(relation -> canTouchRelation(loginUserId, parentRole, relation)) + .toList(); + Set newIdSet = TimiJava.isEmpty(role.getChildRoleIdList()) ? new HashSet<>() : new HashSet<>(role.getChildRoleIdList()); + Map childRoleMap = mapper.selectByIdList(newIdSet).stream().collect(Collectors.toMap(Role::getId, Function.identity())); + Set dbKeySet = dbRelationList.stream().map(this::relationKey).collect(Collectors.toSet()); + Set newKeySet = newIdSet.stream().map(childRoleId -> relationKey(childRoleId, childRoleMap.get(childRoleId))).collect(Collectors.toSet()); - Set deleteIdList = new HashSet<>(dbIdSet); - deleteIdList.removeAll(newIdSet); + Set deleteKeyList = new HashSet<>(dbKeySet); + deleteKeyList.removeAll(newKeySet); - Set addIdList = new HashSet<>(newIdSet); - addIdList.removeAll(dbIdSet); + Set addKeyList = new HashSet<>(newKeySet); + addKeyList.removeAll(dbKeySet); + Set addIdList = addKeyList.stream().map(key -> key.split("\\|", 2)[0]).collect(Collectors.toSet()); + authorizationBoundaryService.checkCanUpdateRolePermissionInherits(loginUserId, role.getId(), addIdList); - if (TimiJava.isNotEmpty(deleteIdList)) { - roleRelationMapper.deleteByChildRoleIdList(role.getId(), new ArrayList<>(deleteIdList)); + if (TimiJava.isNotEmpty(deleteKeyList)) { + for (RolePermissionInherit relation : touchableRelationList) { + if (deleteKeyList.contains(relationKey(relation))) { + rolePermissionInheritMapper.deleteByChildRoleIdList(role.getId(), List.of(relation.getChildRoleId()), relation.getOwnerType(), relation.getOwnerId()); + } + } } if (TimiJava.isNotEmpty(addIdList)) { - List roleRelationList = new ArrayList<>(); + List rolePermissionInheritList = new ArrayList<>(); long now = Time.now(); for (String childRoleId : addIdList) { - RoleRelation roleRelation = new RoleRelation(); - roleRelation.setId(UUID.randomUUID().toString()); - roleRelation.setParentRoleId(role.getId()); - roleRelation.setChildRoleId(childRoleId); - roleRelation.setCreatedAt(now); - roleRelationList.add(roleRelation); + RolePermissionInherit rolePermissionInherit = new RolePermissionInherit(); + rolePermissionInherit.setId(UUID.randomUUID().toString()); + rolePermissionInherit.setParentRoleId(role.getId()); + rolePermissionInherit.setChildRoleId(childRoleId); + fillRelationScope(rolePermissionInherit, childRoleMap.get(childRoleId), loginUserId); + rolePermissionInherit.setCreatedAt(now); + rolePermissionInheritList.add(rolePermissionInherit); } - roleRelationMapper.createBatch(roleRelationList); + rolePermissionInheritMapper.createBatch(rolePermissionInheritList); } } } + @Override + public void delete(String id) { + authorizationBoundaryService.checkCanDeleteRole(userLoginService.getRequireLoginUserId(), id); + super.delete(id); + } + @Override public List listByModuleCode(ModuleCode moduleCode) { TimiException.required(moduleCode, "not found moduleCode"); @@ -157,9 +229,25 @@ public class RoleServiceImplement extends AbstractEntityService im return mapper.selectByIdList(new HashSet<>(idList)); } + @Override + public List listManageableRole(ModuleCode moduleCode) { + String loginUserId = userLoginService.getRequireLoginUserId(); + return authorizationScopeService.listManageableRole(loginUserId, moduleCode); + } + + @Override + public List listGrantableRole(ModuleCode moduleCode) { + String loginUserId = userLoginService.getRequireLoginUserId(); + return authorizationScopeService.listGrantableRole(loginUserId, moduleCode); + } + @Override public List listChildByParentRoleId(String parentRoleId) { - List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleId(parentRoleId); + List childRoleIdList = rolePermissionInheritMapper.selectByParentRoleId(parentRoleId) + .stream() + .map(RolePermissionInherit::getChildRoleId) + .distinct() + .toList(); if (TimiJava.isEmpty(childRoleIdList)) { return new ArrayList<>(); } @@ -168,26 +256,277 @@ public class RoleServiceImplement extends AbstractEntityService im @Override public List listPermissionByRoleId(String roleId) { - List allRoleIdList = listAllRoleIdIncludingChildren(List.of(roleId)); + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userLoginService.getRequireLoginUserId(), List.of(roleId)); return permissionService.listByRoleId(allRoleIdList); } - public List listAllRoleIdIncludingChildren(Collection idList) { - Set allRoleIdSet = new HashSet<>(idList); - List currentLevelRoleIdList = new ArrayList<>(idList); + @Override + public List listGrantableRolePermission(String roleId) { + String loginUserId = userLoginService.getRequireLoginUserId(); + Role role = mapper.selectById(roleId); + TimiException.required(role, "not found role"); + checkManageableRole(loginUserId, role); + Set candidateIdSet = listRoleDirectGrantablePermissionId(loginUserId, role); + return listPermissionById(candidateIdSet).stream() + .collect(Collectors.toMap(Permission::getId, Function.identity(), (oldValue, newValue) -> oldValue, LinkedHashMap::new)) + .values() + .stream() + .toList(); + } - while (TimiJava.isNotEmpty(currentLevelRoleIdList)) { - List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList); - if (TimiJava.isEmpty(childRoleIdList)) { - break; - } - List newChildRoleIdList = childRoleIdList.stream().filter(id -> !allRoleIdSet.contains(id)).toList(); - if (TimiJava.isEmpty(newChildRoleIdList)) { - break; - } - allRoleIdSet.addAll(newChildRoleIdList); - currentLevelRoleIdList = newChildRoleIdList; + @Override + public List listDelegationPermissionByRoleId(String roleId) { + return rolePermissionDelegationMapper.selectPermissionByRoleIdList(List.of(roleId)); + } + + @Override + public List listGrantableDelegationPermission(String roleId) { + String loginUserId = userLoginService.getRequireLoginUserId(); + Role role = mapper.selectById(roleId); + TimiException.required(role, "not found role"); + List allPermissionList = isCoreDelegationRoot(role) + ? Arrays.stream(ModuleCode.values()).flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()).toList() + : listPermissionByRoleId(roleId); + Set allPermissionIdSet = allPermissionList.stream().map(Permission::getId).collect(Collectors.toSet()); + Set operatorGrantableIdSet = listOperatorGrantablePermissionId(loginUserId, allPermissionList); + boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId()); + Set parentDelegationIdSet = listDelegationParentPermissionId(roleId); + boolean systemAdmin = authorizationScopeService.isSystemAdmin(loginUserId); + return allPermissionList.stream() + .filter(permission -> allPermissionIdSet.contains(permission.getId())) + .filter(permission -> parentDelegationIdSet.contains(permission.getId()) || isCoreDelegationRoot(role) && systemAdmin && !hasParentRole) + .filter(permission -> operatorGrantableIdSet.contains(permission.getId()) || systemAdmin) + .toList(); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void updateRoleDelegation(Role role) { + String loginUserId = userLoginService.getRequireLoginUserId(); + TimiException.required(role.getId(), "not found role.id"); + Set newIdSet = TimiJava.isEmpty(role.getDelegationPermissionIdList()) ? new HashSet<>() : new HashSet<>(role.getDelegationPermissionIdList()); + authorizationBoundaryService.checkCanUpdateRoleDelegation(loginUserId, role.getId(), newIdSet); + Set dbIdSet = rolePermissionDelegationMapper.selectByRoleId(role.getId()).stream().map(RolePermissionDelegation::getPermissionId).collect(Collectors.toSet()); + + Set deleteIdList = new HashSet<>(dbIdSet); + deleteIdList.removeAll(newIdSet); + + Set addIdList = new HashSet<>(newIdSet); + addIdList.removeAll(dbIdSet); + if (TimiJava.isNotEmpty(deleteIdList)) { + rolePermissionDelegationMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); } - return new ArrayList<>(allRoleIdSet); + if (TimiJava.isNotEmpty(addIdList)) { + List delegationList = new ArrayList<>(); + long now = Time.now(); + for (String permissionId : addIdList) { + RolePermissionDelegation delegation = new RolePermissionDelegation(); + delegation.setId(UUID.randomUUID().toString()); + delegation.setRoleId(role.getId()); + delegation.setPermissionId(permissionId); + delegation.setCreatedBy(loginUserId); + delegation.setCreatedAt(now); + delegationList.add(delegation); + } + rolePermissionDelegationMapper.createBatch(delegationList); + } + logRoleAuthorizationChange("USER_ROLE_DELEGATION_PERMISSION_UPDATE", loginUserId, role.getId(), addIdList, deleteIdList); + } + + @Override + public List listGrantableParentRole(String roleId) { + String loginUserId = userLoginService.getRequireLoginUserId(); + Role targetRole = mapper.selectById(roleId); + TimiException.required(targetRole, "not found role"); + boolean systemAdmin = authorizationScopeService.isSystemAdmin(loginUserId); + Set manageableRoleIdSet = Arrays.stream(ModuleCode.values()) + .flatMap(moduleCode -> authorizationScopeService.listManageableRole(loginUserId, moduleCode).stream()) + .map(Role::getId) + .collect(Collectors.toSet()); + Set operatorRoleIdSet = listOperatorRoleId(loginUserId); + return mapper.selectAll() + .stream() + .filter(role -> !Objects.equals(role.getId(), targetRole.getId())) + .filter(role -> !isManagementDescendant(targetRole.getId(), role.getId())) + .filter(role -> systemAdmin || operatorRoleIdSet.contains(role.getId()) || manageableRoleIdSet.contains(role.getId())) + .collect(Collectors.toMap(Role::getId, Function.identity(), (oldValue, newValue) -> oldValue, LinkedHashMap::new)) + .values() + .stream() + .toList(); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void updateRoleParent(Role role) { + String loginUserId = userLoginService.getRequireLoginUserId(); + TimiException.required(role.getId(), "not found role.id"); + Role dbRole = mapper.selectById(role.getId()); + TimiException.required(dbRole, "not found role"); + authorizationBoundaryService.checkCanUpdateRoleParent(loginUserId, role.getId(), role.getParentRoleId()); + mapper.updateSelective(role); + Logger logger = new Logger(Logger.Module.USER, Logger.Level.INFO, "USER_ROLE_PARENT_UPDATE"); + logger.setUserId(loginUserId); + logger.setContent("roleId=%s;oldParentRoleId=%s;newParentRoleId=%s".formatted(role.getId(), dbRole.getParentRoleId(), role.getParentRoleId())); + logger.setResult(role.getId()); + loggerService.createSync(logger); + } + + public List listAllRoleIdIncludingChildren(Collection idList) { + return roleHierarchyService.listAllRoleIdIncludingChildren(idList); + } + + private void fillRoleDefault(Role role) { + role.setBuiltin(TimiJava.defaultIfNull(role.getBuiltin(), false)); + role.setProtectedRole(TimiJava.defaultIfNull(role.getProtectedRole(), false)); + role.setOwnerType(TimiJava.defaultIfNull(role.getOwnerType(), AuthOwnerType.MODULE)); + role.setOwnerId(TimiJava.defaultIfEmpty(role.getOwnerId(), role.getModuleCode() == null ? null : role.getModuleCode().name())); + } + + private void fillRelationScope(RolePermissionInherit relation, Role childRole, String createdBy) { + if (childRole == null) { + return; + } + relation.setOwnerType(childRole.getOwnerType()); + relation.setOwnerId(childRole.getOwnerId()); + relation.setCreatedBy(createdBy); + } + + private boolean canTouchRelation(String userId, Role parentRole, RolePermissionInherit relation) { + if (authorizationScopeService.isSystemAdmin(userId)) { + return true; + } + if (relation.getOwnerType() == AuthOwnerType.MODULE && parentRole != null && Objects.equals(relation.getOwnerId(), parentRole.getModuleCode().name())) { + return false; + } + ModuleResourceScopeResolver resolver = moduleResourceScopeResolverList.stream() + .filter(item -> parentRole != null && parentRole.getModuleCode() == item.moduleCode()) + .findFirst() + .orElse(null); + return resolver != null && resolver.canUseOwnerScope(userId, relation.getOwnerType(), relation.getOwnerId()); + } + + private boolean isCoreDelegationRoot(Role role) { + return role != null + && ModuleCode.CORE == role.getModuleCode() + && (com.imyeyu.api.bean.CoreRoleCode.SYSTEM.name().equals(role.getCode()) || com.imyeyu.api.bean.CoreRoleCode.ADMIN.name().equals(role.getCode())); + } + + private Set listOperatorGrantablePermissionId(String loginUserId, List permissionList) { + if (TimiJava.isEmpty(permissionList)) { + return Set.of(); + } + return permissionList.stream() + .map(Permission::getModuleCode) + .distinct() + .flatMap(moduleCode -> authorizationScopeService.listGrantablePermission(loginUserId, moduleCode).stream()) + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private Set listRoleDirectGrantablePermissionId(String loginUserId, Role role) { + boolean systemAdmin = authorizationScopeService.isSystemAdmin(loginUserId); + if (systemAdmin && isCoreDelegationRoot(role) && TimiJava.isEmpty(role.getParentRoleId())) { + return Arrays.stream(ModuleCode.values()) + .flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()) + .map(Permission::getId) + .collect(Collectors.toSet()); + } + TimiException.requiredTrue(TimiJava.isNotEmpty(role.getParentRoleId()), "目标角色缺少管理父级"); + Set parentDelegationPermissionIdSet = listDelegationParentPermissionId(role.getId()); + if (systemAdmin) { + return parentDelegationPermissionIdSet; + } + List parentDelegationPermissionList = listPermissionById(parentDelegationPermissionIdSet); + Set operatorGrantablePermissionIdSet = listOperatorGrantablePermissionId(loginUserId, parentDelegationPermissionList); + return parentDelegationPermissionIdSet.stream() + .filter(operatorGrantablePermissionIdSet::contains) + .collect(Collectors.toSet()); + } + + private List listPermissionById(Set permissionIdSet) { + if (TimiJava.isEmpty(permissionIdSet)) { + return List.of(); + } + return Arrays.stream(ModuleCode.values()) + .flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()) + .filter(permission -> permissionIdSet.contains(permission.getId())) + .toList(); + } + + private Set listDelegationParentPermissionId(String roleId) { + Role role = mapper.selectById(roleId); + if (role == null || TimiJava.isEmpty(role.getParentRoleId())) { + return Set.of(); + } + return rolePermissionDelegationMapper.selectPermissionByRoleIdList(List.of(role.getParentRoleId())) + .stream() + .map(Permission::getId) + .collect(Collectors.toSet()); + } + + private Set listOperatorRoleId(String loginUserId) { + List roleIdList = userRoleMapper.selectByUserIdList(List.of(loginUserId)).stream().map(UserRole::getRoleId).toList(); + if (TimiJava.isEmpty(roleIdList)) { + return Set.of(); + } + return new HashSet<>(roleHierarchyService.listAllRoleIdIncludingChildren(loginUserId, roleIdList)); + } + + private boolean hasRoleBaseField(Role role) { + return role.getModuleCode() != null + || role.getCode() != null + || role.getNameLangId() != null + || role.getDescription() != null + || role.getBuiltin() != null + || role.getProtectedRole() != null + || role.getParentRoleId() != null + || role.getOwnerType() != null + || role.getOwnerId() != null + || role.getCreatedBy() != null + || role.getName() != null; + } + + private void checkManageableRole(String loginUserId, Role role) { + Set manageableRoleIdSet = authorizationScopeService.listManageableRole(loginUserId, role.getModuleCode()) + .stream() + .map(Role::getId) + .collect(Collectors.toSet()); + TimiException.requiredTrue(manageableRoleIdSet.contains(role.getId()), "角色不在可管理范围内:%s".formatted(role.getCode())); + } + + private boolean isManagementDescendant(String parentRoleId, String childRoleId) { + List childRoleList = mapper.selectByParentRoleId(parentRoleId); + for (Role childRole : childRoleList) { + if (childRoleId.equals(childRole.getId()) || isManagementDescendant(childRole.getId(), childRoleId)) { + return true; + } + } + return false; + } + + private void logRoleAuthorizationChange(String operation, String loginUserId, String roleId, Collection addIdList, Collection deleteIdList) { + if (TimiJava.isEmpty(addIdList) && TimiJava.isEmpty(deleteIdList)) { + return; + } + Logger logger = new Logger(Logger.Module.USER, Logger.Level.INFO, operation); + logger.setUserId(loginUserId); + logger.setContent("roleId=%s;addCount=%d;deleteCount=%d;addIdList=%s;deleteIdList=%s".formatted( + roleId, + TimiJava.isEmpty(addIdList) ? 0 : addIdList.size(), + TimiJava.isEmpty(deleteIdList) ? 0 : deleteIdList.size(), + TimiJava.isEmpty(addIdList) ? "" : String.join(",", addIdList), + TimiJava.isEmpty(deleteIdList) ? "" : String.join(",", deleteIdList) + )); + logger.setResult(roleId); + loggerService.createSync(logger); + } + + private String relationKey(RolePermissionInherit relation) { + return "%s|%s|%s".formatted(relation.getChildRoleId(), relation.getOwnerType(), relation.getOwnerId()); + } + + private String relationKey(String childRoleId, Role childRole) { + return "%s|%s|%s".formatted(childRoleId, childRole == null ? null : childRole.getOwnerType(), childRole == null ? null : childRole.getOwnerId()); } } + diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java index 769a1ea..8c8a56d 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java @@ -2,12 +2,16 @@ package com.imyeyu.api.modules.user.service.implement; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.user.bean.BootstrapData; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.AuthorizationBoundaryService; import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleHierarchyService; import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserRoleService; import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest; import com.imyeyu.java.TimiJava; @@ -40,6 +44,9 @@ public class UserRoleServiceImplement extends AbstractEntityService addIdList = new HashSet<>(newIdSet); addIdList.removeAll(dbIdSet); + String loginUserId = userLoginService.getRequireLoginUserId(); + authorizationBoundaryService.checkCanRevokeRoles(loginUserId, req.getUserId(), deleteIdList); + authorizationBoundaryService.checkCanGrantRoles(loginUserId, req.getUserId(), addIdList); + if (TimiJava.isNotEmpty(deleteIdList)) { + for (String roleId : deleteIdList) { + UserRole userRole = new UserRole(); + userRole.setUserId(req.getUserId()); + userRole.setRoleId(roleId); + BootstrapData.checkProtectedUserRole(userRole); + } deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList)); } if (TimiJava.isNotEmpty(addIdList)) { @@ -123,7 +140,7 @@ public class UserRoleServiceImplement extends AbstractEntityService listAllRoleCodeByUserId(String userId) { List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList(); - List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList); + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userId, roleIdList); if (TimiJava.isEmpty(allRoleIdList)) { return List.of(); } @@ -133,7 +150,7 @@ public class UserRoleServiceImplement extends AbstractEntityService listAllPermissionByUserId(String userId) { List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList(); - List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList); + List allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userId, roleIdList); return permissionService.listByRoleId(allRoleIdList); } diff --git a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java index 6dfad63..4e1c281 100644 --- a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java +++ b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java @@ -1,5 +1,7 @@ package com.imyeyu.api.util; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.bean.ImageType; @@ -14,16 +16,18 @@ import com.imyeyu.api.modules.user.bean.BuiltinRoleCode; import com.imyeyu.api.modules.user.entity.Permission; import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.RolePermission; -import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.api.modules.user.entity.RolePermissionDelegation; +import com.imyeyu.api.modules.user.entity.RolePermissionInherit; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.entity.UserRole; import com.imyeyu.api.modules.user.mapper.PermissionMapper; import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionDelegationMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionInheritMapper; import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; -import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; import com.imyeyu.api.modules.user.mapper.UserRoleMapper; import com.imyeyu.api.modules.user.service.PermissionService; -import com.imyeyu.api.modules.user.service.RoleRelationService; +import com.imyeyu.api.modules.user.service.RolePermissionInheritService; import com.imyeyu.api.modules.user.service.RoleService; import com.imyeyu.api.modules.user.service.UserRoleService; import com.imyeyu.api.modules.user.service.UserService; @@ -53,13 +57,14 @@ public class InitBuiltinUser implements ApplicationRunner { private final LoggerService loggerService; private final UserRoleService userRoleService; private final PermissionService permissionService; - private final RoleRelationService roleRelationService; + private final RolePermissionInheritService rolePermissionInheritService; private final RoleMapper roleMapper; private final UserRoleMapper userRoleMapper; private final PermissionMapper permissionMapper; private final RolePermissionMapper rolePermissionMapper; - private final RoleRelationMapper roleRelationMapper; + private final RolePermissionDelegationMapper rolePermissionDelegationMapper; + private final RolePermissionInheritMapper rolePermissionInheritMapper; /** * 初始化内建数据 @@ -73,8 +78,10 @@ public class InitBuiltinUser implements ApplicationRunner { // 先同步所有内建权限和角色,再补齐继承与默认授权关系 Map permissionMap = initBuiltinPermissionMap(); Map roleMap = initBuiltinRoleMap(); - bindBuiltinRoleRelation(roleMap); + bindBuiltinRolePermissionInherit(roleMap); + bindBuiltinRoleParent(roleMap); bindBuiltinPermission(roleMap, permissionMap); + bindBuiltinPermissionDelegation(roleMap, permissionMap); Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE)); // 初始化系统用户 @@ -94,9 +101,7 @@ public class InitBuiltinUser implements ApplicationRunner { String logContent = "初始化 %s 自举用户成功,请立即修改密码:%s".formatted(BootstrapData.SYS_USER_NAME, password); - Logger logger = new Logger(); - logger.setModule(Logger.Module.SYSTEM); - logger.setOperation("INITIALIZE_SYSTEM_USER"); + Logger logger = Logger.info(Logger.Module.SYSTEM, "INITIALIZE_SYSTEM_USER"); logger.setUserId(user.getId()); logger.setContent(logContent); loggerService.create(logger); @@ -131,8 +136,12 @@ public class InitBuiltinUser implements ApplicationRunner { permission.setModuleCode(item.getModuleCode()); permission.setCode(item.getValue()); permission.setName(item.getDefaultName()); + fillBuiltinPermission(permission, item); permissionService.create(permission); permission = permissionMapper.selectByExample(example); + } else { + fillBuiltinPermission(permission, item); + permissionMapper.updateSelective(permission); } permissionMap.put(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue()), permission); } @@ -155,8 +164,12 @@ public class InitBuiltinUser implements ApplicationRunner { role.setModuleCode(item.getModuleCode()); role.setCode(code); role.setName(item.getDefaultName()); + fillBuiltinRole(role, item); roleService.create(role); role = roleMapper.selectByExample(example); + } else { + fillBuiltinRole(role, item); + roleMapper.updateSelective(role); } roleMap.put(BuiltinAuthRegistry.toRoleKey(item.getModuleCode(), code), role); } @@ -166,7 +179,7 @@ public class InitBuiltinUser implements ApplicationRunner { /// 按角色定义补齐父子角色关系 /// /// @param roleMap 已同步的角色映射 - private void bindBuiltinRoleRelation(Map roleMap) { + private void bindBuiltinRolePermissionInherit(Map roleMap) { for (BuiltinRoleCode item : BuiltinAuthRegistry.ROLE_CODE_LIST) { if (TimiJava.isEmpty(item.getParentCode())) { continue; @@ -176,15 +189,26 @@ public class InitBuiltinUser implements ApplicationRunner { if (parentRole == null || childRole == null) { continue; } - RoleRelation relation = new RoleRelation(); + RolePermissionInherit relation = new RolePermissionInherit(); relation.setParentRoleId(parentRole.getId()); relation.setChildRoleId(childRole.getId()); - if (roleRelationMapper.selectByExample(relation) == null) { - roleRelationService.create(relation); + relation.setOwnerType(item.getOwnerType()); + relation.setOwnerId(item.getOwnerId()); + if (rolePermissionInheritMapper.selectByExample(relation) == null) { + rolePermissionInheritService.create(relation); } } } + /// 补齐内置角色管理父级 + /// + /// @param roleMap 已同步的角色映射 + private void bindBuiltinRoleParent(Map roleMap) { + setRoleParent(roleMap, ModuleCode.CORE, CoreRoleCode.ADMIN.name(), ModuleCode.CORE, CoreRoleCode.SYSTEM.name()); + setRoleParent(roleMap, ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name(), ModuleCode.CORE, CoreRoleCode.ADMIN.name()); + setRoleParent(roleMap, ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name(), ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()); + } + /// 按权限定义补齐 SYSTEM 和 ADMIN 的默认权限 /// /// @param roleMap 已同步的角色映射 @@ -205,6 +229,12 @@ public class InitBuiltinUser implements ApplicationRunner { if (item.isGrantToAdmin() && adminRole != null) { createRolePermissionIfAbsent(adminRole.getId(), permission.getId()); } + if (item instanceof CorePermissionCode corePermission && isGrantToModuleRoleManager(corePermission) && gaoGlobalManagerRole != null) { + createRolePermissionIfAbsent(gaoGlobalManagerRole.getId(), permission.getId()); + } + if (item instanceof CorePermissionCode corePermission && isGrantToModuleRoleManager(corePermission) && gaoStoreManagerRole != null) { + createRolePermissionIfAbsent(gaoStoreManagerRole.getId(), permission.getId()); + } if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToGlobalManager() && gaoGlobalManagerRole != null) { createRolePermissionIfAbsent(gaoGlobalManagerRole.getId(), permission.getId()); } @@ -214,6 +244,35 @@ public class InitBuiltinUser implements ApplicationRunner { } } + /// 按内置策略补齐角色转授权限 + /// + /// @param roleMap 已同步的角色映射 + /// @param permissionMap 已同步的权限映射 + private void bindBuiltinPermissionDelegation(Map roleMap, Map permissionMap) { + Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE)); + Role adminRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.ADMIN_ROLE_MODULE_CODE, BootstrapData.ADMIN_ROLE_CODE)); + Role gaoGlobalManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())); + Role gaoStoreManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name())); + for (BuiltinPermissionCode item : BuiltinAuthRegistry.PERMISSION_CODE_LIST) { + Permission permission = permissionMap.get(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue())); + if (permission == null) { + continue; + } + if (systemRole != null) { + createRolePermissionDelegationIfAbsent(systemRole.getId(), permission.getId()); + } + if (adminRole != null) { + createRolePermissionDelegationIfAbsent(adminRole.getId(), permission.getId()); + } + if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToStoreManager() && gaoGlobalManagerRole != null) { + createRolePermissionDelegationIfAbsent(gaoGlobalManagerRole.getId(), permission.getId()); + } + if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToStoreManager() && gaoStoreManagerRole != null) { + createRolePermissionDelegationIfAbsent(gaoStoreManagerRole.getId(), permission.getId()); + } + } + } + /// 角色权限关系不存在时才创建 /// /// @param roleId 角色 ID @@ -230,4 +289,70 @@ public class InitBuiltinUser implements ApplicationRunner { rolePermission.setPermissionId(permissionId); rolePermissionMapper.insert(rolePermission); } + + /// 角色转授权限关系不存在时才创建 + /// + /// @param roleId 角色 ID + /// @param permissionId 权限 ID + private void createRolePermissionDelegationIfAbsent(String roleId, String permissionId) { + RolePermissionDelegation example = new RolePermissionDelegation(); + example.setRoleId(roleId); + example.setPermissionId(permissionId); + if (rolePermissionDelegationMapper.selectByExample(example) != null) { + return; + } + RolePermissionDelegation rolePermissionDelegation = new RolePermissionDelegation(); + rolePermissionDelegation.setRoleId(roleId); + rolePermissionDelegation.setPermissionId(permissionId); + rolePermissionDelegationMapper.insert(rolePermissionDelegation); + } + + /// 填充内置权限元数据 + /// + /// @param permission 权限 + /// @param item 内置权限定义 + private void fillBuiltinPermission(Permission permission, BuiltinPermissionCode item) { + permission.setBuiltin(true); + permission.setProtectedPermission(item.isProtected()); + permission.setOwnerType(item.getOwnerType()); + permission.setOwnerId(item.getOwnerId()); + } + + /// 填充内置角色元数据 + /// + /// @param role 角色 + /// @param item 内置角色定义 + private void fillBuiltinRole(Role role, BuiltinRoleCode item) { + role.setBuiltin(true); + role.setProtectedRole(item.isProtected()); + role.setOwnerType(item.getOwnerType()); + role.setOwnerId(item.getOwnerId()); + } + + /// 设置角色管理父级 + /// + /// @param roleMap 已同步的角色映射 + /// @param moduleCode 角色模块 + /// @param roleCode 角色代码 + /// @param parentModuleCode 父角色模块 + /// @param parentRoleCode 父角色代码 + private void setRoleParent(Map roleMap, ModuleCode moduleCode, String roleCode, ModuleCode parentModuleCode, String parentRoleCode) { + Role role = roleMap.get(BuiltinAuthRegistry.toRoleKey(moduleCode, roleCode)); + Role parentRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(parentModuleCode, parentRoleCode)); + if (role == null || parentRole == null || parentRole.getId().equals(role.getParentRoleId())) { + return; + } + role.setParentRoleId(parentRole.getId()); + roleMapper.updateSelective(role); + } + + /// 判断权限是否默认授予模块角色管理员 + /// + /// @param permissionCode 核心权限 + /// @return true 为授予 + private boolean isGrantToModuleRoleManager(CorePermissionCode permissionCode) { + return permissionCode.name().startsWith("ROLE_") + || permissionCode.name().startsWith("USER_ROLE_") + || CorePermissionCode.PERMISSION_READ == permissionCode; + } } diff --git a/src/main/resources/db/migration/timiserver/V26__add_authorization_boundary_fields.sql b/src/main/resources/db/migration/timiserver/V26__add_authorization_boundary_fields.sql new file mode 100644 index 0000000..6173d6e --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V26__add_authorization_boundary_fields.sql @@ -0,0 +1,39 @@ +ALTER TABLE `role` + ADD COLUMN `builtin` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'true 为系统内置角色' AFTER `description`, + ADD COLUMN `protected_role` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'true 为受保护角色' AFTER `builtin`, + ADD COLUMN `parent_role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '管理父角色 ID' AFTER `protected_role`, + ADD COLUMN `owner_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'MODULE' COMMENT '归属类型' AFTER `parent_role_id`, + ADD COLUMN `owner_id` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '归属 ID' AFTER `owner_type`, + ADD COLUMN `created_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '创建人用户 ID' AFTER `owner_id`, + ADD INDEX `idx_role_parent_role_id` (`parent_role_id`), + ADD INDEX `idx_role_owner` (`owner_type`, `owner_id`); + +ALTER TABLE `permission` + ADD COLUMN `builtin` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'true 为系统内置权限' AFTER `description`, + ADD COLUMN `protected_permission` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'true 为受保护权限' AFTER `builtin`, + ADD COLUMN `owner_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'MODULE' COMMENT '归属类型' AFTER `protected_permission`, + ADD COLUMN `owner_id` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '归属 ID' AFTER `owner_type`, + ADD COLUMN `created_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '创建人用户 ID' AFTER `owner_id`, + ADD INDEX `idx_permission_owner` (`owner_type`, `owner_id`); + +RENAME TABLE `role_relation` TO `role_permission_inherit`; + +ALTER TABLE `role_permission_inherit` + DROP INDEX `idx_role_relation`, + DROP INDEX `idx_child_role_id`, + ADD INDEX `idx_role_permission_inherit` (`parent_role_id`, `child_role_id`), + ADD INDEX `idx_role_permission_inherit_child_role_id` (`child_role_id`); + +ALTER TABLE `role_permission_inherit` + ADD COLUMN `owner_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'MODULE' COMMENT '归属类型' AFTER `child_role_id`, + ADD COLUMN `owner_id` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '归属 ID' AFTER `owner_type`, + ADD COLUMN `created_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '创建人用户 ID' AFTER `owner_id`, + ADD INDEX `idx_role_permission_inherit_owner` (`owner_type`, `owner_id`); + +UPDATE `role_permission_inherit` inherit +INNER JOIN `role` child_role ON inherit.`child_role_id` = child_role.`id` +SET inherit.`owner_id` = child_role.`module_code` +WHERE + inherit.`owner_type` = 'MODULE' + AND inherit.`owner_id` IS NULL + AND inherit.`deleted_at` IS NULL; diff --git a/src/main/resources/db/migration/timiserver/V27__add_role_permission_delegation.sql b/src/main/resources/db/migration/timiserver/V27__add_role_permission_delegation.sql new file mode 100644 index 0000000..871fb22 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V27__add_role_permission_delegation.sql @@ -0,0 +1,11 @@ +CREATE TABLE `role_permission_delegation` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色 ID', + `permission_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '允许转授的权限 ID', + `created_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '创建人用户 ID', + `created_at` BIGINT(20) COMMENT '创建时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_role_permission_delegation` (`role_id` ASC, `permission_id` ASC) USING BTREE, + INDEX `idx_permission_id` (`permission_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色转授权限' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V28__deduplicate_permission_inherit_and_delegation.sql b/src/main/resources/db/migration/timiserver/V28__deduplicate_permission_inherit_and_delegation.sql new file mode 100644 index 0000000..a5d7a96 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V28__deduplicate_permission_inherit_and_delegation.sql @@ -0,0 +1,40 @@ +UPDATE `role_permission_delegation` target +JOIN ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY role_id, permission_id + ORDER BY created_at DESC, id DESC + ) AS rn + FROM `role_permission_delegation` + WHERE deleted_at IS NULL +) duplicate ON duplicate.id = target.id +SET target.deleted_at = UNIX_TIMESTAMP() * 1000 +WHERE duplicate.rn > 1; + +ALTER TABLE `role_permission_delegation` + ADD COLUMN `active_key` TINYINT(1) GENERATED ALWAYS AS (IF(`deleted_at` IS NULL, 1, NULL)) STORED COMMENT '未删除唯一键'; + +CREATE UNIQUE INDEX `uk_role_permission_delegation` + ON `role_permission_delegation` (`role_id`, `permission_id`, `active_key`); + +UPDATE `role_permission_inherit` target +JOIN ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY parent_role_id, child_role_id, owner_type, IFNULL(owner_id, '') + ORDER BY created_at DESC, id DESC + ) AS rn + FROM `role_permission_inherit` + WHERE deleted_at IS NULL +) duplicate ON duplicate.id = target.id +SET target.deleted_at = UNIX_TIMESTAMP() * 1000 +WHERE duplicate.rn > 1; + +ALTER TABLE `role_permission_inherit` + ADD COLUMN `active_key` TINYINT(1) GENERATED ALWAYS AS (IF(`deleted_at` IS NULL, 1, NULL)) STORED COMMENT '未删除唯一键', + ADD COLUMN `owner_key` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (IFNULL(`owner_id`, '')) STORED COMMENT '归属唯一键'; + +CREATE UNIQUE INDEX `uk_role_permission_inherit` + ON `role_permission_inherit` (`parent_role_id`, `child_role_id`, `owner_type`, `owner_key`, `active_key`); diff --git a/src/main/resources/mapper/timi-server/PermissionMapper.xml b/src/main/resources/mapper/timi-server/PermissionMapper.xml new file mode 100644 index 0000000..3a14d3b --- /dev/null +++ b/src/main/resources/mapper/timi-server/PermissionMapper.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/main/resources/mapper/timi-server/RoleMapper.xml b/src/main/resources/mapper/timi-server/RoleMapper.xml index 736551c..2ae6f1b 100644 --- a/src/main/resources/mapper/timi-server/RoleMapper.xml +++ b/src/main/resources/mapper/timi-server/RoleMapper.xml @@ -1,6 +1,15 @@ + + + diff --git a/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml new file mode 100644 index 0000000..5a00cd6 --- /dev/null +++ b/src/main/resources/mapper/timi-server/RolePermissionDelegationMapper.xml @@ -0,0 +1,53 @@ + + + + + + + INSERT INTO `role_permission_delegation` ( + id, + role_id, + permission_id, + created_by, + created_at + ) VALUES + + (#{item.id}, #{item.roleId}, #{item.permissionId}, #{item.createdBy}, #{item.createdAt}) + + + + UPDATE `role_permission_delegation` + SET `deleted_at` = UNIX_TIMESTAMP() + WHERE + `role_id` = #{roleId} + AND `permission_id` IN + + #{item} + + AND `deleted_at` IS NULL + + diff --git a/src/main/resources/mapper/timi-server/RoleRelationMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml similarity index 53% rename from src/main/resources/mapper/timi-server/RoleRelationMapper.xml rename to src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml index dea0b6e..8b7510a 100644 --- a/src/main/resources/mapper/timi-server/RoleRelationMapper.xml +++ b/src/main/resources/mapper/timi-server/RolePermissionInheritMapper.xml @@ -1,10 +1,10 @@ - - SELECT * - FROM `role_relation` + FROM `role_permission_inherit` WHERE `parent_role_id` = #{parentRoleId} AND `deleted_at` IS NULL @@ -12,15 +12,26 @@ + - INSERT INTO `role_relation` ( + INSERT INTO `role_permission_inherit` ( id, parent_role_id, child_role_id, + owner_type, + owner_id, + created_by, created_at ) VALUES - - (#{item.id}, #{item.parentRoleId}, #{item.childRoleId}, #{item.createdAt}) + + (#{item.id}, #{item.parentRoleId}, #{item.childRoleId}, #{item.ownerType}, #{item.ownerId}, #{item.createdBy}, #{item.createdAt}) - UPDATE `role_relation` - SET `deleted_at` = UNIX_TIMESTAMP() + UPDATE `role_permission_inherit` + SET `deleted_at` = UNIX_TIMESTAMP() * 1000 WHERE `parent_role_id` = #{parentRoleId} AND `child_role_id` IN #{item} + AND `owner_type` = #{ownerType} + + AND `owner_id` = #{ownerId} + + + AND `owner_id` IS NULL + diff --git a/src/main/resources/mapper/timi-server/RolePermissionMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml index 8efab7c..3c223c7 100644 --- a/src/main/resources/mapper/timi-server/RolePermissionMapper.xml +++ b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml @@ -13,7 +13,7 @@ AND `role_permission`.`deleted_at` IS NULL