refactor permission system

This commit is contained in:
Timi
2026-08-11 23:57:59 +08:00
parent 6e43ff349d
commit 39ac73dc66
60 changed files with 2300 additions and 335 deletions
@@ -0,0 +1,23 @@
package com.imyeyu.api.modules.user.bean;
/// 授权对象归属类型
///
/// @author Codex
/// @since 2026-08-07
public enum AuthOwnerType {
/// 系统级
SYSTEM,
/// 模块级
MODULE,
/// 租户级
TENANT,
/// 门店级
STORE,
/// 用户级
USER
}
@@ -33,4 +33,25 @@ public interface BuiltinPermissionCode {
///
/// @return 是否授权给 ADMIN 角色
boolean isGrantToAdmin();
/// true 为受保护权限
///
/// @return 是否受保护
default boolean isProtected() {
return false;
}
/// 归属类型
///
/// @return 归属类型
default AuthOwnerType getOwnerType() {
return AuthOwnerType.MODULE;
}
/// 归属 ID
///
/// @return 归属 ID
default String getOwnerId() {
return getModuleCode().name();
}
}
@@ -28,4 +28,18 @@ public interface BuiltinRoleCode {
///
/// @return 父角色代码
String getParentCode();
/// 归属类型
///
/// @return 归属类型
default AuthOwnerType getOwnerType() {
return AuthOwnerType.MODULE;
}
/// 归属 ID
///
/// @return 归属 ID
default String getOwnerId() {
return getModuleCode().name();
}
}
@@ -1,12 +1,12 @@
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.RoleMatchMode;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.service.AuthorizationScopeService;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.bean.Page;
@@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 权限 SDK 控制器
*
@@ -32,16 +34,22 @@ import org.springframework.web.bind.annotation.RestController;
public class PermissionController {
private final PermissionService service;
private final UserLoginService userLoginService;
private final AuthorizationScopeService authorizationScopeService;
@RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY)
@RequireCorePermission(CorePermissionCode.PERMISSION_READ)
@PostMapping("/list")
public PageResult<Permission> list(@RequestBody Page<Permission> page) {
return service.page(page);
}
@RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ)
@PostMapping("/grantable/list")
public List<Permission> 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) {
@@ -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<Role> list(@RequestBody Page<Role> page) {
Set<String> manageableRoleIdSet = listManageableRoleId();
PageResult<Role> 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<Role> listManageableRole(@RequestParam ModuleCode moduleCode) {
return service.listManageableRole(moduleCode);
}
@RequireCorePermission(CorePermissionCode.USER_ROLE_READ)
@PostMapping("/grantable/list")
public List<Role> listGrantableRole(@RequestParam ModuleCode moduleCode) {
return service.listGrantableRole(moduleCode);
}
@RequiredToken
@PostMapping("/current/detail/list")
public List<Role> listCurrentRoleDetail(@RequestParam(required = false) ModuleCode moduleCode) {
String loginUserId = userLoginService.getRequireLoginUserId();
List<Role> 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<Permission> listGrantableRolePermission(@RequestParam String roleId) {
requireManageableRole(roleId);
return service.listGrantableRolePermission(roleId);
}
@RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ)
@PostMapping("/delegation/list")
public List<Permission> listDelegationPermission(@RequestParam String roleId) {
requireManageableRole(roleId);
return service.listDelegationPermissionByRoleId(roleId);
}
@RequireCorePermission(CorePermissionCode.ROLE_PERMISSION_READ)
@PostMapping("/delegation/grantable/list")
public List<Permission> 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<Role> 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<Role> listUserRole(@RequestParam(required = false) ModuleCode moduleCode, @RequestParam String userId) {
Set<String> 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<Permission> listAuthorizedPermission(@RequestParam String userId) {
return userRoleService.listAllPermissionByUserId(userId);
Set<String> manageableRoleIdSet = listManageableRoleId();
List<String> 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<String> 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);
}
}
}
@@ -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<LoginRequest> req) {
return userLoginService.login(req.getData());
@@ -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;
@@ -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<Role> childRoleList;
/// 管理父角色
@Transient
protected Role parentRole;
/// 角色允许继续转授的权限列表
@Transient
protected List<Permission> delegationPermissionList;
/** 权限 ID 列表 */
@Transient
protected Set<String> permissionIdList;
@@ -55,4 +82,8 @@ public class Role extends UUIDEntity {
/** 子角色 ID 列表 */
@Transient
protected Set<String> childRoleIdList;
/// 转授权限 ID 列表
@Transient
protected Set<String> delegationPermissionIdList;
}
@@ -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<String>, 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;
}
@@ -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<String>, Creatable, Deletable {
public class RolePermissionInherit implements IDEntity<String>, 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;
}
@@ -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<Permission, String> {
List<Permission> selectByIdList(List<String> list);
List<Permission> selectByIdList(@Param("idList") List<String> idList);
List<Permission> selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode);
}
@@ -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<Role, String> {
List<Role> selectByIdList(Collection<String> idList);
Role selectById(String id);
List<Role> selectByModuleCode(ModuleCode moduleCode);
List<Role> selectByIdList(@Param("idList") Collection<String> idList);
List<Role> selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode);
List<Role> selectByParentRoleId(@Param("parentRoleId") String parentRoleId);
List<Role> selectAll();
}
@@ -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<RolePermissionDelegation, String> {
default List<RolePermissionDelegation> selectByRoleId(String roleId) {
return selectByRoleIdList(List.of(roleId));
}
List<RolePermissionDelegation> selectByRoleIdList(@Param("roleIdList") List<String> roleIdList);
List<Permission> selectPermissionByRoleIdList(@Param("roleIdList") List<String> roleIdList);
void createBatch(@Param("rolePermissionDelegationList") List<RolePermissionDelegation> rolePermissionDelegationList);
void deleteByPermissionIdList(@Param("roleId") String roleId, @Param("permissionIdList") List<String> permissionIdList);
}
@@ -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<RolePermissionInherit, String> {
List<RolePermissionInherit> selectByParentRoleId(@Param("parentRoleId") String parentRoleId);
List<String> selectChildRoleIdListByParentRoleId(@Param("parentRoleId") String parentRoleId);
List<RolePermissionInherit> selectByParentRoleIdList(@Param("parentRoleIdList") List<String> parentRoleIdList);
List<String> selectChildRoleIdListByParentRoleIdList(@Param("parentRoleIdList") List<String> parentRoleIdList);
void createBatch(@Param("rolePermissionInheritList") List<RolePermissionInherit> rolePermissionInheritList);
void deleteByChildRoleIdList(
@Param("parentRoleId") String parentRoleId,
@Param("childRoleIdList") List<String> childRoleIdList,
@Param("ownerType") AuthOwnerType ownerType,
@Param("ownerId") String ownerId
);
}
@@ -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<RolePermission, String>
return selectByRoleIdList(List.of(roleId));
}
List<RolePermission> selectByRoleIdList(List<String> roleIdList);
List<RolePermission> selectByRoleIdList(@Param("roleIdList") List<String> roleIdList);
List<Permission> selectPermissionByRoleIdList(List<String> roleIdList);
List<Permission> selectPermissionByRoleIdList(@Param("roleIdList") List<String> roleIdList);
void createBatch(List<RolePermission> rolePermissionList);
void createBatch(@Param("rolePermissionList") List<RolePermission> rolePermissionList);
void deleteByPermissionIdList(String roleId, List<String> permissionIdList);
void deleteByPermissionIdList(@Param("roleId") String roleId, @Param("permissionIdList") List<String> permissionIdList);
}
@@ -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<RoleRelation, String> {
List<RoleRelation> selectByParentRoleId(String parentRoleId);
List<String> selectChildRoleIdListByParentRoleId(String parentRoleId);
List<String> selectChildRoleIdListByParentRoleIdList(List<String> parentRoleIdList);
void createBatch(List<RoleRelation> roleRelationList);
void deleteByChildRoleIdList(String parentRoleId, List<String> childRoleIdList);
}
@@ -19,5 +19,5 @@ public interface UserRoleMapper extends BaseMapper<UserRole, String> {
List<UserRole> selectByUserIdList(@Param("userIdList") Collection<String> userIdList);
void deleteByRoleIdList(String userId, List<String> roleIdList);
void deleteByRoleIdList(@Param("userId") String userId, @Param("roleIdList") List<String> roleIdList);
}
@@ -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<String> roleIdList);
/// 校验是否可以移除用户角色
///
/// @param operatorUserId 操作者用户 ID
/// @param targetUserId 目标用户 ID
/// @param roleIdList 角色 ID 列表
void checkCanRevokeRoles(String operatorUserId, String targetUserId, Collection<String> 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<String> permissionIdList);
/// 校验是否可以移除角色权限
///
/// @param operatorUserId 操作者用户 ID
/// @param roleId 角色 ID
/// @param permissionIdList 权限 ID 列表
void checkCanRevokePermissions(String operatorUserId, String roleId, Collection<String> permissionIdList);
/// 校验是否可以修改角色转授权限
///
/// @param operatorUserId 操作者用户 ID
/// @param roleId 角色 ID
/// @param permissionIdList 转授权限 ID 列表
void checkCanUpdateRoleDelegation(String operatorUserId, String roleId, Collection<String> 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<String> childRoleIdList);
}
@@ -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<Role> listGrantableRole(String operatorUserId, ModuleCode moduleCode);
/// 查询可授权权限
///
/// @param operatorUserId 操作者用户 ID
/// @param moduleCode 模块代码
/// @return 权限列表
List<Permission> listGrantablePermission(String operatorUserId, ModuleCode moduleCode);
/// 查询可管理角色
///
/// @param operatorUserId 操作者用户 ID
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listManageableRole(String operatorUserId, ModuleCode moduleCode);
/// 判断是否为系统管理员
///
/// @param operatorUserId 操作者用户 ID
/// @return true 为系统管理员
boolean isSystemAdmin(String operatorUserId);
}
@@ -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);
}
@@ -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<Permission, String> {
/// @param roleIdList 角色 ID 列表
/// @return 权限列表
List<Permission> listByRoleId(List<String> roleIdList);
/// 查询模块权限
///
/// @param moduleCode 模块代码
/// @return 权限列表
List<Permission> listByModuleCode(ModuleCode moduleCode);
}
@@ -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<String> listAllRoleIdIncludingChildren(Collection<String> roleIdList);
/// 递归查询用户作用域内的角色及其所有子角色 ID
///
/// @param userId 用户 ID
/// @param roleIdList 角色 ID 列表
/// @return 角色 ID 列表
List<String> listAllRoleIdIncludingChildren(String userId, Collection<String> roleIdList);
/// 校验角色继承关系不存在环
///
/// @param parentRoleId 父角色 ID
/// @param childRoleIdList 子角色 ID 列表
void checkNoCycle(String parentRoleId, Collection<String> childRoleIdList);
/// 判断目标角色是否为子孙角色
///
/// @param parentRoleId 父角色 ID
/// @param childRoleId 子角色 ID
/// @return true 为子孙角色
boolean isDescendant(String parentRoleId, String childRoleId);
}
@@ -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<RolePermissionInherit, String> {
/// 查询直属权限继承
///
/// @param parentRoleId 继承方角色 ID
/// @return 权限继承列表
List<RolePermissionInherit> listByParentRoleId(String parentRoleId);
/// 查询直属被继承角色 ID 列表
///
/// @param parentRoleId 继承方角色 ID
/// @return 被继承角色 ID 列表
List<String> listChildRoleIdByParentRoleId(String parentRoleId);
/// 递归展开全部被继承角色 ID
///
/// @param parentRoleIdList 继承方角色 ID 列表
/// @return 全部被继承角色 ID 列表
List<String> listAllChildRoleIdByParentRoleIdList(List<String> parentRoleIdList);
}
@@ -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<RoleRelation, String> {
/// 查询直属子角色关联
///
/// @param parentRoleId 父角色 ID
/// @return 角色关联列表
List<RoleRelation> listByParentRoleId(String parentRoleId);
/// 查询直属子角色 ID 列表
///
/// @param parentRoleId 父角色 ID
/// @return 子角色 ID 列表
List<String> listChildRoleIdByParentRoleId(String parentRoleId);
/// 递归展开全部子角色 ID
///
/// @param parentRoleIdList 父角色 ID 列表
/// @return 全部子角色 ID 列表
List<String> listAllChildRoleIdByParentRoleIdList(List<String> parentRoleIdList);
}
@@ -22,6 +22,18 @@ public interface RoleService extends BaseService<Role, String> {
List<Role> listByIdList(Collection<String> idList);
/// 查询当前用户可管理的模块角色
///
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listManageableRole(ModuleCode moduleCode);
/// 查询当前用户可授权给账号的模块角色
///
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listGrantableRole(ModuleCode moduleCode);
/// 查询直属子角色
///
/// @param parentRoleId 父角色 ID
@@ -34,6 +46,40 @@ public interface RoleService extends BaseService<Role, String> {
/// @return 权限列表
List<Permission> listPermissionByRoleId(String roleId);
/// 查询可配置给目标角色的直接权限
///
/// @param roleId 角色 ID
/// @return 权限列表
List<Permission> listGrantableRolePermission(String roleId);
/// 查询角色转授权限
///
/// @param roleId 角色 ID
/// @return 权限列表
List<Permission> listDelegationPermissionByRoleId(String roleId);
/// 查询可配置给目标角色的转授权限
///
/// @param roleId 角色 ID
/// @return 权限列表
List<Permission> listGrantableDelegationPermission(String roleId);
/// 修改角色转授权限
///
/// @param role 角色
void updateRoleDelegation(Role role);
/// 查询可配置给目标角色的管理父级角色
///
/// @param roleId 角色 ID
/// @return 角色列表
List<Role> listGrantableParentRole(String roleId);
/// 修改角色管理父级
///
/// @param role 角色
void updateRoleParent(Role role);
/// 递归获取角色及其所有子角色的 ID 列表
///
/// @param idList 角色 ID 列表
@@ -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<ModuleResourceScopeResolver> moduleResourceScopeResolverList;
@Override
public void checkCanGrantRoles(String operatorUserId, String targetUserId, Collection<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return;
}
checkCorePermission(operatorUserId, CorePermissionCode.USER_ROLE_CREATE);
if (authorizationScopeService.isSystemAdmin(operatorUserId)) {
return;
}
List<Role> 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<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return;
}
checkCorePermission(operatorUserId, CorePermissionCode.USER_ROLE_DELETE);
if (authorizationScopeService.isSystemAdmin(operatorUserId)) {
return;
}
List<Role> 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<String> 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<Permission> permissionList = permissionMapper.selectByIdList(permissionIdList.stream().toList());
checkPermissionIdListComplete(permissionIdList, permissionList);
Set<String> 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<String> 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<String> 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<Permission> permissionList = permissionMapper.selectByIdList(permissionIdList.stream().toList());
checkPermissionIdListComplete(permissionIdList, permissionList);
Set<String> allPermissionIdSet = listRoleAllPermissionId(operatorUserId, roleId);
boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId());
Set<String> parentDelegationPermissionIdSet = listParentDelegationPermissionId(roleId);
if (!isCoreDelegationRoot(role)) {
TimiException.requiredTrue(hasParentRole, "目标角色缺少管理父级");
}
Set<String> 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<String> 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<Role> 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<String> 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<String> 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<String> listRoleAllPermissionId(String operatorUserId, String roleId) {
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, List.of(roleId));
return rolePermissionMapper.selectPermissionByRoleIdList(allRoleIdList)
.stream()
.map(Permission::getId)
.collect(Collectors.toSet());
}
private Set<String> listGrantablePermissionId(String operatorUserId, List<Permission> 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<String> 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<String> parentDelegationPermissionIdSet = listParentDelegationPermissionId(role.getId());
if (systemAdmin) {
return parentDelegationPermissionIdSet;
}
List<Permission> parentDelegationPermissionList = TimiJava.isEmpty(parentDelegationPermissionIdSet)
? List.of()
: permissionMapper.selectByIdList(parentDelegationPermissionIdSet.stream().toList());
Set<String> operatorGrantablePermissionIdSet = listGrantablePermissionId(operatorUserId, parentDelegationPermissionList);
return parentDelegationPermissionIdSet.stream()
.filter(operatorGrantablePermissionIdSet::contains)
.collect(Collectors.toSet());
}
private Set<String> 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<String> 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<Role> 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<String> roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList();
if (TimiJava.isEmpty(roleIdList)) {
return false;
}
List<String> 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<String> idList, List<Role> roleList) {
Set<String> foundIdSet = roleList.stream().map(Role::getId).collect(Collectors.toSet());
Set<String> idSet = new HashSet<>(idList);
TimiException.requiredTrue(foundIdSet.containsAll(idSet), "存在无效角色 ID");
}
private void checkPermissionIdListComplete(Collection<String> idList, List<Permission> permissionList) {
Set<String> foundIdSet = permissionList.stream().map(Permission::getId).collect(Collectors.toSet());
Set<String> idSet = new HashSet<>(idList);
TimiException.requiredTrue(foundIdSet.containsAll(idSet), "存在无效权限 ID");
}
}
@@ -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<ModuleResourceScopeResolver> moduleResourceScopeResolverList;
@Override
public List<Role> listGrantableRole(String operatorUserId, ModuleCode moduleCode) {
List<Role> roleList = listManageableRole(operatorUserId, moduleCode);
if (isSystemAdmin(operatorUserId)) {
return roleList;
}
Set<String> operatorDelegationPermissionIdSet = listOperatorDelegationPermissionId(operatorUserId);
return roleList.stream()
.filter(role -> operatorDelegationPermissionIdSet.containsAll(listRoleAllPermissionId(operatorUserId, role.getId())))
.toList();
}
@Override
public List<Permission> listGrantablePermission(String operatorUserId, ModuleCode moduleCode) {
List<Permission> permissionList = permissionMapper.selectByModuleCode(moduleCode);
if (isSystemAdmin(operatorUserId)) {
return permissionList;
}
Set<String> delegationPermissionIdSet = listOperatorDelegationPermissionId(operatorUserId, moduleCode);
return permissionList.stream()
.filter(permission -> !Boolean.TRUE.equals(permission.getProtectedPermission()))
.filter(permission -> delegationPermissionIdSet.contains(permission.getId()))
.toList();
}
@Override
public List<Role> listManageableRole(String operatorUserId, ModuleCode moduleCode) {
List<Role> roleList = roleMapper.selectByModuleCode(moduleCode);
if (isSystemAdmin(operatorUserId)) {
return roleList;
}
Set<String> 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<String> listManagementDescendantRoleId(String operatorUserId) {
List<String> operatorRoleIdList = listOperatorRoleId(operatorUserId);
if (TimiJava.isEmpty(operatorRoleIdList)) {
return Set.of();
}
Map<String, List<Role>> childRoleMap = roleMapper.selectAll()
.stream()
.filter(role -> TimiJava.isNotEmpty(role.getParentRoleId()))
.collect(Collectors.groupingBy(Role::getParentRoleId));
Set<String> result = new HashSet<>();
ArrayDeque<String> 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<Role> listAllOperatorRole(String operatorUserId) {
List<String> allRoleIdList = listAllOperatorRoleId(operatorUserId);
if (TimiJava.isEmpty(allRoleIdList)) {
return List.of();
}
Map<String, Role> 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<String> listAllOperatorRoleId(String operatorUserId) {
List<String> roleIdList = listOperatorRoleId(operatorUserId);
if (TimiJava.isEmpty(roleIdList)) {
return List.of();
}
return roleHierarchyService.listAllRoleIdIncludingChildren(operatorUserId, roleIdList);
}
private List<String> listOperatorRoleId(String operatorUserId) {
if (TimiJava.isEmpty(operatorUserId)) {
return List.of();
}
return userRoleMapper.selectByUserIdList(List.of(operatorUserId)).stream().map(UserRole::getRoleId).toList();
}
private Set<String> listOperatorDelegationPermissionId(String operatorUserId, ModuleCode moduleCode) {
List<String> 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<String> listOperatorDelegationPermissionId(String operatorUserId) {
List<String> 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<String> listRoleAllPermissionId(String operatorUserId, String roleId) {
List<String> 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);
}
}
@@ -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<Permission
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(Permission permission) {
fillPermissionDefault(permission);
permission.setNameLangId(multilingualService.create(permission.getName()));
super.create(permission);
}
@@ -62,6 +65,7 @@ public class PermissionServiceImplement extends AbstractEntityService<Permission
permission.setModuleCode(req.getModuleCode());
permission.setCode(req.getPrefixCode() + "_" + type.name());
permission.setName(req.getPrefixName() + messageResolver.resolve(type.name().toLowerCase(), null, TimiSpring.getLanguage()));
fillPermissionDefault(permission);
permission.setCreatedAt(Time.now());
permissionList.add(permission);
}
@@ -84,4 +88,16 @@ public class PermissionServiceImplement extends AbstractEntityService<Permission
}
return rolePermissionMapper.selectPermissionByRoleIdList(roleIdList);
}
@Override
public List<Permission> 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()));
}
}
@@ -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<ModuleResourceScopeResolver> moduleResourceScopeResolverProvider;
@Override
public List<String> listAllRoleIdIncludingChildren(Collection<String> roleIdList) {
return listAllRoleIdIncludingChildren(null, roleIdList);
}
@Override
public List<String> listAllRoleIdIncludingChildren(String userId, Collection<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return List.of();
}
Set<String> allRoleIdSet = new HashSet<>(roleIdList);
List<String> currentLevelRoleIdList = new ArrayList<>(roleIdList);
while (TimiJava.isNotEmpty(currentLevelRoleIdList)) {
List<RolePermissionInherit> relationList = rolePermissionInheritMapper.selectByParentRoleIdList(currentLevelRoleIdList);
List<String> parentAndChildRoleIdList = relationList.stream()
.flatMap(relation -> java.util.stream.Stream.of(relation.getParentRoleId(), relation.getChildRoleId()))
.distinct()
.toList();
Map<String, Role> roleMap = roleMapper.selectByIdList(parentAndChildRoleIdList).stream().collect(Collectors.toMap(Role::getId, Function.identity()));
List<String> childRoleIdList = relationList.stream()
.filter(relation -> isVisible(userId, relation, roleMap.get(relation.getParentRoleId())))
.map(RolePermissionInherit::getChildRoleId)
.toList();
if (TimiJava.isEmpty(childRoleIdList)) {
break;
}
List<String> 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<String> 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());
}
}
@@ -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<RoleRelation, String> implements RoleRelationService {
public class RolePermissionInheritServiceImplement extends AbstractEntityService<RolePermissionInherit, String> implements RolePermissionInheritService {
private final RoleRelationMapper mapper;
private final RolePermissionInheritMapper mapper;
@Override
protected BaseMapper<RoleRelation, String> mapper() {
protected BaseMapper<RolePermissionInherit, String> mapper() {
return mapper;
}
@Override
public List<RoleRelation> listByParentRoleId(String parentRoleId) {
public List<RolePermissionInherit> 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<String> listChildRoleIdByParentRoleId(String parentRoleId) {
return listByParentRoleId(parentRoleId).stream().map(RoleRelation::getChildRoleId).distinct().toList();
return listByParentRoleId(parentRoleId).stream().map(RolePermissionInherit::getChildRoleId).distinct().toList();
}
@Override
@@ -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<Role, String> 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<ModuleResourceScopeResolver> 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<Role, String> mapper() {
@@ -52,8 +77,20 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> 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<RolePermission> rolePermissionList = new ArrayList<>();
@@ -70,37 +107,51 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
}
if (TimiJava.isNotEmpty(role.getChildRoleIdList())) {
List<RoleRelation> roleRelationList = new ArrayList<>();
List<RolePermissionInherit> rolePermissionInheritList = new ArrayList<>();
long now = Time.now();
Map<String, Role> 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<String> dbIdSet = rolePermissionMapper.selectByRoleId(role.getId()).stream().map(RolePermission::getPermissionId).collect(Collectors.toSet());
Set<String> newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getPermissionIdList()), new HashSet<>());
Set<String> newIdSet = TimiJava.isEmpty(role.getPermissionIdList()) ? new HashSet<>() : new HashSet<>(role.getPermissionIdList());
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> 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<RolePermission> rolePermissionList = new ArrayList<>();
@@ -115,37 +166,58 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
}
rolePermissionMapper.createBatch(rolePermissionList);
}
logRoleAuthorizationChange("USER_ROLE_PERMISSION_UPDATE", loginUserId, role.getId(), addIdList, deleteIdList);
}
{
Set<String> dbIdSet = roleRelationMapper.selectByParentRoleId(role.getId()).stream().map(RoleRelation::getChildRoleId).collect(Collectors.toSet());
Set<String> newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getChildRoleIdList()), new HashSet<>());
if (role.getChildRoleIdList() != null) {
Role parentRole = mapper.selectById(role.getId());
List<RolePermissionInherit> dbRelationList = rolePermissionInheritMapper.selectByParentRoleId(role.getId());
List<RolePermissionInherit> touchableRelationList = dbRelationList.stream()
.filter(relation -> canTouchRelation(loginUserId, parentRole, relation))
.toList();
Set<String> newIdSet = TimiJava.isEmpty(role.getChildRoleIdList()) ? new HashSet<>() : new HashSet<>(role.getChildRoleIdList());
Map<String, Role> childRoleMap = mapper.selectByIdList(newIdSet).stream().collect(Collectors.toMap(Role::getId, Function.identity()));
Set<String> dbKeySet = dbRelationList.stream().map(this::relationKey).collect(Collectors.toSet());
Set<String> newKeySet = newIdSet.stream().map(childRoleId -> relationKey(childRoleId, childRoleMap.get(childRoleId))).collect(Collectors.toSet());
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> deleteKeyList = new HashSet<>(dbKeySet);
deleteKeyList.removeAll(newKeySet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
Set<String> addKeyList = new HashSet<>(newKeySet);
addKeyList.removeAll(dbKeySet);
Set<String> 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<RoleRelation> roleRelationList = new ArrayList<>();
List<RolePermissionInherit> 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<Role> listByModuleCode(ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
@@ -157,9 +229,25 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
public List<Role> listManageableRole(ModuleCode moduleCode) {
String loginUserId = userLoginService.getRequireLoginUserId();
return authorizationScopeService.listManageableRole(loginUserId, moduleCode);
}
@Override
public List<Role> listGrantableRole(ModuleCode moduleCode) {
String loginUserId = userLoginService.getRequireLoginUserId();
return authorizationScopeService.listGrantableRole(loginUserId, moduleCode);
}
@Override
public List<Role> listChildByParentRoleId(String parentRoleId) {
List<String> childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleId(parentRoleId);
List<String> 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<Role, String> im
@Override
public List<Permission> listPermissionByRoleId(String roleId) {
List<String> allRoleIdList = listAllRoleIdIncludingChildren(List.of(roleId));
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userLoginService.getRequireLoginUserId(), List.of(roleId));
return permissionService.listByRoleId(allRoleIdList);
}
public List<String> listAllRoleIdIncludingChildren(Collection<String> idList) {
Set<String> allRoleIdSet = new HashSet<>(idList);
List<String> currentLevelRoleIdList = new ArrayList<>(idList);
@Override
public List<Permission> listGrantableRolePermission(String roleId) {
String loginUserId = userLoginService.getRequireLoginUserId();
Role role = mapper.selectById(roleId);
TimiException.required(role, "not found role");
checkManageableRole(loginUserId, role);
Set<String> 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<String> childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList);
if (TimiJava.isEmpty(childRoleIdList)) {
break;
}
List<String> newChildRoleIdList = childRoleIdList.stream().filter(id -> !allRoleIdSet.contains(id)).toList();
if (TimiJava.isEmpty(newChildRoleIdList)) {
break;
}
allRoleIdSet.addAll(newChildRoleIdList);
currentLevelRoleIdList = newChildRoleIdList;
@Override
public List<Permission> listDelegationPermissionByRoleId(String roleId) {
return rolePermissionDelegationMapper.selectPermissionByRoleIdList(List.of(roleId));
}
@Override
public List<Permission> listGrantableDelegationPermission(String roleId) {
String loginUserId = userLoginService.getRequireLoginUserId();
Role role = mapper.selectById(roleId);
TimiException.required(role, "not found role");
List<Permission> allPermissionList = isCoreDelegationRoot(role)
? Arrays.stream(ModuleCode.values()).flatMap(moduleCode -> permissionService.listByModuleCode(moduleCode).stream()).toList()
: listPermissionByRoleId(roleId);
Set<String> allPermissionIdSet = allPermissionList.stream().map(Permission::getId).collect(Collectors.toSet());
Set<String> operatorGrantableIdSet = listOperatorGrantablePermissionId(loginUserId, allPermissionList);
boolean hasParentRole = TimiJava.isNotEmpty(role.getParentRoleId());
Set<String> 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<String> newIdSet = TimiJava.isEmpty(role.getDelegationPermissionIdList()) ? new HashSet<>() : new HashSet<>(role.getDelegationPermissionIdList());
authorizationBoundaryService.checkCanUpdateRoleDelegation(loginUserId, role.getId(), newIdSet);
Set<String> dbIdSet = rolePermissionDelegationMapper.selectByRoleId(role.getId()).stream().map(RolePermissionDelegation::getPermissionId).collect(Collectors.toSet());
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> 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<RolePermissionDelegation> 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<Role> listGrantableParentRole(String roleId) {
String loginUserId = userLoginService.getRequireLoginUserId();
Role targetRole = mapper.selectById(roleId);
TimiException.required(targetRole, "not found role");
boolean systemAdmin = authorizationScopeService.isSystemAdmin(loginUserId);
Set<String> manageableRoleIdSet = Arrays.stream(ModuleCode.values())
.flatMap(moduleCode -> authorizationScopeService.listManageableRole(loginUserId, moduleCode).stream())
.map(Role::getId)
.collect(Collectors.toSet());
Set<String> 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<String> listAllRoleIdIncludingChildren(Collection<String> 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<String> listOperatorGrantablePermissionId(String loginUserId, List<Permission> 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<String> 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<String> parentDelegationPermissionIdSet = listDelegationParentPermissionId(role.getId());
if (systemAdmin) {
return parentDelegationPermissionIdSet;
}
List<Permission> parentDelegationPermissionList = listPermissionById(parentDelegationPermissionIdSet);
Set<String> operatorGrantablePermissionIdSet = listOperatorGrantablePermissionId(loginUserId, parentDelegationPermissionList);
return parentDelegationPermissionIdSet.stream()
.filter(operatorGrantablePermissionIdSet::contains)
.collect(Collectors.toSet());
}
private List<Permission> listPermissionById(Set<String> 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<String> 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<String> listOperatorRoleId(String loginUserId) {
List<String> 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<String> 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<Role> 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<String> addIdList, Collection<String> 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());
}
}
@@ -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<UserRole, St
private final RoleService roleService;
private final PermissionService permissionService;
private final UserLoginService userLoginService;
private final AuthorizationBoundaryService authorizationBoundaryService;
private final RoleHierarchyService roleHierarchyService;
private final UserRoleMapper mapper;
@@ -78,7 +85,17 @@ public class UserRoleServiceImplement extends AbstractEntityService<UserRole, St
Set<String> 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<UserRole, St
@Override
public List<String> listAllRoleCodeByUserId(String userId) {
List<String> roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList);
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userId, roleIdList);
if (TimiJava.isEmpty(allRoleIdList)) {
return List.of();
}
@@ -133,7 +150,7 @@ public class UserRoleServiceImplement extends AbstractEntityService<UserRole, St
@Override
public List<Permission> listAllPermissionByUserId(String userId) {
List<String> roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList);
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(userId, roleIdList);
return permissionService.listByRoleId(allRoleIdList);
}