- sys_user_app_role_rel 新增 group_id:组=绑定发生时所选组(路径不落库,按组ID实时拼) - 角色用户列表按绑定行返回,新增部门列(groupPath 按查看者数据范围裁剪,学校管理员只看本校) - 保存改为按组覆盖/增量添加:编辑弹窗按所在组覆盖,角色授权页增量添加不误删其他组绑定 - 调整组不带走角色:旧组绑定保留不生效,调回自动恢复 - collectEnabledRoleModuleIds 只算用户当前所在组上的启用角色(NULL 视为全局兜底) - UserGroupTree/UserRoles 增加可选 groupId:所属部门按行收窄、角色按组回显 - 附迁移脚本 sys_user_app_role_rel_add_group_dimension.sql(回填主组 + 换唯一约束)
This commit is contained in:
@@ -108,16 +108,17 @@ public class AppRoleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询角色已绑定的用户列表。
|
||||
* 查询角色已绑定的用户列表(一行 = 一条「用户×组」绑定,含部门路径)。<br>
|
||||
* 路径查询时按 groupId 实时拼接;非超管按数据范围过滤行并裁剪路径(学校管理员只看到本校路径)。
|
||||
*/
|
||||
@GetMapping("/RoleUsers")
|
||||
public List<AppRoleUserVo> roleUsers(@RequestParam Integer roleId) {
|
||||
accessScopeService.requireAdmin(); // S+B+C:NONE 已被拦截器挡住,双保险
|
||||
return appRoleService.findRoleUsers(roleId);
|
||||
return appRoleService.findRoleUsers(roleId, accessScopeService.scopeGroupIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色绑定用户保存(角色维度,全量覆盖)。
|
||||
* 角色绑定用户保存(角色维度,增量添加「用户×组」绑定)。
|
||||
*/
|
||||
@OperationLog("保存角色用户绑定")
|
||||
@PostMapping("SaveRoleUsers")
|
||||
@@ -127,7 +128,7 @@ public class AppRoleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户绑定角色保存(用户维度,全量覆盖)。
|
||||
* 用户绑定角色保存(用户维度,按组覆盖:只覆盖该用户在指定组下的绑定)。
|
||||
*/
|
||||
@OperationLog("保存用户角色绑定")
|
||||
@PostMapping("SaveUserRoles")
|
||||
@@ -137,13 +138,14 @@ public class AppRoleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除单个用户与角色的绑定关系(精确删除单条记录,非全量重提)。<br>
|
||||
* 移除单条「用户×组×角色」绑定(精确删除一行,非全量重提)。<br>
|
||||
* 前端「用户管理 - 删除」调用此接口。
|
||||
*/
|
||||
@OperationLog("移除角色用户绑定")
|
||||
@PostMapping("RemoveRoleUser")
|
||||
public void removeRoleUser(@RequestParam Integer roleId, @RequestParam Integer userId) {
|
||||
public void removeRoleUser(@RequestParam Integer roleId, @RequestParam Integer userId,
|
||||
@RequestParam Integer groupId) {
|
||||
accessScopeService.requireAdmin(); // S+B+C:NONE 已被拦截器挡住,双保险
|
||||
appRoleService.removeRoleUser(roleId, userId);
|
||||
appRoleService.removeRoleUser(roleId, userId, groupId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,30 +148,35 @@ public class GroupAuthzController {
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属的业务角色列表(中间下方「所属角色」区域展示用)。<br>
|
||||
* 返回该用户绑定的全部角色(含禁用角色,disabled 字段标记),普通角色回填所属应用名称。
|
||||
* 返回该用户绑定的全部角色(含禁用角色,disabled 字段标记),普通角色回填所属应用名称。<br>
|
||||
* groupId 可选:传入时只返回该组下的绑定(编辑弹窗按所在组回显角色用);为空返回全部绑定聚合去重。
|
||||
*/
|
||||
@GetMapping("/UserRoles")
|
||||
public List<UserRoleVo> userRoles(@RequestParam Integer userId) {
|
||||
public List<UserRoleVo> userRoles(@RequestParam Integer userId,
|
||||
@RequestParam(required = false) Integer groupId) {
|
||||
if (!accessScopeService.resolveCurrentScope().isSuper()
|
||||
&& !accessScopeService.userIntersectsScope(userId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return groupAuthzService.findUserRoles(userId);
|
||||
return groupAuthzService.findUserRoles(userId, groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属部门树(左侧上方「所属部门」区域展示用)。<br>
|
||||
* 树仅由「用户直接归属的组 + 其全部祖先组」构成(如:云平台-学校中学-语文组)。<br>
|
||||
* groupId 可选(上下文组):传入且用户确属该组时只画该组的祖先链——中间列表每行是「用户×组」对,
|
||||
* 所属部门只展示行所在组的链路,而非用户全部归属组。<br>
|
||||
* 非超管:目标用户归属组与数据范围无交集时返回空集合;且树按数据范围剪枝——
|
||||
* 学校管理员视角只展示到学校组(区域组/平台根不在其范围内,不再冒头到根节点)。
|
||||
*/
|
||||
@GetMapping("/UserGroupTree")
|
||||
public List<UserGroupNodeVo> userGroupTree(@RequestParam Integer userId) {
|
||||
public List<UserGroupNodeVo> userGroupTree(@RequestParam Integer userId,
|
||||
@RequestParam(required = false) Integer groupId) {
|
||||
if (!accessScopeService.resolveCurrentScope().isSuper()
|
||||
&& !accessScopeService.userIntersectsScope(userId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return groupAuthzService.findUserGroupTree(userId, accessScopeService.scopeGroupIds());
|
||||
return groupAuthzService.findUserGroupTree(userId, accessScopeService.scopeGroupIds(), groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,9 @@ import javax.validation.constraints.NotNull;
|
||||
* @author chenx
|
||||
* @date 2026/7/27
|
||||
* @description 用户-业务角色关系表(逻辑外键,无物理 FK)。<br>
|
||||
* 一个用户可绑定多个业务角色;一个角色也可绑定多个用户。<br>
|
||||
* 一行 = 用户 u 在组 g 下拥有角色 r;一个用户可在多个组下分别绑定角色,同一组下同一角色唯一。<br>
|
||||
* 组维度(2026-09-16):groupId = 绑定发生时所选的组(角色授权页添加用户弹窗左侧树选中组 / 用户编辑弹窗所在组),
|
||||
* NULL = 历史数据视为全局生效(代码侧兜底,不参与按组过滤的排除)。
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@@ -24,10 +26,11 @@ import javax.validation.constraints.NotNull;
|
||||
name = "sys_user_app_role_rel",
|
||||
indexes = {
|
||||
@Index(name = "idx_user_app_role_user", columnList = "user_id"),
|
||||
@Index(name = "idx_user_app_role_role", columnList = "role_id")
|
||||
@Index(name = "idx_user_app_role_role", columnList = "role_id"),
|
||||
@Index(name = "idx_user_app_role_group", columnList = "group_id")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_user_app_role", columnNames = {"user_id", "role_id"})
|
||||
@UniqueConstraint(name = "uk_user_app_role_group", columnNames = {"user_id", "role_id", "group_id"})
|
||||
}
|
||||
)
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||
@@ -46,4 +49,10 @@ public class SysUserAppRoleRel extends BaseEntity {
|
||||
@NotNull
|
||||
@Column(name = "role_id", nullable = false)
|
||||
private Integer roleId;
|
||||
|
||||
/**
|
||||
* 绑定发生时所选的组ID(逻辑外键 → SysGroup.id);NULL = 历史数据视为全局。<br>
|
||||
*/
|
||||
@Column(name = "group_id")
|
||||
private Integer groupId;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.yunhai.userbase.model.approle;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色绑定用户保存入参(全量覆盖,角色维度)。<br>
|
||||
* userIds 为该角色本次绑定的用户ID全量列表;传空列表即解绑该角色全部用户。<br>
|
||||
* 角色绑定用户保存入参(增量添加,角色维度)。<br>
|
||||
* bindings 为本次新增的「用户×组」绑定明细(组 = 弹窗左侧树当前选中组);已存在的 (userId, groupId) 跳过。<br>
|
||||
* 组维度改造(2026-09-16)后不再全量覆盖:覆盖会把其他组下的既有绑定一并删掉。
|
||||
*
|
||||
* @author chenx
|
||||
*/
|
||||
@@ -21,7 +23,7 @@ public class AppRoleUserBindRequest {
|
||||
private Integer roleId;
|
||||
|
||||
/**
|
||||
* 绑定的用户ID全量列表。<br>
|
||||
* 本次新增的「用户×组」绑定明细。<br>
|
||||
*/
|
||||
private List<Integer> userIds;
|
||||
private List<@Valid AppRoleUserBinding> bindings;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.yunhai.userbase.model.approle;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 角色绑定用户明细项:一行 = 用户在指定组下的绑定。<br>
|
||||
* groupId = 添加用户弹窗左侧树当前选中组(绑定发生时的组),部门列展示时按组ID实时拼路径。
|
||||
*
|
||||
* @author chenx
|
||||
*/
|
||||
@Data
|
||||
public class AppRoleUserBinding {
|
||||
|
||||
/** 用户ID。 */
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Integer userId;
|
||||
|
||||
/** 绑定发生时所选的组ID。 */
|
||||
@NotNull(message = "组ID不能为空")
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.yunhai.userbase.model.approle;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 角色已绑定用户列表项视图。<br>
|
||||
* 角色已绑定用户列表项视图(一行 = 一条「用户×组」绑定)。<br>
|
||||
* groupPath 为查询时按 groupId 实时拼接的组路径(不落库),scope 非空时按查看者数据范围裁剪——
|
||||
* 学校管理员只看到本校路径(如 /云海小学/教师组),超管看全路径。
|
||||
*
|
||||
* @author chenx
|
||||
*/
|
||||
@@ -18,4 +20,10 @@ public class AppRoleUserVo {
|
||||
|
||||
/** 真实姓名。 */
|
||||
private String realName;
|
||||
|
||||
/** 绑定发生时所选的组ID。 */
|
||||
private Integer groupId;
|
||||
|
||||
/** 组路径(查询时按 groupId 现算,scope 裁剪后)。 */
|
||||
private String groupPath;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户绑定角色保存入参(全量覆盖,用户维度)。<br>
|
||||
* roleIds 为该用户本次绑定的角色ID全量列表;传空列表即解绑该用户全部角色。<br>
|
||||
* 用户绑定角色保存入参(按组覆盖,用户维度)。<br>
|
||||
* roleIds 为该用户在 groupId 组下的角色ID全量列表;传空列表即解绑该用户在该组下的全部角色,
|
||||
* 不影响其在其他组的绑定;groupId 为空时兜底主组(历史调用方兼容)。<br>
|
||||
*
|
||||
* @author chenx
|
||||
*/
|
||||
@@ -21,7 +22,12 @@ public class UserAppRoleBindRequest {
|
||||
private Integer userId;
|
||||
|
||||
/**
|
||||
* 绑定的角色ID全量列表。<br>
|
||||
* 绑定组ID(可选):角色归属的组,空则兜底该用户主组。<br>
|
||||
*/
|
||||
private Integer groupId;
|
||||
|
||||
/**
|
||||
* 该组下绑定的角色ID全量列表。<br>
|
||||
*/
|
||||
private List<Integer> roleIds;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -20,6 +21,24 @@ public interface SysUserAppRoleRelRepository extends JpaRepository<SysUserAppRol
|
||||
|
||||
List<SysUserAppRoleRel> findByRoleId(Integer roleId);
|
||||
|
||||
/** 指定用户在指定组下的绑定(编辑弹窗按所在组回显/覆盖保存)。 */
|
||||
List<SysUserAppRoleRel> findByUserIdAndGroupId(Integer userId, Integer groupId);
|
||||
|
||||
/** 指定用户无组维度(NULL=历史数据视为全局)的绑定;用户不属于任何组时权限计算只认这些行。 */
|
||||
List<SysUserAppRoleRel> findByUserIdAndGroupIdIsNull(Integer userId);
|
||||
|
||||
/**
|
||||
* 权限计算取数:用户「当前所在组」上的绑定 ∪ NULL(历史数据视为全局)。<br>
|
||||
* groupIds 为空时勿调用(IN () 空集合 SQL 兼容性差),改走 {@link #findByUserIdAndGroupIdIsNull(Integer)}。
|
||||
*/
|
||||
@Query("SELECT r FROM SysUserAppRoleRel r WHERE r.userId = :userId "
|
||||
+ "AND (r.groupId IS NULL OR r.groupId IN :groupIds)")
|
||||
List<SysUserAppRoleRel> findEffectiveByUserId(@Param("userId") Integer userId,
|
||||
@Param("groupIds") Collection<Integer> groupIds);
|
||||
|
||||
/** 判断 (用户, 角色, 组) 绑定是否已存在(增量添加去重)。 */
|
||||
boolean existsByUserIdAndRoleIdAndGroupId(Integer userId, Integer roleId, Integer groupId);
|
||||
|
||||
/**
|
||||
* 按角色ID批量物理删除绑定记录(bulk delete 直接发 SQL)。<br>
|
||||
* 用于「角色绑定用户」全量覆盖式保存的先删后插,以及删除角色时级联清理。<br>
|
||||
@@ -43,9 +62,17 @@ public interface SysUserAppRoleRelRepository extends JpaRepository<SysUserAppRol
|
||||
void deleteByUserIdBulk(@Param("userId") Integer userId);
|
||||
|
||||
/**
|
||||
* 按角色ID + 用户ID 删除单条绑定记录(移除单个用户的角色关联,避免全量重提)。<br>
|
||||
* 按用户ID + 组ID 批量物理删除该组下的全部绑定(编辑弹窗按组覆盖保存的先删后插)。<br>
|
||||
*/
|
||||
@Modifying
|
||||
@Query("DELETE FROM SysUserAppRoleRel r WHERE r.roleId = :roleId AND r.userId = :userId")
|
||||
void deleteByRoleIdAndUserId(@Param("roleId") Integer roleId, @Param("userId") Integer userId);
|
||||
@Query("DELETE FROM SysUserAppRoleRel r WHERE r.userId = :userId AND r.groupId = :groupId")
|
||||
void deleteByUserIdAndGroupIdBulk(@Param("userId") Integer userId, @Param("groupId") Integer groupId);
|
||||
|
||||
/**
|
||||
* 按角色ID + 用户ID + 组ID 删除单条绑定记录(角色用户列表按行移除)。<br>
|
||||
*/
|
||||
@Modifying
|
||||
@Query("DELETE FROM SysUserAppRoleRel r WHERE r.roleId = :roleId AND r.userId = :userId AND r.groupId = :groupId")
|
||||
void deleteByRoleIdAndUserIdAndGroupId(@Param("roleId") Integer roleId, @Param("userId") Integer userId,
|
||||
@Param("groupId") Integer groupId);
|
||||
}
|
||||
|
||||
@@ -74,32 +74,38 @@ public interface IAppRoleService {
|
||||
void saveRoleModules(AppRoleModuleAssignRequest request);
|
||||
|
||||
/**
|
||||
* 查询角色已绑定的用户列表。<br>
|
||||
* 查询角色已绑定的用户列表(一行 = 一条「用户×组」绑定)。<br>
|
||||
* groupPath 为查询时按 groupId 实时拼接的组路径;scopeGroupIds 非空(非超管)时滤除
|
||||
* 绑定组不在范围内的行,且路径只拼到范围内的祖先为止(学校管理员只看到本校路径)。<br>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 用户列表
|
||||
* @param roleId 角色ID
|
||||
* @param scopeGroupIds 当前管理端数据范围组集合(null=不限制)
|
||||
* @return 绑定列表
|
||||
*/
|
||||
List<AppRoleUserVo> findRoleUsers(Integer roleId);
|
||||
List<AppRoleUserVo> findRoleUsers(Integer roleId, java.util.Set<Integer> scopeGroupIds);
|
||||
|
||||
/**
|
||||
* 角色绑定用户保存(角色维度,全量覆盖):bulk 先删该角色全部绑定再批量插入。<br>
|
||||
* 角色绑定用户保存(角色维度,增量添加):bindings 为「用户×组」明细,
|
||||
* 已存在的 (userId, groupId) 跳过;不做全量覆盖——覆盖会把其他组下的既有绑定删掉。<br>
|
||||
*
|
||||
* @param request 角色ID + 用户ID全量列表
|
||||
* @param request 角色ID + 绑定明细列表
|
||||
*/
|
||||
void saveRoleUsers(AppRoleUserBindRequest request);
|
||||
|
||||
/**
|
||||
* 用户绑定角色保存(用户维度,全量覆盖):bulk 先删该用户全部绑定再批量插入。<br>
|
||||
* 用户绑定角色保存(用户维度,按组覆盖):只覆盖该用户在 groupId 组下的绑定,
|
||||
* 不影响其在其他组的绑定;groupId 为空时兜底该用户主组。<br>
|
||||
*
|
||||
* @param request 用户ID + 角色ID全量列表
|
||||
* @param request 用户ID + 组ID + 该组下角色ID全量列表
|
||||
*/
|
||||
void saveUserRoles(UserAppRoleBindRequest request);
|
||||
|
||||
/**
|
||||
* 移除单个用户与角色的绑定关系(精确删除单条记录,非全量重提)。<br>
|
||||
* 移除单条「用户×组×角色」绑定(精确删除一行,非全量重提)。<br>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param userId 用户ID
|
||||
* @param roleId 角色ID
|
||||
* @param userId 用户ID
|
||||
* @param groupId 绑定组ID
|
||||
*/
|
||||
void removeRoleUser(Integer roleId, Integer userId);
|
||||
void removeRoleUser(Integer roleId, Integer userId, Integer groupId);
|
||||
}
|
||||
|
||||
@@ -104,26 +104,30 @@ public interface IGroupAuthzService {
|
||||
List<UserPermissionCategoryVo> findUserPermissionTree(Integer groupId, Integer userId);
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属的业务角色列表(中间下方「所属角色」区域展示用)。<br>
|
||||
* 返回该用户绑定的全部角色(含禁用角色,disabled 字段标记),普通角色回填所属应用名称。<br>
|
||||
* 查询用户所属的业务角色列表(权限一览「所属角色」/ 编辑弹窗角色回显)。<br>
|
||||
* 返回该用户绑定的角色(含禁用角色,disabled 字段标记),普通角色回填所属应用名称,按角色去重。<br>
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userId 用户ID
|
||||
* @param groupId 组ID(可选):非空时只取该组下的绑定(编辑弹窗按所在组回显);为空取全部绑定聚合
|
||||
* @return 用户所属角色列表
|
||||
*/
|
||||
List<UserRoleVo> findUserRoles(Integer userId);
|
||||
List<UserRoleVo> findUserRoles(Integer userId, Integer groupId);
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属部门树(左侧上方「所属部门」区域展示用)。<br>
|
||||
* 树仅由「用户直接归属的组 + 其全部祖先组」构成(如:云平台-学校中学-语文组),
|
||||
* 多个归属组时合并为一棵树(公共祖先只出现一次)。<br>
|
||||
* 数据范围剪枝:scopeGroupIds 非空时树节点只保留范围内的组——学校管理员视角
|
||||
* 区域组/根节点不在其范围内,学校组即成为展示树的根(不再冒头到平台根);null 表示不剪枝(超管)。
|
||||
* 区域组/根节点不在其范围内,学校组即成为展示树的根(不再冒头到平台根);null 表示不剪枝(超管)。<br>
|
||||
* 上下文收窄:contextGroupId 非空且该用户确属此组时,只画该组的祖先链(权限一览按行查看
|
||||
* 「用户×组」对时,所属部门只展示行所在组的链路,而非用户全部归属组);此时「本组」标记也指向该组。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param scopeGroupIds 当前管理端数据范围组集合(null=不限制)
|
||||
* @param userId 用户ID
|
||||
* @param scopeGroupIds 当前管理端数据范围组集合(null=不限制)
|
||||
* @param contextGroupId 上下文组ID(null=不收窄,画全部归属组链路)
|
||||
* @return 所属部门树(根节点列表);用户无组归属时返回空列表
|
||||
*/
|
||||
List<UserGroupNodeVo> findUserGroupTree(Integer userId, java.util.Set<Integer> scopeGroupIds);
|
||||
List<UserGroupNodeVo> findUserGroupTree(Integer userId, java.util.Set<Integer> scopeGroupIds, Integer contextGroupId);
|
||||
|
||||
/**
|
||||
* 权限一览:查询<b>组本身</b>的有效已授权应用模块树(点击「所属部门」树节点联动右侧面板用)。<br>
|
||||
|
||||
@@ -5,13 +5,16 @@ import com.yunhai.userbase.entity.AppModule;
|
||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
||||
import com.yunhai.userbase.entity.SysAppRole;
|
||||
import com.yunhai.userbase.entity.SysAppRoleModuleRel;
|
||||
import com.yunhai.userbase.entity.SysGroup;
|
||||
import com.yunhai.userbase.entity.SysUser;
|
||||
import com.yunhai.userbase.entity.SysUserAppRoleRel;
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.event.AppAuthzChangedEvent;
|
||||
import com.yunhai.userbase.model.approle.AppRoleAuthzAppVo;
|
||||
import com.yunhai.userbase.model.approle.AppRoleAuthzModuleVo;
|
||||
import com.yunhai.userbase.model.approle.AppRoleModuleAssignRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserBindRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserBinding;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserVo;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleQuery;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleRequest;
|
||||
@@ -21,9 +24,12 @@ import com.yunhai.userbase.repository.AppModuleRepository;
|
||||
import com.yunhai.userbase.repository.ApplicationInfoRepository;
|
||||
import com.yunhai.userbase.repository.SysAppRoleModuleRelRepository;
|
||||
import com.yunhai.userbase.repository.SysAppRoleRepository;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysUserAppRoleRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRepository;
|
||||
import com.yunhai.userbase.service.IAppRoleService;
|
||||
import com.yunhai.userbase.service.resolve.MainGroupResolver;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -56,7 +62,8 @@ import java.util.Set;
|
||||
* @description 应用业务角色服务实现。<br>
|
||||
* 角色创建:普通角色(allAppFlag=false,appId 必填,同应用下重名校验)/ 全应用角色(allAppFlag=true,appId 为空,全应用角色间重名校验)。<br>
|
||||
* 模块授权:sys_app_role_module_rel 全量覆盖式保存(bulk 先删后插);普通角色仅能授权本应用模块,全应用角色可跨应用。<br>
|
||||
* 用户绑定:sys_user_app_role_rel 支持角色维度 / 用户维度两个方向的全量覆盖保存。<br>
|
||||
* 用户绑定:sys_user_app_role_rel 带组维度(一行 = 用户×组×角色,2026-09-16)——
|
||||
* 角色维度增量添加(绑定发生时的组)、用户维度按组覆盖(编辑弹窗所在组),组路径查询时实时拼接不落库。
|
||||
*/
|
||||
@Service
|
||||
public class AppRoleService implements IAppRoleService {
|
||||
@@ -65,6 +72,9 @@ public class AppRoleService implements IAppRoleService {
|
||||
* 无应用类型;全应用授权范围须排除它——与 ApplicationInfoRepository「appKey > 100000」口径一致。 */
|
||||
private static final int USER_CENTER_APP_KEY = 100000;
|
||||
|
||||
/** 组根节点的 parentId 约定值(与 GroupAuthzService.ROOT_PARENT_ID 同语义)。 */
|
||||
private static final int ROOT_PARENT_ID = 0;
|
||||
|
||||
@Autowired
|
||||
private SysAppRoleRepository sysAppRoleRepository;
|
||||
|
||||
@@ -83,6 +93,15 @@ public class AppRoleService implements IAppRoleService {
|
||||
@Autowired
|
||||
private SysUserRepository sysUserRepository;
|
||||
|
||||
@Autowired
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
|
||||
@Autowired
|
||||
private MainGroupResolver mainGroupResolver;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@@ -274,10 +293,10 @@ public class AppRoleService implements IAppRoleService {
|
||||
publishAuthzEvent("saveRoleModules");
|
||||
}
|
||||
|
||||
// ===================== 用户角色绑定 =====================
|
||||
// ===================== 用户角色绑定(组维度:一行 = 用户×组×角色) =====================
|
||||
|
||||
@Override
|
||||
public List<AppRoleUserVo> findRoleUsers(Integer roleId) {
|
||||
public List<AppRoleUserVo> findRoleUsers(Integer roleId, Set<Integer> scopeGroupIds) {
|
||||
Objects.requireNonNull(roleId, "角色ID不能为空");
|
||||
if (!sysAppRoleRepository.existsById(roleId)) {
|
||||
throw new DException("角色不存在: " + roleId);
|
||||
@@ -286,16 +305,30 @@ public class AppRoleService implements IAppRoleService {
|
||||
if (rels.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// scope 非空(非超管)时只保留绑定组在范围内的行(与 searchUsers 同口径)
|
||||
List<SysUserAppRoleRel> visibleRels = rels;
|
||||
if (scopeGroupIds != null) {
|
||||
visibleRels = new ArrayList<>();
|
||||
for (SysUserAppRoleRel rel : rels) {
|
||||
if (rel.getGroupId() != null && scopeGroupIds.contains(rel.getGroupId())) {
|
||||
visibleRels.add(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<Integer> userIds = new LinkedHashSet<>();
|
||||
for (SysUserAppRoleRel rel : rels) {
|
||||
userIds.add(rel.getUserId());
|
||||
for (SysUserAppRoleRel rel : visibleRels) {
|
||||
if (rel.getUserId() != null) {
|
||||
userIds.add(rel.getUserId());
|
||||
}
|
||||
}
|
||||
Map<Integer, SysUser> userMap = new HashMap<>();
|
||||
for (SysUser user : sysUserRepository.findAllById(userIds)) {
|
||||
userMap.put(user.getId(), user);
|
||||
}
|
||||
// 部门路径不落库:按绑定行的 groupId 实时拼路径,scope 非空时裁到范围内祖先为止
|
||||
Map<Integer, SysGroup> groupMap = buildAllGroupMap();
|
||||
List<AppRoleUserVo> result = new ArrayList<>();
|
||||
for (SysUserAppRoleRel rel : rels) {
|
||||
for (SysUserAppRoleRel rel : visibleRels) {
|
||||
SysUser user = userMap.get(rel.getUserId());
|
||||
if (user == null) {
|
||||
continue;
|
||||
@@ -304,6 +337,8 @@ public class AppRoleService implements IAppRoleService {
|
||||
vo.setId(user.getId());
|
||||
vo.setAccount(user.getAccount());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setGroupId(rel.getGroupId());
|
||||
vo.setGroupPath(buildScopedGroupPath(rel.getGroupId(), groupMap, scopeGroupIds));
|
||||
result.add(vo);
|
||||
}
|
||||
return result;
|
||||
@@ -317,21 +352,29 @@ public class AppRoleService implements IAppRoleService {
|
||||
if (!sysAppRoleRepository.existsById(roleId)) {
|
||||
throw new DException("角色不存在: " + roleId);
|
||||
}
|
||||
// 去重并校验用户均存在
|
||||
List<Integer> userIds = new ArrayList<>(new LinkedHashSet<>(
|
||||
request.getUserIds() == null ? Collections.emptyList() : request.getUserIds()));
|
||||
for (Integer userId : userIds) {
|
||||
if (!sysUserRepository.existsById(userId)) {
|
||||
throw new DException("用户不存在: " + userId);
|
||||
}
|
||||
}
|
||||
// 全量覆盖:bulk 先删旧绑定,再批量插入
|
||||
sysUserAppRoleRelRepository.deleteByRoleIdBulk(roleId);
|
||||
// 增量添加「用户×组」绑定(组=弹窗左侧树当前选中组),已存在的 (userId, groupId) 跳过;
|
||||
// 不做全量覆盖——覆盖会把该角色在其他组下的既有绑定一并删掉
|
||||
List<AppRoleUserBinding> bindings = request.getBindings() == null
|
||||
? Collections.emptyList() : request.getBindings();
|
||||
List<SysUserAppRoleRel> batch = new ArrayList<>();
|
||||
for (Integer userId : userIds) {
|
||||
for (AppRoleUserBinding binding : bindings) {
|
||||
if (binding == null || binding.getUserId() == null || binding.getGroupId() == null) {
|
||||
continue;
|
||||
}
|
||||
if (!sysUserRepository.existsById(binding.getUserId())) {
|
||||
throw new DException("用户不存在: " + binding.getUserId());
|
||||
}
|
||||
if (!sysGroupRepository.existsById(binding.getGroupId())) {
|
||||
throw new DException("系统组不存在: " + binding.getGroupId());
|
||||
}
|
||||
if (sysUserAppRoleRelRepository.existsByUserIdAndRoleIdAndGroupId(
|
||||
binding.getUserId(), roleId, binding.getGroupId())) {
|
||||
continue;
|
||||
}
|
||||
SysUserAppRoleRel rel = new SysUserAppRoleRel();
|
||||
rel.setUserId(userId);
|
||||
rel.setUserId(binding.getUserId());
|
||||
rel.setRoleId(roleId);
|
||||
rel.setGroupId(binding.getGroupId());
|
||||
batch.add(rel);
|
||||
}
|
||||
if (!batch.isEmpty()) {
|
||||
@@ -348,6 +391,26 @@ public class AppRoleService implements IAppRoleService {
|
||||
if (!sysUserRepository.existsById(userId)) {
|
||||
throw new DException("用户不存在: " + userId);
|
||||
}
|
||||
// 绑定组:入参为空兜底主组(历史调用方兼容);组必须属于该用户实际所在组,防止给任意组写绑定
|
||||
List<SysUserGroupRel> userRels = sysUserGroupRelRepository.findByUserId(userId);
|
||||
Integer groupId = request.getGroupId();
|
||||
if (groupId == null) {
|
||||
SysUserGroupRel mainRel = mainGroupResolver.resolve(userRels);
|
||||
groupId = mainRel != null ? mainRel.getGroupId() : null;
|
||||
}
|
||||
if (groupId == null) {
|
||||
throw new DException("用户未归属任何组,无法绑定角色");
|
||||
}
|
||||
boolean inGroup = false;
|
||||
for (SysUserGroupRel rel : userRels) {
|
||||
if (groupId.equals(rel.getGroupId())) {
|
||||
inGroup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inGroup) {
|
||||
throw new DException("绑定组不在该用户所在组范围内: " + groupId);
|
||||
}
|
||||
// 去重并校验角色均存在
|
||||
List<Integer> roleIds = new ArrayList<>(new LinkedHashSet<>(
|
||||
request.getRoleIds() == null ? Collections.emptyList() : request.getRoleIds()));
|
||||
@@ -356,13 +419,14 @@ public class AppRoleService implements IAppRoleService {
|
||||
throw new DException("角色不存在: " + roleId);
|
||||
}
|
||||
}
|
||||
// 全量覆盖:bulk 先删旧绑定,再批量插入
|
||||
sysUserAppRoleRelRepository.deleteByUserIdBulk(userId);
|
||||
// 按组覆盖:只删该用户在该组下的绑定,其他组绑定不受影响
|
||||
sysUserAppRoleRelRepository.deleteByUserIdAndGroupIdBulk(userId, groupId);
|
||||
List<SysUserAppRoleRel> batch = new ArrayList<>();
|
||||
for (Integer roleId : roleIds) {
|
||||
SysUserAppRoleRel rel = new SysUserAppRoleRel();
|
||||
rel.setUserId(userId);
|
||||
rel.setRoleId(roleId);
|
||||
rel.setGroupId(groupId);
|
||||
batch.add(rel);
|
||||
}
|
||||
if (!batch.isEmpty()) {
|
||||
@@ -373,16 +437,17 @@ public class AppRoleService implements IAppRoleService {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void removeRoleUser(Integer roleId, Integer userId) {
|
||||
public void removeRoleUser(Integer roleId, Integer userId, Integer groupId) {
|
||||
Objects.requireNonNull(roleId, "角色ID不能为空");
|
||||
Objects.requireNonNull(userId, "用户ID不能为空");
|
||||
Objects.requireNonNull(groupId, "组ID不能为空");
|
||||
if (!sysAppRoleRepository.existsById(roleId)) {
|
||||
throw new DException("角色不存在: " + roleId);
|
||||
}
|
||||
if (!sysUserRepository.existsById(userId)) {
|
||||
throw new DException("用户不存在: " + userId);
|
||||
}
|
||||
sysUserAppRoleRelRepository.deleteByRoleIdAndUserId(roleId, userId);
|
||||
sysUserAppRoleRelRepository.deleteByRoleIdAndUserIdAndGroupId(roleId, userId, groupId);
|
||||
publishAuthzEvent("removeRoleUser");
|
||||
}
|
||||
|
||||
@@ -394,6 +459,42 @@ public class AppRoleService implements IAppRoleService {
|
||||
eventPublisher.publishEvent(new AppAuthzChangedEvent(this, "AppRoleService." + sourceOp));
|
||||
}
|
||||
|
||||
/** 全量组快照(组表量级小,与 GroupAuthzService.buildAllGroupMap 同思路)。 */
|
||||
private Map<Integer, SysGroup> buildAllGroupMap() {
|
||||
Map<Integer, SysGroup> map = new HashMap<>();
|
||||
for (SysGroup g : sysGroupRepository.findAll()) {
|
||||
map.put(g.getId(), g);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* scope 感知的组路径拼接:自绑定组向上收集祖先名,遇到不在 scope 内的祖先即停(不含)。
|
||||
* scope 为 null(超管)拼全路径;学校管理员只看到本校路径(如 /云海小学/教师组),
|
||||
* 平台/局端看到各自的范围内路径。
|
||||
*/
|
||||
private String buildScopedGroupPath(Integer groupId, Map<Integer, SysGroup> groupMap, Set<Integer> scopeGroupIds) {
|
||||
if (groupId == null) {
|
||||
return "";
|
||||
}
|
||||
List<String> names = new ArrayList<>();
|
||||
Set<Integer> visited = new HashSet<>();
|
||||
Integer cursor = groupId;
|
||||
while (cursor != null && cursor != ROOT_PARENT_ID && visited.add(cursor)) {
|
||||
if (scopeGroupIds != null && !scopeGroupIds.contains(cursor)) {
|
||||
break;
|
||||
}
|
||||
SysGroup g = groupMap.get(cursor);
|
||||
if (g == null) {
|
||||
break;
|
||||
}
|
||||
names.add(g.getName() == null ? "" : g.getName());
|
||||
cursor = g.getParentId();
|
||||
}
|
||||
Collections.reverse(names);
|
||||
return names.isEmpty() ? "" : "/" + String.join("/", names);
|
||||
}
|
||||
|
||||
// ===================== 私有工具 =====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -1259,12 +1259,15 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserRoleVo> findUserRoles(Integer userId) {
|
||||
public List<UserRoleVo> findUserRoles(Integer userId, Integer groupId) {
|
||||
Objects.requireNonNull(userId, "用户ID不能为空");
|
||||
if (!sysUserRepository.existsById(userId)) {
|
||||
throw new DException("用户不存在");
|
||||
}
|
||||
List<SysUserAppRoleRel> rels = sysUserAppRoleRelRepository.findByUserId(userId);
|
||||
// groupId 非空 = 只取该组下的绑定(编辑弹窗按所在组回显);为空 = 全部绑定聚合去重(权限一览展示)
|
||||
List<SysUserAppRoleRel> rels = groupId == null
|
||||
? sysUserAppRoleRelRepository.findByUserId(userId)
|
||||
: sysUserAppRoleRelRepository.findByUserIdAndGroupId(userId, groupId);
|
||||
if (rels.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -1310,7 +1313,7 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserGroupNodeVo> findUserGroupTree(Integer userId, Set<Integer> scopeGroupIds) {
|
||||
public List<UserGroupNodeVo> findUserGroupTree(Integer userId, Set<Integer> scopeGroupIds, Integer contextGroupId) {
|
||||
Objects.requireNonNull(userId, "用户ID不能为空");
|
||||
if (!sysUserRepository.existsById(userId)) {
|
||||
throw new DException("用户不存在");
|
||||
@@ -1329,6 +1332,13 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
if (selfGroupIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 1.5 上下文收窄:指定组且用户确属该组时只画该组链路(权限一览按行查看「用户×组」对);
|
||||
// 「本组」标记同步指向该组——主组可能在被收窄掉的链路上,沿用主组会导致整棵树没有「本组」
|
||||
if (contextGroupId != null && selfGroupIds.contains(contextGroupId)) {
|
||||
selfGroupIds = new LinkedHashSet<>();
|
||||
selfGroupIds.add(contextGroupId);
|
||||
mainGroupId = contextGroupId;
|
||||
}
|
||||
// 2. 祖先链并集(含自身);多个归属组共享祖先时只出现一次
|
||||
Map<Integer, SysGroup> allGroupMap = buildAllGroupMap();
|
||||
Set<Integer> nodeIds = new LinkedHashSet<>();
|
||||
@@ -1851,10 +1861,21 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
|
||||
/**
|
||||
* 收集用户经「启用状态」业务角色获得授权的模块ID集合(角色授权并集的角色侧输入)。<br>
|
||||
* 组维度(2026-09-16):只算「用户当前所在组」上的绑定(group_id ∈ 当前组集合),
|
||||
* group_id 为 NULL 的历史行视为全局生效(兜底防迁移脚本未跑导致权限丢失)。<br>
|
||||
* 调整组不删绑定:旧组上的绑定因组关系重建而自然失效,调回该组自动恢复。<br>
|
||||
* 禁用角色(disabled=true)不参与权限计算;用户无绑定或角色全被禁用时返回空集合。<br>
|
||||
*/
|
||||
private Set<Integer> collectEnabledRoleModuleIds(Integer userId) {
|
||||
List<SysUserAppRoleRel> rels = sysUserAppRoleRelRepository.findByUserId(userId);
|
||||
List<Integer> userGroupIds = new ArrayList<>();
|
||||
for (SysUserGroupRel rel : sysUserGroupRelRepository.findByUserId(userId)) {
|
||||
if (rel.getGroupId() != null) {
|
||||
userGroupIds.add(rel.getGroupId());
|
||||
}
|
||||
}
|
||||
List<SysUserAppRoleRel> rels = userGroupIds.isEmpty()
|
||||
? sysUserAppRoleRelRepository.findByUserIdAndGroupIdIsNull(userId)
|
||||
: sysUserAppRoleRelRepository.findEffectiveByUserId(userId, userGroupIds);
|
||||
if (rels.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
-- ============================================================================
|
||||
-- 用户角色绑定加组维度迁移(2026-09-16)· 直执行完整版
|
||||
-- 适用:新后端尚未启动过(ddl-auto=update 还没建结构)的情况;逐条执行,勿整段粘贴。
|
||||
-- 与启动先后无关:若新后端已启动过,结构语句会报"已存在",按下述忽略规则处理即可。
|
||||
-- 不幂等的语句及忽略规则(报错=已做过,视为成功):
|
||||
-- ① 报 1060 Duplicate column name → group_id 列已存在,忽略
|
||||
-- ② 报 1061 Duplicate key name → 索引/约束已存在,忽略
|
||||
-- ④ 报 1091 index doesn't exist → 旧约束已删过,忽略
|
||||
-- 回填语句(③)本身幂等,重复执行 0 行。
|
||||
-- ============================================================================
|
||||
|
||||
-- ① 加列(只存组ID,不存路径字符串;部门列展示时按组ID实时拼路径)
|
||||
ALTER TABLE sys_user_app_role_rel ADD COLUMN group_id INT NULL AFTER role_id;
|
||||
|
||||
-- ② 加新唯一约束 + 组维度索引
|
||||
ALTER TABLE sys_user_app_role_rel ADD UNIQUE KEY uk_user_app_role_group (user_id, role_id, group_id);
|
||||
ALTER TABLE sys_user_app_role_rel ADD INDEX idx_user_app_role_group (group_id);
|
||||
|
||||
-- ③ 存量回填:各绑定行挂到该用户的主组(is_main=1 优先,无主组则取最小 rel.id 的组)
|
||||
UPDATE sys_user_app_role_rel r
|
||||
JOIN (
|
||||
SELECT rel.user_id,
|
||||
SUBSTRING_INDEX(GROUP_CONCAT(rel.group_id ORDER BY rel.is_main DESC, rel.id ASC), ',', 1) + 0 AS main_group_id
|
||||
FROM sys_user_group_rel rel
|
||||
WHERE rel.group_id IS NOT NULL
|
||||
GROUP BY rel.user_id
|
||||
) m ON m.user_id = r.user_id
|
||||
SET r.group_id = m.main_group_id
|
||||
WHERE r.group_id IS NULL;
|
||||
|
||||
-- ④ 删除旧二元唯一约束(不删则同用户跨组绑同角色必撞 DuplicateKey)
|
||||
ALTER TABLE sys_user_app_role_rel DROP INDEX uk_user_app_role;
|
||||
|
||||
-- ============================================================================
|
||||
-- 自检:
|
||||
-- 1) 应返回 0 行:有组关系的用户却仍挂着 NULL 绑定(回填漏网)
|
||||
SELECT r.id, r.user_id, r.role_id
|
||||
FROM sys_user_app_role_rel r
|
||||
JOIN sys_user_group_rel g ON g.user_id = r.user_id AND g.group_id IS NOT NULL
|
||||
WHERE r.group_id IS NULL
|
||||
GROUP BY r.id, r.user_id, r.role_id;
|
||||
|
||||
-- 2) 应只剩新约束:uk_user_app_role_group 在列,uk_user_app_role 不在列
|
||||
SHOW INDEX FROM sys_user_app_role_rel WHERE Key_name LIKE 'uk%';
|
||||
@@ -4,11 +4,14 @@ import com.yunhai.userbase.entity.AppCategory;
|
||||
import com.yunhai.userbase.entity.AppModule;
|
||||
import com.yunhai.userbase.entity.SysAppRole;
|
||||
import com.yunhai.userbase.entity.SysGroupAppRel;
|
||||
import com.yunhai.userbase.entity.SysUserAppRoleRel;
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.event.AppAuthzChangedEvent;
|
||||
import com.yunhai.userbase.model.appcategory.AppCategoryRequest;
|
||||
import com.yunhai.userbase.model.appmodule.AppModuleRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleModuleAssignRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserBindRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserBinding;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleRequest;
|
||||
import com.yunhai.userbase.model.approle.UserAppRoleBindRequest;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoRequest;
|
||||
@@ -23,9 +26,11 @@ import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysSchoolAppOverrideRepository;
|
||||
import com.yunhai.userbase.repository.SysSchoolCategoryOverrideRepository;
|
||||
import com.yunhai.userbase.repository.SysUserAppRoleRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRepository;
|
||||
import com.yunhai.userbase.enums.AccessLevelEnum;
|
||||
import com.yunhai.userbase.service.resolve.AccessScope;
|
||||
import com.yunhai.userbase.service.resolve.MainGroupResolver;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -35,9 +40,11 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -67,6 +74,10 @@ class AppAuthzEventPublishTest {
|
||||
@Mock
|
||||
private SysUserRepository sysUserRepository;
|
||||
@Mock
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
@Mock
|
||||
private MainGroupResolver mainGroupResolver;
|
||||
@Mock
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
// AppCategoryService
|
||||
@@ -154,9 +165,14 @@ class AppAuthzEventPublishTest {
|
||||
void appRole_saveRoleUsers_publishes() {
|
||||
when(sysAppRoleRepository.existsById(1)).thenReturn(true);
|
||||
when(sysUserRepository.existsById(2)).thenReturn(true);
|
||||
when(sysGroupRepository.existsById(3)).thenReturn(true);
|
||||
when(sysUserAppRoleRelRepository.existsByUserIdAndRoleIdAndGroupId(2, 1, 3)).thenReturn(false);
|
||||
AppRoleUserBinding binding = new AppRoleUserBinding();
|
||||
binding.setUserId(2);
|
||||
binding.setGroupId(3);
|
||||
AppRoleUserBindRequest request = new AppRoleUserBindRequest();
|
||||
request.setRoleId(1);
|
||||
request.setUserIds(Collections.singletonList(2));
|
||||
request.setBindings(Collections.singletonList(binding));
|
||||
appRoleService.saveRoleUsers(request);
|
||||
assertEquals("AppRoleService.saveRoleUsers", capturedOp().getSourceOp());
|
||||
}
|
||||
@@ -165,8 +181,14 @@ class AppAuthzEventPublishTest {
|
||||
void appRole_saveUserRoles_publishes() {
|
||||
when(sysUserRepository.existsById(2)).thenReturn(true);
|
||||
when(sysAppRoleRepository.existsById(1)).thenReturn(true);
|
||||
SysUserGroupRel rel = new SysUserGroupRel();
|
||||
rel.setUserId(2);
|
||||
rel.setGroupId(3);
|
||||
when(sysUserGroupRelRepository.findByUserId(2)).thenReturn(Collections.singletonList(rel));
|
||||
when(mainGroupResolver.resolve(anyList())).thenReturn(rel);
|
||||
UserAppRoleBindRequest request = new UserAppRoleBindRequest();
|
||||
request.setUserId(2);
|
||||
request.setGroupId(3);
|
||||
request.setRoleIds(Collections.singletonList(1));
|
||||
appRoleService.saveUserRoles(request);
|
||||
assertEquals("AppRoleService.saveUserRoles", capturedOp().getSourceOp());
|
||||
@@ -176,7 +198,7 @@ class AppAuthzEventPublishTest {
|
||||
void appRole_removeRoleUser_publishes() {
|
||||
when(sysAppRoleRepository.existsById(1)).thenReturn(true);
|
||||
when(sysUserRepository.existsById(2)).thenReturn(true);
|
||||
appRoleService.removeRoleUser(1, 2);
|
||||
appRoleService.removeRoleUser(1, 2, 3);
|
||||
assertEquals("AppRoleService.removeRoleUser", capturedOp().getSourceOp());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user