RuoYi-Vue -- 权限篇
|Word count:2.5k|Reading time:10min|Post View:
“vue-element-admin项目中的页面权限是写死预设的,而很多公司的需求是每个页面的权限是动态配置的。如:你可以在后台通过一个 tree 控件或者其它展现形式给每一个页面动态配置权限,之后将这份路由表存储到后端。当用户登录后得到 roles,前端根据roles 去向后端请求可访问的路由表,从而动态生成可访问页面,之后就是 router.addRoutes 动态挂载到 router 上。多了一步将后端返回路由表和本地的组件映射到一起。就是说:将角色对应的路由表存在数据库中,前端通过用户角色去从数据库中取得路由表。”
参考:
环境部署 | 文档
若依前后端分离版,通俗易懂,快速上手 | bilibili
下载启动
Ui是前端部分,其他是后端部分,启动类在admin中。common,framework,generator,quartz,system服务于admin。
用idea或者vscode单独打开Ui文件夹。
配置数据库mysql,:创建数据库 ry
并导入数据脚本 ry_2021xxxx.sql
,quartz.sql
后端:启动类 com.ruoyi.RuoYiApplication.java
前端:按照readme运行命令
若依框架入门(前后端分离版本) | CSDN
权限管理
点击登陆后,浏览器还发送了两个请求,再看看这两个是什么东西,代码就是这样一步一步去看的。
如何找到这两个方法(前端):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { to.meta.title && store.dispatch('settings/setTitle', to.meta.title) if (to.path === '/login') { next({ path: '/' }) NProgress.done() } else { if (store.getters.roles.length === 0) { isRelogin.show = true store.dispatch('GetInfo').then(() => { isRelogin.show = false store.dispatch('GenerateRoutes').then(accessRoutes => { router.addRoutes(accessRoutes) next({ ...to, replace: true }) }) }).catch(err => { store.dispatch('LogOut').then(() => { Message.error(err) next({ path: '/' }) }) }) } } } }
|
在后端中如何找到对应部分:右键根文件夹,Find in files,搜索”getInfo”。
进入到该方法,打个断点,刷新浏览器,看看是不是这个:
“ ::*”(通配符?):所有权限
在数据库中,表Sys_user_role记录了user_id和role_id的关系。
generateRoutes,定义在store/permission.js,其中的getRouters来自文件夹api,请求路径/getRouters
根据前端的请求路径,查看后端代码,追踪到其.mapper文件,可以看到SQL语句(控制台也会输出sql语句,加断点调试),复制,在navicat中执行,可以看到结果集
查询到23条记录,为什么只显示4个:父节点4个。那如何实现这种层级嵌套,数据库层面如何设计这种级联关系:Sys_menu中,字段parent_id记录自己的父表的menu_id。逻辑是按照Parent_id的值从menu_id中寻找父menu。还有,如果找不到,比如parent_id为0,则此menu为一级menu。避免再用一个字段记录自己是哪一层级。
侧边栏
路由和侧边栏如何绑定起来(点击不同按钮,跳到不同页面):在数据库中设定。
前端的getRouters方法不仅获取到路由表,还有每个路由的component信息。
比如“用户管理”页面的组件是src/system/user/index,前端会运行index.vue,然后渲染到浏览器上。
菜单与操作权限
数据库中表sys_menu记录了菜单与权限的信息,菜单如用户管理、角色管理等,权限如用户查询、用户新增等。注意表中的属性列perms,用户拥有哪些权限用perms中的值表示,比如某一角色拥有 system:user:query
,表示此角色具有用户查询权限。
比如项目中,当用户跳转到了用户管理页面,前端会向后端请求用户列表等数据,后端会先判断该用户是否有用户查询的权限,再去查询数据:
1 2 3 4 5 6 7 8 9
| @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); List<SysUser> list = userService.selectUserList(user); return getDataTable(list); }
|
数据权限
在业务中,用户必须绑定一个角色,而角色又必须将自身绑定到部门,角色绑定了哪些部门,就决定着隶属于该角色的用户能对哪些部门数据进行增删改。那么,怎么实现让用户只能遵循其绑定角色所指定的部门,来进行数据范围控制呢?一般情况下,假如我们对一张表要进行查询或更新的话,需要在sql 语句中,where
条件语法后面 加上 dept.id = {currentDeptId}
来进行过滤。在若依框架中,我们只需要在 Service 层的方法上加入 @DataScope
注解,并分别通过deptAlias 和userAlias 属性,分别指出部门表和用户表在 sql语句中的别名是什么。利用此注解,就不需要去手动在 sql 语句后面加上过滤条件了。
数据权限在表sys_role,属性列data_scope中设置。项目目前支持以下几种权限:全部数据权限、自定数据权限、本部门数据权限、本部门及以下数据权限、仅本人数据权限,分别用字符串“1”,“2”,“3”,“4”,“5”表示。
比如项目中查询用户列表的方法 SysUserController::list
,它会调用 SysUserServiceImpl::selectUserList
。
1 2 3 4 5 6 7
| @Override @DataScope(deptAlias = "d", userAlias = "u") public List<SysUser> selectUserList(SysUser user) { return userMapper.selectUserList(user); }
|
“在数据查询的方法上添加注解@DataScope。”如果某角色的data_scope的值为“3“(本部门数据权限),而某一方法涉及部门相关的数据,则就需要在该方法的注解上提供部门信息,比如@DataScope(deptAlias = “d”, userAlias = “u”),其中d对应最终执行的sql语句中的某个表。如selectUserList方法最终执行的sql语句为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult"> select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id where u.del_flag = '0' <if test="userId != null and userId != 0"> AND u.user_id = #{userId} </if> <if test="userName != null and userName != ''"> AND u.user_name like concat('%', #{userName}, '%') </if> <if test="status != null and status != ''"> AND u.status = #{status} </if> <if test="phonenumber != null and phonenumber != ''"> AND u.phonenumber like concat('%', #{phonenumber}, '%') </if> <if test="params.beginTime != null and params.beginTime != ''"> AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') </if> <if test="params.endTime != null and params.endTime != ''"> AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') </if> <if test="deptId != null and deptId != 0"> AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) )) </if>
${params.dataScope} </select>
|
方法 selectUserList
的注解 @DataScope(deptAlias = "d", userAlias = "u")
中,d就是表sys_dept。注解@DataScope会生成一些sql语句,替换上面的${params.dataScope}(在已经获取数据的基础上再筛选出用户所在部门的数据)。
- 实体类(parameterType的那个类,而不是resultMap)需要继承BaseEntity,因为生成的SQL语句会存放到BaseEntity对象中的params属性中,然后在xml中通过${params.dataScope}获取拼接后的语句。
@DataScope
的逻辑实现代码在com.ruoyi.framework.aspectj.DataScopeAspect.dataScopeFilter。根据业务修改dataScopeFilter中的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission){ StringBuilder sqlString = new StringBuilder(); List<String> conditions = new ArrayList<String>();
for (SysRole role : user.getRoles())
{ String dataScope = role.getDataScope(); if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) { continue; } if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { continue; }
if (DATA_SCOPE_ALL.equals(dataScope)) { sqlString = new StringBuilder(); break; } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { sqlString.append(StringUtils.format( " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); } else if (DATA_SCOPE_DEPT.equals(dataScope)) { sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { sqlString.append(StringUtils.format( " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); } else if (DATA_SCOPE_SELF.equals(dataScope)) { if (StringUtils.isNotBlank(userAlias)) { sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); } else { sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); } } conditions.add(dataScope); }
if (StringUtils.isNotBlank(sqlString.toString())) { Object params = joinPoint.getArgs()[0]; if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); } } }
|