diff --git a/sql/mysql/youlai_admin.sql b/sql/mysql/youlai_admin.sql index 30d6c2d2..346f7ef8 100644 --- a/sql/mysql/youlai_admin.sql +++ b/sql/mysql/youlai_admin.sql @@ -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); -- ---------------------------- diff --git a/src/main/java/com/youlai/boot/plugin/mybatis/MyDataPermissionHandler.java b/src/main/java/com/youlai/boot/plugin/mybatis/MyDataPermissionHandler.java index a05ad624..84937abd 100644 --- a/src/main/java/com/youlai/boot/plugin/mybatis/MyDataPermissionHandler.java +++ b/src/main/java/com/youlai/boot/plugin/mybatis/MyDataPermissionHandler.java @@ -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 dataScopes, Expression where) { + @SneakyThrows + private Expression dataScopeFilterWithUnion(String mappedStatementId, DataPermission annotation, List 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 customDeptIds) { if (CollectionUtil.isEmpty(customDeptIds)) { // 没有自定义部门,返回 1=0(无权限) @@ -275,13 +256,11 @@ public class MyDataPermissionHandler implements DataPermissionHandler { return falseCondition; } - // 构建 IN 表达式列表 - ExpressionList 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); } } diff --git a/src/main/java/com/youlai/boot/system/converter/RoleConverter.java b/src/main/java/com/youlai/boot/system/converter/RoleConverter.java index ddcfc930..ca89421b 100644 --- a/src/main/java/com/youlai/boot/system/converter/RoleConverter.java +++ b/src/main/java/com/youlai/boot/system/converter/RoleConverter.java @@ -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 toPageVo(Page page); @Mappings({ diff --git a/src/main/java/com/youlai/boot/system/mapper/UserRoleMapper.java b/src/main/java/com/youlai/boot/system/mapper/UserRoleMapper.java index 38b24507..43383a11 100644 --- a/src/main/java/com/youlai/boot/system/mapper/UserRoleMapper.java +++ b/src/main/java/com/youlai/boot/system/mapper/UserRoleMapper.java @@ -19,4 +19,12 @@ public interface UserRoleMapper extends BaseMapper { * @param roleId 角色ID */ int countUsersByRoleId(Long roleId); + + /** + * 获取角色绑定的用户ID集合 + * + * @param roleId 角色ID + * @return 用户ID集合 + */ + java.util.List listUserIdsByRoleId(Long roleId); } diff --git a/src/main/java/com/youlai/boot/system/model/vo/RolePageVO.java b/src/main/java/com/youlai/boot/system/model/vo/RolePageVO.java index 3c3d7b44..92a975bb 100644 --- a/src/main/java/com/youlai/boot/system/model/vo/RolePageVO.java +++ b/src/main/java/com/youlai/boot/system/model/vo/RolePageVO.java @@ -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; -} - - \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/system/service/UserRoleService.java b/src/main/java/com/youlai/boot/system/service/UserRoleService.java index 7254490e..1117e973 100644 --- a/src/main/java/com/youlai/boot/system/service/UserRoleService.java +++ b/src/main/java/com/youlai/boot/system/service/UserRoleService.java @@ -30,4 +30,12 @@ public interface UserRoleService extends IService { * @return true:已分配 false:未分配 */ boolean hasAssignedUsers(Long roleId); + + /** + * 获取角色绑定的用户ID集合 + * + * @param roleId 角色ID + * @return 用户ID集合 + */ + List listUserIdsByRoleId(Long roleId); } diff --git a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java index 3634be94..fabf28a6 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java @@ -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 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 implements Ro // 编辑角色时,判断角色是否存在 Role oldRole = null; + List 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 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 newDeptIds = roleForm.getDeptIds() != null ? roleForm.getDeptIds() : List.of(); + List oldIds = oldDeptIds != null ? oldDeptIds : List.of(); + customDeptChanged = !new java.util.HashSet<>(oldIds).equals(new java.util.HashSet<>(newDeptIds)); + } + + if (dataScopeChanged || customDeptChanged) { + List userIds = userRoleService.listUserIdsByRoleId(savedRoleId); + if (CollectionUtil.isNotEmpty(userIds)) { + userIds.forEach(tokenManager::invalidateUserSessions); + } + } + } } return result; } diff --git a/src/main/java/com/youlai/boot/system/service/impl/UserRoleServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/UserRoleServiceImpl.java index b3457551..f04c2b0c 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/UserRoleServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/UserRoleServiceImpl.java @@ -94,4 +94,12 @@ public class UserRoleServiceImpl extends ServiceImpl i int count = this.baseMapper.countUsersByRoleId(roleId); return count > 0; } + + @Override + public List listUserIdsByRoleId(Long roleId) { + if (roleId == null) { + return List.of(); + } + return this.baseMapper.listUserIdsByRoleId(roleId); + } } diff --git a/src/main/resources/mapper/system/UserRoleMapper.xml b/src/main/resources/mapper/system/UserRoleMapper.xml index d99bce0f..9ff7066d 100644 --- a/src/main/resources/mapper/system/UserRoleMapper.xml +++ b/src/main/resources/mapper/system/UserRoleMapper.xml @@ -26,4 +26,15 @@ WHERE t1.role_id = #{roleId} + + +