若依官网的介绍:http://doc.ruoyi.vip/ruoyi/document/htsc.html#%E6%95%B0%E6%8D%AE%E6%9D%83%E9%99%90
其实是在mybatis 的 sql 上添加了${params.dataScope}这个东西,实现了数据过滤。
<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>
为什么加这个就可以实现呢?我们根据/system/user/list这个请求路径来分析?
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
// 设置 分页 参数
startPage();
// 根据分页参数查询数据 表:sys_user 和 sys_dept
List<SysUser> list = userService.selectUserList(user);
// 封装前台需要的数据
return getDataTable(list);
}
然后我们进入selectUserList方法
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
在这里我们发现了关键,就是==@DataScope==注解,其实若依框架的数据过滤就是通过这个注解实现的。这个是一个AOP知识点的利用,我们需要找到标注了@Aspect的注解,我们找到了类DataScopeAspect。之后我们要进入 selectUserList()方法之前,我们先要进入DataScopeAspect类。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCKvTkYQ-1666165318913)(数据权限.assets/image-20221019151535820.png)]](https://1000bd.com/contentImg/2024/05/23/7dea3129a2884ac2.png)
方法的调用流程:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izkviK1k-1666165318913)(数据权限.assets/image-20221019152129069.png)]](https://1000bd.com/contentImg/2024/05/23/0b392069aa410c5a.png)
进入切面类 DataScopeAspect ,首先进入
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point); // 拼接权限sql前先清空params.dataScope参数防止注入
handleDataScope(point, controllerDataScope);
}
然后:
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser(); // 获取当前登陆的用户
if (StringUtils.isNotNull(loginUser))
{ // 如果 loginUser 不为空的话
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{// 下面就要过滤数据了
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
// 这里其实就是 想给 每一个实体类都会继承的 BaseEntity 里面的 Map params; 属性添加一些过滤条件
// 之后再 mapper 生成sql的时候就可以用来 过滤数据了
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), permission);
}
}
}
然后:
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>(); // 存储 数据权限 1,2,3,4,5
// 获取当前用户 的全部角色信息
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope(); // 获取 当前角色的数据权限
// String DATA_SCOPE_CUSTOM = "2"; => 自定数据权限
// 如果当前的 数据权限 不是 自定数据权限 并且 conditions 里面不包含当前的 数据权限
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
{
continue;
}
// 如果 ①permission 不为空 并且 ② 角色的权限信息不为空 并且 ③角色的权限不包含传递过来的角色权限
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
//===================================== 从这里之后,sqlString 前面被赋值后,后面要是再次赋值会覆盖前面的 ====================================
// String DATA_SCOPE_ALL = "1"; 全部数据权限
// 如果当前的 数据权限是 全部数据权限
if (DATA_SCOPE_ALL.equals(dataScope))
{
// 如果遍历到 一个 角色的数据权限为1的时候,就是 全部数据权限 就不需要过滤了,直接返回
sqlString = new StringBuilder();
break;
}
// String DATA_SCOPE_CUSTOM = "2"; => 自定数据权限
// 如果当前的 数据权限是 自定数据权限
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()));
}
// String DATA_SCOPE_DEPT = "3"; => 部门数据权限
// 如果当前的 数据权限是 部门数据权限
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
// String DATA_SCOPE_DEPT_AND_CHILD = "4"; => 部门及以下数据权限
// 如果当前的 数据权限是 部门及以下数据权限
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()));
}
// String DATA_SCOPE_SELF = "5"; => 仅本人数据权限
// 如果当前的 数据权限是 仅本人数据权限
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
//====================上面是为了 设置 需要拼接的sql,下面是为将 sql 设置到实体类里面 =======================
// 如果 sqlString不为 空的话
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
// DATA_SCOPE = "dataScope";
// 因为每一个实体类都继承了 BaseEntity 类,所以,每一个类也就有里面的每一个属性
// 这里进行的操作就是给,实体类的 Map params; 字段 put("dataScope","xxxx")
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
然后就进入:
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
执行sql去了,这个时候 user 对象因为经过 DataScopeAspect类的一大堆骚操作,因为每一个实体类都继承了 BaseEntity 类,所以,每一个类也就有里面的每一个属性,经过上面的方法 给 params属性增加了key :"dataScope"值:“xxxx”
然后再 mybatis 的sql里面就可以取到${params.dataScope},这个东西取到的就是sqlString字符串
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
....
${params.dataScope}
select>
总结:
1、如果我们再若依项目里面想用数据过滤其实就只需要再 service 层加上一个 @DataScope注解即可
2、若依框架能实现数据过滤的主要原因就是利用了AOP切面,再进入 service 方法之前先进入切面类里面的方法,执行完毕后再进入 service 层的方法。
下面我们根据系统管理下的用户管理页面作为分析,查看每一种数据权限。
调用的接口:
http://localhost/dev-api/system/user/list?pageNum=1&pageSize=10
测试的数据
INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2022-10-19 14:22:42', 'admin', '2022-09-30 22:02:23', '', '2022-10-19 14:22:42', '管理员');
INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2022-09-30 22:02:23', 'admin', '2022-09-30 22:02:23', 'admin', '2022-10-17 11:51:08', '测试员');
INSERT INTO `sys_user` VALUES (100, NULL, '111', '1', '00', '', '', '0', '', '$2a$10$t0cnTqGNcOZlx99Hj3FIk.C3.4tFvtperOpp8nmhlhOl/vax.DjZq', '0', '0', '', NULL, 'admin', '2022-10-13 14:09:55', 'admin', '2022-10-17 11:51:10', NULL);
INSERT INTO `sys_user` VALUES (101, 101, 'zs', 'zs', '00', '', '', '0', '', '$2a$10$MI9C2H3sw6orfGUcGOQoZushCERPPYmF0RCJFapI8ShBrU27bH8s6', '0', '0', '127.0.0.1', '2022-10-19 14:16:16', 'admin', '2022-10-13 17:48:39', 'admin', '2022-10-19 14:16:16', NULL);
INSERT INTO `sys_user` VALUES (102, 101, 'sz', '深圳总公司', '00', '', '', '0', '', '$2a$10$Z1qGn6CKHZ9UgMpCILsKoO3Bgsc.oOOswqhChqrFpgAklFrDMzy6C', '0', '0', '', NULL, 'admin', '2022-10-19 11:45:10', '', NULL, NULL);
现象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lW0sSfsf-1666165318914)(数据权限.assets/image-20221019141523757.png)]](https://1000bd.com/contentImg/2024/05/23/4bd1e6f358014067.png)
登陆zs用户查询,zs用户的数据权限只有仅自己。查询结果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6inGdNY-1666165318915)(数据权限.assets/image-20221019141623670.png)]](https://1000bd.com/contentImg/2024/05/23/b9e994170da4f20a.png)
分析
相当于查询用户id只为当前登陆用户
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
查询的SQL:
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 = 101 )
LIMIT 10;
现象:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5NGi5gy-1666165318915)(数据权限.assets/image-20221019115431318.png)]](https://1000bd.com/contentImg/2024/05/23/4bd1e6f358014067.png)
登陆zs用户查询,zs用户的数据权限只有本部门。查询结果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeKDrMeV-1666165318915)(数据权限.assets/image-20221019115645550.png)]](https://1000bd.com/contentImg/2024/05/23/72e533c4781b52ac.png)
分析
根据当前用户的部门信息过滤数据
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
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 ( d.dept_id = 101 )
LIMIT 10;
其实是给原本的SQL拼接了:
AND ( d.dept_id = 101 )
现象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTsuJbb7-1666165318916)(数据权限.assets/image-20221019133902852.png)]](https://1000bd.com/contentImg/2024/05/23/4f30b441e62e4864.png)
登陆zs用户查询,zs用户的数据权限只有本部门及以下数据权限。查询结果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jPIPw1m-1666165318917)(数据权限.assets/image-20221019134006401.png)]](https://1000bd.com/contentImg/2024/05/23/d446adc7434d4163.png)
分析
根据当前用户本部门及以下数据权限信息过滤数据
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()));
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 (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 101 OR find_in_set(101, ancestors) ))
LIMIT 10;
其实是给原本的 SQL 拼接了:
AND (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 101 OR find_in_set(101, ancestors) ))
现象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8QVuH9Aa-1666165318917)(数据权限.assets/image-20221019134937313.png)]](https://1000bd.com/contentImg/2024/05/23/8fdbd9cf1bc6402a.png)
登陆zs用户查询,zs用户的数据权限只有全部数据权限。查询结果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4elxONG-1666165318917)(数据权限.assets/image-20221019135141590.png)]](https://1000bd.com/contentImg/2024/05/23/302cd5435b9a1498.png)
全部都可以查询出来
分析
相当于给原本的 sql 后面什么 都不拼接
sqlString = new StringBuilder();
break;
查询数据的sql语句:
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'
LIMIT 10;
相当于什么数据没有过滤。
现象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fynpBReo-1666165318918)(数据权限.assets/image-20221019135603747.png)]](https://1000bd.com/contentImg/2024/05/23/b9232c96ae2a9f51.png)
设置自定义数据权限只能看到研发部门。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlwqfsTf-1666165318919)(数据权限.assets/image-20221019135737731.png)]](https://1000bd.com/contentImg/2024/05/23/d0dbfb4a53bb47ae.png)
# 题外话:后面会详细讲解
# 1
在按下确定的这一瞬间其实发起了请求`http://localhost/dev-api/system/role/dataScope`
这个请求往数据库表`sys_role_dept`插入数据,这个表就是 自定义数据权限才会用到的表
# 2
这里 还存在一个 父子联动的问题,按照我这里显示的问题,这里我们选择父子联动
- 如果选择父子联动: 用户属于若依科技、深圳总公司、研发部门,这三个任意一个都可以查询出来
- 如果不选择父子联动:只能查询用户属于 深圳总公司下研发部门的用户
登陆zs用户查询,zs用户的数据权限只有自定数据权限。查询结果
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2pkBPBXw-1666165318919)(数据权限.assets/image-20221019140139543.png)]](https://1000bd.com/contentImg/2024/05/23/18d21a94c779abf4.png)
分析
根据当前用户本部门及以下数据权限信息过滤数据
相当于根据角色id在ssys_role_dept表里面找到部门id,然后根据部门id进行过滤数据
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
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 ( d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 104 ) )
LIMIT 10;
前面我们不是有说到一个 父子联动的按钮吗,我们现在就来详细来看看,这里为了更加能说明问题,我在数据库有添加了一个用户。她是属于长沙分公司的研发部门员工。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nyDjCvmx-1666165318920)(数据权限.assets/image-20221019142533118.png)]](https://1000bd.com/contentImg/2024/05/23/233891b204b7b4c8.png)
INSERT INTO `sys_user` VALUES (103, 205, 'csyf', '长沙-研发1', '00', '', '', '0', '', '$2a$10$VdGvUO.ZHGgAFCGBXMNBA.sjOtnYoODeqYYkrJhuqC4etrh/m.sXK', '0', '0', '', NULL, 'admin', '2022-10-19 14:08:46', '', NULL, NULL);
我们先来看第①种情况,父子不联动,我们只选择 深圳分公司下面的 研发部门
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvoW7QBv-1666165318920)(数据权限.assets/image-20221019142929643.png)]](https://1000bd.com/contentImg/2024/05/23/066b26754a330204.png)
点击确定后,我们发现请求了地址:http://localhost/dev-api/system/role/dataScope
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVSQ5D9x-1666165318920)(数据权限.assets/image-20221019143226204.png)]](https://1000bd.com/contentImg/2024/05/23/b347f2aeaec5f2d7.png)
保存到数据库表sys_role_dept里面的也只有一条数据
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EN6TXbUN-1666165318920)(数据权限.assets/image-20221019143322052.png)]](https://1000bd.com/contentImg/2024/05/23/f745f1f4e6da22e6.png)
admin用户的页面:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwg3LB7e-1666165318921)(数据权限.assets/image-20221019143624004.png)]](https://1000bd.com/contentImg/2024/05/23/59637736ba484ffa.png)
我们现在再使用zs用户登陆系统。根据我们上面的分析,过滤的时候过滤的时候是查询当前角色下的部门id,根据这些部门id进行过滤的。所以 我们也就只能看到 深圳分公司下面研发部门的用户,即只能看到zs用户。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LcCgrhdj-1666165318921)(数据权限.assets/image-20221019143558083.png)]](https://1000bd.com/contentImg/2024/05/23/aa33117c4d4adb07.png)
下面我们再看一下勾选父子联动,选择深圳分公司下面的研发部门会发生什么。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wa3WFvVt-1666165318921)(数据权限.assets/image-20221019143810568.png)]](https://1000bd.com/contentImg/2024/05/23/4769fa32c4fb4c1b.png)
点击确定后,我们发现请求了地址:http://localhost/dev-api/system/role/dataScope
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uM9wUVqh-1666165318921)(数据权限.assets/image-20221019143939974.png)]](https://1000bd.com/contentImg/2024/05/23/0ed4242eb05476ca.png)
再看看数据库:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Bpdgjc5-1666165318921)(数据权限.assets/image-20221019144000199.png)]](https://1000bd.com/contentImg/2024/05/23/96d6b4b0f8804a6a.png)
admin用户的页面:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0NiRrFLP-1666165318922)(数据权限.assets/image-20221019144026824.png)]](https://1000bd.com/contentImg/2024/05/23/e4092a9ecf79a2f4.png)
我们现在再使用zs用户登陆系统。根据我们上面的分析,过滤的时候过滤的时候是查询当前角色下的部门id,根据这些部门id进行过滤的。所以 我们也就只能看到 深圳分公司下面研发部门的用户,即我们可以看到 admin、zs、sz这三个用户。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7PGn2Ga-1666165318922)(数据权限.assets/image-20221019144206933.png)]](https://1000bd.com/contentImg/2024/05/23/4f7aad97f36f872c.png)