“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
权限管理
点击登陆后,浏览器还发送了两个请求,再看看这两个是什么东西,代码就是这样一步一步去看的。
如何找到这两个方法(前端):
javascript
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
,表示此角色具有用户查询权限。
](https://jsdelivr.codeqihan.com/gh/c-parrot/source/img/blog/后端/20230316012617.png)
比如项目中,当用户跳转到了用户管理页面,前端会向后端请求用户列表等数据,后端会先判断该用户是否有用户查询的权限,再去查询数据:
java
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
。
java
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语句为:
xml
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中的逻辑:
java
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) + ")"); } } }
|
使用:
方法 1:通过数据库直接分配权限
在若依框架中,权限管理是通过用户角色和权限(菜单)来实现的。要为用户 1781
设置修改密码的权限,需要将该用户与包含修改密码权限的角色或菜单关联起来。
步骤 1:确定修改密码的权限菜单
查找对应的菜单 ID: 在 sys_menu
表中,查找与修改密码相关的菜单项。一般情况下,菜单项名称可能为“修改密码”或类似的内容。使用以下 SQL 查询找到该菜单的 menu_id
:
sql
复制代码
SELECT menu_id, menu_name FROM sys_menu WHERE menu_name LIKE '%修改密码%';
假设查询结果为 menu_id=10
(具体的 menu_id
可能不同,您需要根据实际情况修改)。
步骤 2:将修改密码权限分配给用户
查看用户的角色: 在 sys_user_role
表中查找用户 1781
的角色 ID:
sql
复制代码
SELECT role_id FROM sys_user_role WHERE user_id = 1781;
假设用户 1781
的角色 role_id=2
。
分配修改密码权限给该角色: 在 sys_role_menu
表中将修改密码的 menu_id
(如 10
)与角色 ID(如 2
)关联:
sql
复制代码
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 10);
这将确保角色 2
的所有用户(包括用户 1781
)都具备修改密码的权限。
步骤 3:更新缓存
完成数据库操作后,您需要确保系统的缓存已更新,或手动清除系统缓存,确保新权限生效。
方法 2:通过若依管理后台分配权限
如果您有若依的管理后台权限,您可以通过后台界面直接为用户分配权限。
步骤 1:进入用户管理
- 登录若依管理后台。
- 进入 系统管理 -> 用户管理。
- 找到
ID=1781
的用户并点击编辑。
步骤 2:修改角色权限
- 在用户编辑页面,查看该用户的角色。
- 确保该用户的角色具备修改密码的权限。
- 如果角色没有相关权限,您可以进入 系统管理 -> 角色管理,为该角色添加修改密码的权限。
- 如果需要,您可以为用户分配新的角色,该角色具有修改密码的权限。
步骤 3:保存并更新
- 完成权限分配后,点击保存。
- 更新系统缓存,以确保新的权限配置生效。
例如:报错提示,无数据权限
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void checkUserDataScope(Long userId) { if (!SysUser.isAdmin(SecurityUtils.getUserId())) { SysUser user = new SysUser(); user.setUserId(userId); List<SysUser> users = SpringUtils.getAopProxy(this).selectUserList(user); if (StringUtils.isEmpty(users)) { throw new ServiceException("没有权限访问用户数据!"); } } }
|
打断点发现,是users为空。
查看sql:
1
| Preparing: 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' AND u.user_id = ? AND (d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 2 ) )
|
(前提知道在sys_user_role中用户属于2id的角色,在sys_role里2为普通角色)
去sys_role_dept表查找对应用户橘色的部门关系,发现有
查看用户表,该对象dept_id为空,填上100,101,105其中一个值,问题解决。