适合人群:已理解 RuoYi 认证授权机制,想深入学习 RBAC 权限模型的同学
本文是《RuoYi 框架从零到一》系列第 04 篇,基于 RuoYi-Vue 4.x 版本。建议先读 03 - 认证与授权机制。
一、RBAC 权限模型四层关系
RBAC(Role-Based Access Control,基于角色的访问控制)是企业级系统最常用的权限模型。RuoYi 的 RBAC 模型包含四层关系:
1.1 核心实体与关系
(1)用户(User)
sys_user 表:
1 | CREATE TABLE sys_user ( |
关键字段:
dept_id:外键,关联部门表(一个用户属于一个部门)password:BCrypt 加密存储(60 位定长字符串)status:账号状态(停用用户无法登录)del_flag:逻辑删除标志(软删除,数据不真正删除)
(2)角色(Role)
sys_role 表:
1 | CREATE TABLE sys_role ( |
关键字段:
role_key:角色标识符(如admin、common),用于@PreAuthorize("@ss.hasRole('admin')")data_scope:数据权限范围(下一节详细讲解)menu_check_strictly:分配菜单时是否父子联动(false 时可以只选子节点,父节点不自动选中)
(3)菜单权限(Menu)
sys_menu 表:
1 | CREATE TABLE sys_menu ( |
关键字段:
parent_id:父菜单 ID(0 表示根节点,构建树形结构的关键)menu_type:- M(目录):一级菜单容器(如”系统管理”)
- C(菜单):具体页面(如”用户管理”)
- F(按钮):页面内的按钮权限(如”新增”、”删除”)
perms:权限标识(如system:user:add),对应@PreAuthorize注解path:前端路由路径(如/system/user)component:Vue 组件路径(如system/user/index)
菜单类型示例:
1 | 系统管理 (M) |
(4)部门(Dept)
sys_dept 表:
1 | CREATE TABLE sys_dept ( |
关键字段:
parent_id:父部门 ID(树形结构)ancestors:祖级列表(如0,100,101),冗余字段,提升查询性能(查询”本部门及以下”时直接用LIKE '0,100,101%',无需递归)
部门树示例:
1 | INSERT INTO sys_dept VALUES(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0'); |
查询”研发部门(103)及以下”的所有部门:
1 | SELECT * FROM sys_dept WHERE ancestors LIKE '0,100,101,103%' OR dept_id = 103; |
1.2 关联表(多对多关系)
(1)用户-角色关联表
sys_user_role:
1 | CREATE TABLE sys_user_role ( |
一个用户可以拥有多个角色,一个角色可以分配给多个用户(N:M)。
(2)角色-菜单关联表
sys_role_menu:
1 | CREATE TABLE sys_role_menu ( |
一个角色可以拥有多个菜单权限,一个菜单可以被多个角色引用(N:M)。
(3)角色-部门关联表(自定义数据权限)
sys_role_dept:
1 | CREATE TABLE sys_role_dept ( |
仅当角色的 data_scope = 2(自定义数据权限)时使用,指定该角色可以看到哪些部门的数据。
1.3 权限查询核心 SQL
查询用户的所有权限:
1 | SELECT DISTINCT m.perms |
查询用户的菜单树(用于前端动态路由):
1 | SELECT DISTINCT m.menu_id, m.parent_id, m.menu_name, m.path, m.component, |
二、数据权限(DataScope)实现
数据权限解决的问题:同一个功能(如”查询用户列表”),不同角色看到的数据范围不同。
- 超级管理员:看到所有用户
- 部门经理:看到本部门及下属部门的用户
- 普通员工:只看到自己创建的用户
2.1 数据权限 5 种范围
sys_role.data_scope 字段定义:
| 值 | 名称 | 说明 | SQL 条件示例 |
|---|---|---|---|
| 1 | 全部数据权限 | 不限制,查询所有数据 | 无额外条件 |
| 2 | 自定义数据权限 | 指定部门(sys_role_dept 表) | dept_id IN (101, 102, 105) |
| 3 | 本部门数据权限 | 仅当前用户所在部门 | dept_id = 103 |
| 4 | 本部门及以下数据权限 | 当前部门 + 下属部门 | ancestors LIKE '0,100,103%' |
| 5 | 仅本人数据权限 | 只看自己创建的数据 | create_by = 'admin' |
2.2 @DataScope 注解
定义(ruoyi-common 模块):
1 |
|
使用示例:
1 |
|
对应的 MyBatis XML(关键是 ${params.dataScope}):
1 | <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult"> |
一句话记住:${params.dataScope} 是动态注入的 SQL 条件,由 AOP 切面根据用户的角色权限生成。
2.3 DataScopeAspect 切面实现
核心代码(ruoyi-framework 模块):
1 |
|
工作流程:
- 拦截:
@Before拦截带@DataScope注解的方法 - 获取用户:从
SecurityContextHolder获取当前登录用户 - 判断管理员:如果是 admin(userId = 1),不做任何过滤
- 遍历角色:用户可能有多个角色,取最宽松的权限(遇到
dataScope = 1直接跳出) - 生成 SQL:根据
dataScope值拼接 SQL 条件 - 注入参数:将 SQL 字符串存入
params.dataScope,MyBatis 通过${params.dataScope}拼接
生成的 SQL 示例(假设当前用户部门为 103,角色权限为”本部门及以下”):
1 | SELECT u.user_id, u.user_name, u.dept_id |
2.4 数据权限的局限性
⚠️ 注意事项:
- 只能过滤列表查询:数据权限只在”查询列表”时生效,对”根据 ID 查询详情”无效(需要业务代码额外判断)
- 需要关联部门表:SQL 必须
LEFT JOIN sys_dept,否则d.dept_id不存在会报错 - 多角色取并集:用户有多个角色时,权限是”或”关系(有一个角色权限宽松就按宽松的来)
- 性能问题:
find_in_set()函数和LIKE查询性能较差,数据量大时建议使用部门 ID 范围查询
最佳实践:
- 新增/修改/删除操作前,先调用”查询详情”接口,判断是否有权限操作该数据
- 敏感操作(如删除)额外加上业务层权限校验:
1 |
|
三、菜单树构建与动态路由
3.1 菜单树的递归构建
后端递归构建菜单树:
1 |
|
返回的 JSON 结构:
1 | [ |
3.2 前端动态路由生成
前端接收菜单树后,转换为 Vue Router 路由:
1 | // ruoyi-ui/src/store/modules/permission.js |
生成的路由配置:
1 | [ |
挂载到 Vue Router:
1 | // ruoyi-ui/src/permission.js |
一句话记住:后端返回菜单树 JSON → 前端递归解析 → 转换为 Vue Router 配置 → 动态 addRoutes → 侧边栏菜单自动生成。
3.3 按钮权限控制
前端根据菜单树中的 F 类型(按钮) 节点,控制按钮显示:
1 | <template> |
v-hasPermi 指令会在 DOM 渲染时移除没有权限的按钮(前文已讲解,这里不再赘述)。
结语
这篇文章深入剖析了 RuoYi 的 RBAC 权限模型实现:
- 四层关系:用户 → 角色(N:M)→ 菜单权限(N:M)+ 用户 → 部门(N:1)
- 数据权限:@DataScope 注解 → AOP 拦截 → 根据 role.dataScope 生成 SQL 条件 → 注入 params.dataScope → MyBatis 拼接
- 5 种范围:全部数据(1)、自定义数据(2)、本部门(3)、本部门及以下(4)、仅本人(5)
- 菜单树:parent_id 递归构建树形结构 → 前端转换为 Vue Router 配置 → 动态挂载路由
- 按钮权限:菜单表 menu_type = F → 前端 v-hasPermi 指令 → 无权限移除 DOM
下一篇预告:我们将深入 RuoYi 的代码生成器——Velocity 模板引擎原理、代码生成配置、自定义模板开发,一键生成 CRUD 的全流程。
思考与练习
- 尝试创建一个新角色”部门经理”,设置数据权限为”本部门及以下”,验证查询用户列表时的 SQL 条件
- 阅读
DataScopeAspect源码,理解为什么用户有多个角色时权限取并集(OR 关系)- 实现一个自定义数据权限:只能看到”最近 30 天创建的数据”