RuoYi-Cloud-Plus 2.5.1 权限与数据层深度解析:Sa-Token + Mybatis-Plus 实现 3 种数据权限控制
📅 2026/7/11 8:52:04
👁️ 阅读次数
📝 编程学习
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(); List<SysUser> list = userService.selectUserList(user); return getDataTable(list); }2. Sa-Token 在分布式场景下的优势
相比传统 JWT 方案,Sa-Token 在分布式系统中展现出独特优势:
| 特性 | Sa-Token | JWT |
|---|---|---|
| 会话管理 | 服务端集中存储 | 无状态 |
| 踢人下线 | 原生支持 | 需额外实现 |
| 权限实时更新 | 立即生效 | 依赖令牌过期 |
| 多端登录控制 | 精细化控制 | 较难实现 |
| 性能影响 | 需访问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 List<User> 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 List<Task> 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 ThreadLocal<String> 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 id="selectUserList" resultType="SysUser"> 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} </select>8. 扩展:动态数据源下的权限处理
在多租户场景中,数据权限需要与动态数据源协同工作:
- 确定当前数据源
- 加载对应数据源的权限规则
- 应用权限过滤时考虑数据源边界
public class DynamicDataPermissionInterceptor extends DataPermissionInterceptor { @Override protected String getDataScope() { String dsName = DynamicDataSourceContextHolder.getDataSourceType(); DataSourceConfig config = getConfig(dsName); return buildDataScope(config.getPermissionRules()); } }
编程学习
技术分享
实战经验