fix: 数据权限调整后引发的问题修复

This commit is contained in:
Ray.Hao
2026-02-14 10:56:24 +08:00
parent 8df1252ff8
commit d379e30d3f
9 changed files with 134 additions and 61 deletions

View File

@@ -271,15 +271,10 @@ CREATE TABLE `sys_role` (
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'ROOT', 1, 1, 1, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (2, '系统管理员', 'ADMIN', 2, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (3, '访问游客', 'GUEST', 3, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (4, '系统管理员1', 'ADMIN1', 4, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (5, '系统管理员2', 'ADMIN2', 5, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (6, '系统管理员3', 'ADMIN3', 6, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (7, '系统管理员4', 'ADMIN4', 7, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (8, '系统管理员5', 'ADMIN5', 8, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (9, '系统管理员6', 'ADMIN6', 9, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (10, '系统管理员7', 'ADMIN7', 10, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (11, '系统管理员8', 'ADMIN8', 11, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (12, '系统管理员9', 'ADMIN9', 12, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (4, '部门主管', 'DEPT_MANAGER', 4, 1, 2, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (5, '部门成员', 'DEPT_MEMBER', 5, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (6, '普通员工', 'EMPLOYEE', 6, 1, 4, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (7, '自定义权限用户', 'CUSTOM_USER', 7, 1, 5, NULL, now(), NULL, now(), 0);
-- ----------------------------
-- Table structure for sys_role_menu
@@ -301,6 +296,12 @@ CREATE TABLE `sys_role_dept` (
UNIQUE INDEX `uk_roleid_deptid`(`role_id` ASC, `dept_id` ASC) USING BTREE COMMENT '角色部门唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色部门关联表';
-- ----------------------------
-- Records of sys_role_dept
-- ----------------------------
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 2);
-- ============================================
-- 系统管理员角色菜单权限role_id=2
-- 顶级目录
@@ -315,6 +316,22 @@ INSERT INTO `sys_role_menu` VALUES (2, 251), (2, 2511), (2, 2512), (2, 2513), (2
INSERT INTO `sys_role_menu` VALUES (2, 260);
INSERT INTO `sys_role_menu` VALUES (2, 270), (2, 2701), (2, 2702), (2, 2703), (2, 2704), (2, 2705);
INSERT INTO `sys_role_menu` VALUES (2, 280), (2, 2801), (2, 2802), (2, 2803), (2, 2804), (2, 2805), (2, 2806);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 210), (4, 2101), (4, 2102), (4, 2103), (4, 2104), (4, 2105), (4, 2106), (4, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 220), (4, 2201), (4, 2202), (4, 2203), (4, 2204), (4, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 210), (5, 2101), (5, 2102), (5, 2103), (5, 2104), (5, 2105), (5, 2106), (5, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 220), (5, 2201), (5, 2202), (5, 2203), (5, 2204), (5, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 210), (6, 2101), (6, 2102), (6, 2103), (6, 2104), (6, 2105), (6, 2106), (6, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 220), (6, 2201), (6, 2202), (6, 2203), (6, 2204), (6, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 210), (7, 2101), (7, 2102), (7, 2103), (7, 2104), (7, 2105), (7, 2106), (7, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 220), (7, 2201), (7, 2202), (7, 2203), (7, 2204), (7, 2205);
-- 代码生成
INSERT INTO `sys_role_menu` VALUES (2, 310);
-- 平台文档
@@ -358,6 +375,10 @@ CREATE TABLE `sys_user` (
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (6, 'employee', '普通员工', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 2, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345682', 1, 'employee@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (7, 'custom_user', '自定义权限用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345683', 1, 'custom@youlaitech.com', now(), NULL, now(), NULL, 0);
-- ----------------------------
-- Table structure for sys_user_role
@@ -372,9 +393,13 @@ CREATE TABLE `sys_user_role` (
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
INSERT IGNORE INTO `sys_user_role` VALUES (1, 1);
INSERT IGNORE INTO `sys_user_role` VALUES (2, 2);
INSERT IGNORE INTO `sys_user_role` VALUES (3, 3);
INSERT IGNORE INTO `sys_user_role` VALUES (4, 4);
INSERT IGNORE INTO `sys_user_role` VALUES (5, 5);
INSERT IGNORE INTO `sys_user_role` VALUES (6, 6);
INSERT IGNORE INTO `sys_user_role` VALUES (7, 7);
-- ----------------------------

View File

@@ -14,13 +14,8 @@ import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import java.lang.reflect.Method;
import java.util.List;
@@ -89,7 +84,7 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
return where;
}
// 使用并集策略过滤
return dataScopeFilterWithUnion(annotation, dataScopes, where);
return dataScopeFilterWithUnion(mappedStatementId, annotation, dataScopes, where);
}
}
return where;
@@ -119,7 +114,8 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
* @param where 原始查询条件
* @return 追加权限过滤后的查询条件
*/
private Expression dataScopeFilterWithUnion(DataPermission annotation, List<RoleDataScope> dataScopes, Expression where) {
@SneakyThrows
private Expression dataScopeFilterWithUnion(String mappedStatementId, DataPermission annotation, List<RoleDataScope> dataScopes, Expression where) {
String deptAlias = annotation.deptAlias();
String deptIdColumnName = annotation.deptIdColumnName();
String userAlias = annotation.userAlias();
@@ -145,13 +141,17 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
}
// 用括号包裹并集条件
Expression finalExpression = new ParenthesedExpressionList<>(unionExpression);
Expression finalExpression = CCJSqlParserUtil.parseCondExpression("(" + unionExpression + ")");
if (where == null) {
log.debug("DataPermission applied. mappedStatementId={}, segment={}", mappedStatementId, finalExpression);
return finalExpression;
}
return new AndExpression(where, finalExpression);
Expression combined = new AndExpression(where, finalExpression);
log.debug("DataPermission applied. mappedStatementId={}, originWhere={}, segment={}, combined={}",
mappedStatementId, where, finalExpression, combined);
return combined;
}
/**
@@ -226,35 +226,15 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
* @param deptId 部门ID
* @return IN 子查询表达式
*/
@SneakyThrows
private Expression buildDeptAndSubExpression(Column deptColumn, Long deptId) {
// 构建子查询: SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path)
PlainSelect subSelectBody = new PlainSelect();
subSelectBody.setFromItem(new Table(DEPT_TABLE));
subSelectBody.addSelectItems(new Column(DEPT_ID_COLUMN));
// WHERE id = ?
EqualsTo idEquals = new EqualsTo();
idEquals.setLeftExpression(new Column(DEPT_ID_COLUMN));
idEquals.setRightExpression(new LongValue(deptId));
// FIND_IN_SET(?, tree_path)
Function findInSet = new Function();
findInSet.setName("FIND_IN_SET");
findInSet.setParameters(new ExpressionList<>(
new LongValue(deptId),
new Column(DEPT_TREE_PATH_COLUMN)
));
// WHERE id = ? OR FIND_IN_SET(?, tree_path)
OrExpression whereClause = new OrExpression(idEquals, findInSet);
subSelectBody.setWhere(whereClause);
// 构建子查询
ParenthesedSelect subSelect = new ParenthesedSelect();
subSelect.setSelect(subSelectBody);
// 构建 IN 表达式
return new InExpression(deptColumn, subSelect);
// 使用字符串解析,避免不同 JSqlParser 版本下 InExpression/ItemsList 渲染差异导致 SQL 语法错误
// SQL: dept_id IN (SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path))
String columnName = deptColumn.toString();
String sql = columnName + " IN (SELECT " + DEPT_ID_COLUMN + " FROM " + DEPT_TABLE +
" WHERE " + DEPT_ID_COLUMN + " = " + deptId +
" OR FIND_IN_SET(" + deptId + ", " + DEPT_TREE_PATH_COLUMN + "))";
return CCJSqlParserUtil.parseCondExpression(sql);
}
/**
@@ -266,6 +246,7 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
* @param customDeptIds 自定义部门ID列表
* @return IN 表达式,如果没有部门则返回 1=0
*/
@SneakyThrows
private Expression buildCustomDeptExpression(Column deptColumn, List<Long> customDeptIds) {
if (CollectionUtil.isEmpty(customDeptIds)) {
// 没有自定义部门,返回 1=0无权限
@@ -275,13 +256,11 @@ public class MyDataPermissionHandler implements DataPermissionHandler {
return falseCondition;
}
// 构建 IN 表达式列表
ExpressionList<Expression> deptIdList = new ExpressionList<>();
for (Long deptId : customDeptIds) {
deptIdList.addExpression(new LongValue(deptId));
}
return new InExpression(deptColumn, deptIdList);
// 使用字符串解析,确保渲染始终为 IN (..)
String columnName = deptColumn.toString();
String ids = customDeptIds.stream().map(String::valueOf).reduce((a, b) -> a + "," + b).orElse("");
String sql = columnName + " IN (" + ids + ")";
return CCJSqlParserUtil.parseCondExpression(sql);
}
}

View File

@@ -20,6 +20,10 @@ import java.util.List;
@Mapper(componentModel = "spring")
public interface RoleConverter {
@Mapping(target = "dataScope", source = "dataScope")
@Mapping(target = "dataScopeLabel", expression = "java(com.youlai.boot.common.enums.DataScopeEnum.getByValue(role.getDataScope()) == null ? null : com.youlai.boot.common.enums.DataScopeEnum.getByValue(role.getDataScope()).getLabel())")
RolePageVO toPageVo(Role role);
Page<RolePageVO> toPageVo(Page<Role> page);
@Mappings({

View File

@@ -19,4 +19,12 @@ public interface UserRoleMapper extends BaseMapper<UserRole> {
* @param roleId 角色ID
*/
int countUsersByRoleId(Long roleId);
/**
* 获取角色绑定的用户ID集合
*
* @param roleId 角色ID
* @return 用户ID集合
*/
java.util.List<Long> listUserIdsByRoleId(Long roleId);
}

View File

@@ -25,11 +25,15 @@ public class RolePageVO {
@Schema(description="排序")
private Integer sort;
@Schema(description="数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)")
private Integer dataScope;
@Schema(description="数据权限名称")
private String dataScopeLabel;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -30,4 +30,12 @@ public interface UserRoleService extends IService<UserRole> {
* @return true已分配 false未分配
*/
boolean hasAssignedUsers(Long roleId);
/**
* 获取角色绑定的用户ID集合
*
* @param roleId 角色ID
* @return 用户ID集合
*/
List<Long> listUserIdsByRoleId(Long roleId);
}

View File

@@ -4,9 +4,11 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.security.token.TokenManager;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.common.constant.SystemConstants;
import com.youlai.boot.common.enums.DataScopeEnum;
import com.youlai.boot.core.exception.BusinessException;
import com.youlai.boot.security.model.RoleDataScope;
@@ -17,7 +19,6 @@ import com.youlai.boot.system.model.entity.RoleMenu;
import com.youlai.boot.system.model.form.RoleForm;
import com.youlai.boot.system.model.query.RoleQuery;
import com.youlai.boot.system.model.vo.RolePageVO;
import com.youlai.boot.common.constant.SystemConstants;
import com.youlai.boot.common.model.Option;
import com.youlai.boot.security.util.SecurityUtils;
import com.youlai.boot.system.service.RoleDeptService;
@@ -47,6 +48,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
private final RoleMenuService roleMenuService;
private final RoleDeptService roleDeptService;
private final UserRoleService userRoleService;
private final TokenManager tokenManager;
private final RoleConverter roleConverter;
/**
@@ -111,9 +113,14 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
// 编辑角色时,判断角色是否存在
Role oldRole = null;
List<Long> oldDeptIds = null;
if (roleId != null) {
oldRole = this.getById(roleId);
Assert.isTrue(oldRole != null, "角色不存在");
if (DataScopeEnum.CUSTOM.getValue().equals(oldRole.getDataScope())) {
oldDeptIds = roleDeptService.getDeptIdsByRoleId(roleId);
}
}
String roleCode = roleForm.getCode();
@@ -147,6 +154,25 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
)) {
roleMenuService.refreshRolePermsCache(oldRole.getCode(), roleCode);
}
// 数据权限发生变化时失效该角色关联用户的登录态JWT tokenVersion
if (oldRole != null) {
boolean dataScopeChanged = !ObjectUtil.equals(oldRole.getDataScope(), roleForm.getDataScope());
boolean customDeptChanged = false;
if (!dataScopeChanged && DataScopeEnum.CUSTOM.getValue().equals(roleForm.getDataScope())) {
List<Long> newDeptIds = roleForm.getDeptIds() != null ? roleForm.getDeptIds() : List.of();
List<Long> oldIds = oldDeptIds != null ? oldDeptIds : List.of();
customDeptChanged = !new java.util.HashSet<>(oldIds).equals(new java.util.HashSet<>(newDeptIds));
}
if (dataScopeChanged || customDeptChanged) {
List<Long> userIds = userRoleService.listUserIdsByRoleId(savedRoleId);
if (CollectionUtil.isNotEmpty(userIds)) {
userIds.forEach(tokenManager::invalidateUserSessions);
}
}
}
}
return result;
}

View File

@@ -94,4 +94,12 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
int count = this.baseMapper.countUsersByRoleId(roleId);
return count > 0;
}
@Override
public List<Long> listUserIdsByRoleId(Long roleId) {
if (roleId == null) {
return List.of();
}
return this.baseMapper.listUserIdsByRoleId(roleId);
}
}

View File

@@ -26,4 +26,15 @@
WHERE
t1.role_id = #{roleId}
</select>
<!-- 获取角色绑定的用户ID集合 -->
<select id="listUserIdsByRoleId" resultType="java.lang.Long">
SELECT DISTINCT
t1.user_id
FROM
sys_user_role t1
INNER JOIN sys_user t2 ON t1.user_id = t2.id AND t2.is_deleted = 0
WHERE
t1.role_id = #{roleId}
</select>
</mapper>