RuoYi-Cloud-Plus 2.5.1 权限与数据层深度解析:Sa-Token + Mybatis-Plus 实现 3 种数据权限控制
RuoYi-Cloud-Plus 2.5.1 权限与数据层深度解析Sa-Token Mybatis-Plus 实现 3 种数据权限控制在当今企业级应用开发中权限控制与数据安全是系统架构的核心考量。RuoYi-Cloud-Plus 作为基于 SpringCloudAlibaba 的微服务快速开发框架其 2.5.1 版本通过整合 Sa-Token 和 Mybatis-Plus提供了完善的权限控制解决方案。本文将深入剖析其权限体系设计原理特别是如何实现部门、角色和自定义条件三种数据权限的无感过滤。1. 权限体系架构设计RuoYi-Cloud-Plus 采用分层权限控制模型将权限验证逻辑从业务代码中彻底解耦。整个体系分为认证层、鉴权层和数据权限层三个关键部分认证层基于 Sa-Token 实现统一身份认证鉴权层通过注解和拦截器实现接口访问控制数据权限层利用 Mybatis-Plus 插件机制实现数据过滤// 典型权限校验流程示例 SaCheckPermission(system:user:list) GetMapping(/list) public TableDataInfo list(SysUser user) { startPage(); ListSysUser list userService.selectUserList(user); return getDataTable(list); }2. Sa-Token 在分布式场景下的优势相比传统 JWT 方案Sa-Token 在分布式系统中展现出独特优势特性Sa-TokenJWT会话管理服务端集中存储无状态踢人下线原生支持需额外实现权限实时更新立即生效依赖令牌过期多端登录控制精细化控制较难实现性能影响需访问Redis无需额外存储实际应用建议对于需要复杂权限管理的后台系统Sa-Token 的会话模型更易控制而对高并发API场景JWT 的无状态特性可能更适合。3. 数据权限实现原理Mybatis-Plus 通过插件机制实现数据权限过滤核心类DataPermissionInterceptor的工作流程如下解析当前用户的数据权限注解根据权限类型构建对应的SQL片段通过改写原始SQL实现数据过滤public class DataPermissionInterceptor implements InnerInterceptor { Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 数据权限处理逻辑 String dataScope getDataScope(); if (StringUtils.isNotEmpty(dataScope)) { BoundSql newBoundSql new BoundSql(...); resetSql(ms, newBoundSql); } } }4. 三种数据权限实现详解4.1 部门数据权限基于组织架构的数据隔离适用于多分支机构场景。实现要点用户-部门关联关系存储部门树形结构维护SQL改写为dept_id IN (权限部门列表)配置示例-- 原始SQL SELECT * FROM sys_user -- 改写后SQL SELECT * FROM sys_user WHERE dept_id IN (100, 101, 102)4.2 角色数据权限按角色划分数据可见范围常见配置模式全部数据权限本部门及以下数据权限本部门数据权限仅本人数据权限DataScope(deptAlias d, userAlias u) public ListUser selectUserList(User user) { return mapper.selectUserList(user); }4.3 自定义数据权限通过注解扩展实现业务特定的数据过滤规则Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface DataAuth { String value(); // 权限表达式 } // 使用示例 DataAuth(project_id IN (#auth.projects)) public ListTask getTaskList() { //... }5. 实战完整数据权限配置5.1 数据库设计关键表CREATE TABLE sys_role ( role_id BIGINT PRIMARY KEY, role_name VARCHAR(30), data_scope CHAR(1) -- 数据范围(1:全部 2:自定义 3:本部门 4:本部门及以下 5:仅本人) ); CREATE TABLE sys_role_dept ( role_id BIGINT, dept_id BIGINT ); CREATE TABLE sys_user_role ( user_id BIGINT, role_id BIGINT );5.2 Mybatis-Plus 配置# application.yml mybatis-plus: configuration: default-scripting-language: freemarker global-config: db-config: logic-delete-field: delFlag # 逻辑删除字段 logic-not-delete-value: 0 logic-delete-value: 15.3 数据权限切面实现Aspect Component public class DataScopeAspect { Before(annotation(dataScope)) public void doBefore(DataScope dataScope) { String permission getCurrentUserDataScope(); String deptAlias dataScope.deptAlias(); String userAlias dataScope.userAlias(); if (StringUtils.isNotEmpty(permission)) { DataPermissionHelper.addDataPermission( AND deptAlias .dept_id IN ( permission )); } } }6. 性能优化建议缓存策略将用户权限数据缓存在Redis中SQL优化确保权限字段有适当索引批量处理对批量查询做特殊处理避免多次权限校验懒加载非必要场景延迟权限校验// 使用ThreadLocal存储权限信息避免重复查询 private static final ThreadLocalString DATA_PERMISSION new ThreadLocal(); public static void setDataPermission(String permission) { DATA_PERMISSION.set(permission); } public static String getDataPermission() { return DATA_PERMISSION.get(); }7. 常见问题解决方案问题1分页总数不准确解决方案先获取未过滤的count值再应用数据权限查询数据问题2多表关联权限失效解决方案确保所有关联表都添加了正确的别名问题3自定义SQL权限不生效解决方案在XML中使用${params.dataScope}接收权限参数!-- mapper.xml示例 -- select idselectUserList resultTypeSysUser SELECT u.*, d.dept_name FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id d.dept_id WHERE u.del_flag 0 ${params.dataScope} /select8. 扩展动态数据源下的权限处理在多租户场景中数据权限需要与动态数据源协同工作确定当前数据源加载对应数据源的权限规则应用权限过滤时考虑数据源边界public class DynamicDataPermissionInterceptor extends DataPermissionInterceptor { Override protected String getDataScope() { String dsName DynamicDataSourceContextHolder.getDataSourceType(); DataSourceConfig config getConfig(dsName); return buildDataScope(config.getPermissionRules()); } }