沈阳做网站,汕头seo推广,广告公司网站(附falsh及源代码),建设电影网站的关键这篇文章我先和大家分析一下 RuoYi-Vue 脚手架中 DataScope 注解的实现原理#xff0c;在 TienChin 项目视频中到时候还会有深入讲解。
1. 思路分析
首先我们先来捋一捋这里的权限实现的思路。
DataScope 注解处理的内容叫做数据权限#xff0c;就是说你这个用户登录后能够…这篇文章我先和大家分析一下 RuoYi-Vue 脚手架中 DataScope 注解的实现原理在 TienChin 项目视频中到时候还会有深入讲解。
1. 思路分析
首先我们先来捋一捋这里的权限实现的思路。
DataScope 注解处理的内容叫做数据权限就是说你这个用户登录后能够访问哪些数据。传统的做法就是根据当前认证用户的 id 或者角色或者权限等信息去查询但是这种做法比较麻烦比较费事每次查询都要写大量 SQL而这些 SQL 中又有大量雷同的地方所以我们希望能够将之进行统一处理进而就引出了 DataScope 注解。
在 RuoYi-Vue 脚手架中将用户的数据权限分为了五类分别如下
1这个表示全部数据权限也就是这个用户登录上来之后可以访问所有的数据一般来说只有超级管理员具备此权限。
2这个表示自定义数据权限自定义数据权限就表示根据用户的角色查找到这个用户可以操作哪个部门的数据以此为依据进行数据查询。
3这个表示部门数据权限这个简单就是这个用户只能查询到本部门的数据。
4这个表示本部门及以下数据权限这个意思就是用户可以查询到本部门以及本部门下面子部门的数据。
5这个就表示这个用户仅仅只能查看自己的数据。在 TienChin 这个项目中数据权限也基本上是按照这个脚手架的设计来的我们只需要搞懂这里的实现思路将来就可以随心所欲的去自定义数据权限注解了。
2. 表结构分析
捋清楚了思路再来看看表结构。
这里涉及到如下几张表
sys_user用户表
sys_role角色表
sys_dept部门表
sys_user_role用户角色关联表
sys_role_dept角色部门关联表这几个表中有一些细节我来和大家梳理下。一个一个来看。
用户表中有一个 dept_id 表示这个用户所属的部门 id一个用户属于一个部门。
角色表中有一个字段叫做 data_scope表示这个角色所对应的数据权限取值就是 1-5含义就是我们上面所列出来的含义这个很重要哦。
部门表在设计的时候有一个 ancestors 字段通过这个字段可以非常方便的查询一个部门的子部门。
最后两张关联表就没啥好说了。
好了这些都分析完了我们就来看看具体的实现。
3. 具体实现
3.1 DataScope
先来看数据权限注解的定义
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface DataScope {/*** 部门表的别名*/public String deptAlias() default ;/*** 用户表的别名*/public String userAlias() default ;
}这个注解中有两个属性一个是 deptAlias 和 userAlias。由于数据权限实现的核心思路就是在要执行的 SQL 上动态追加查询条件那么动态追加的 SQL 必须要考虑到原本 SQL 定义时的部门表别名和用户表别名。这两个属性就是用来干这事的。
所以小伙伴们可能也看出来这个 DataScope 跟我们以前的注解还不太一样以前自定义的其他注解跟业务耦合度比较小这个 DataScope 跟业务的耦合度则比较高必须要看一下你业务 SQL 中对于部门表和用户表取的别名是啥然后配置到这个注解上。
因此DataScope 注解不算是一个特别灵活的注解咱们就抱着学习的态度了解下他的实现方式就行了。
3.2 切面分析
注解定义好了接下来就是切面分析了。 我把 RuoYi-Vue 脚手架中解析这个注解的切面代码列出来咱们逐行进行分析。
Aspect
Component
public class DataScopeAspect {/*** 全部数据权限*/public static final String DATA_SCOPE_ALL 1;/*** 自定数据权限*/public static final String DATA_SCOPE_CUSTOM 2;/*** 部门数据权限*/public static final String DATA_SCOPE_DEPT 3;/*** 部门及以下数据权限*/public static final String DATA_SCOPE_DEPT_AND_CHILD 4;/*** 仅本人数据权限*/public static final String DATA_SCOPE_SELF 5;/*** 数据权限过滤关键字*/public static final String DATA_SCOPE dataScope;Before(annotation(controllerDataScope))public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {clearDataScope(point);handleDataScope(point, controllerDataScope);}protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {// 获取当前的用户LoginUser loginUser SecurityUtils.getLoginUser();if (StringUtils.isNotNull(loginUser)) {SysUser currentUser loginUser.getUser();// 如果是超级管理员则不过滤数据if (StringUtils.isNotNull(currentUser) !currentUser.isAdmin()) {dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),controllerDataScope.userAlias());}}}/*** 数据范围过滤** param joinPoint 切点* param user 用户* param userAlias 别名*/public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) {StringBuilder sqlString new StringBuilder();for (SysRole role : user.getRoles()) {String dataScope role.getDataScope();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 {// 数据权限为仅本人且没有userAlias别名不查询任何数据sqlString.append( OR 10 );}}}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) ));}}}/*** 拼接权限sql前先清空params.dataScope参数防止注入*/private void clearDataScope(final JoinPoint joinPoint) {Object params joinPoint.getArgs()[0];if (StringUtils.isNotNull(params) params instanceof BaseEntity) {BaseEntity baseEntity (BaseEntity) params;baseEntity.getParams().put(DATA_SCOPE, );}}
}首先一上来就定义了五种不同的数据权限类型这五种类型咱们前面已经介绍过了这里就不再赘述了。接下来的 doBefore 方法是一个前置通知。由于 DataScope 注解是加在 service 层的方法上所以这里使用前置通知为方法的执行补充 SQL 参数具体思路是这样加了数据权限注解的 service 层方法的参数必须是对象并且这个对象必须继承自 BaseEntityBaseEntity 中则有一个 Map 类型的 params 属性我们如果需要为 service 层方法的执行补充一句 SQL那么就把补充的内容放到这个 params 变量中补充内容的 key 就是前面声明的 dataScopevalue 则是一句 SQL。在 doBefore 方法中先执行 clearDataScope 去清除 params 变量中已有的内容防止 SQL 注入因为这个 params 的内容也可以从前端传来然后执行 handleDataScope 方法进行数据权限的过滤。在 handleDataScope 方法中主要是查询到当前的用户然后调用 dataScopeFilter 方法进行数据过滤这个就是过滤的核心方法了。由于一个用户可能有多个角色所以在 dataScopeFilter 方法中要先遍历角色不同的角色有不同的数据权限这些不同的数据权限之间通过 OR 相连最终生成的补充 SQL 的格式类似这样 AND(xxx OR xxx OR xxx) 这样每一个 xxx 代表一个角色生成的过滤条件。接下来就是根据各种不同的数据权限生成补充 SQL 了如果数据权限为 1则生成的 SQL 为空即查询 SQL 不添加限制条件如果数据权限为 2表示自定义数据权限此时根据用户的角色查询出用户的部门生成查询限制的 SQL如果数据权限为 3表示用户的数据权限仅限于自己所在的部门那么将用户所属的部门拎出来作为查询限制如果数据权限为 4表示用户的权限是自己的部门和他的子部门那么就将用户所属的部门以及其子部门拎出来作为限制查询条件如果数据权限为 5表示用户的数据权限仅限于自己即只能查看自己的数据那么就用用户自身的 id 作为查询的限制条件。最后再把生成的 SQL 稍微处理下变成 AND(xxx OR xxx OR xxx) 格式这个就比较简单了就是字符串截取字符串拼接。
好啦大功告成接下来我们通过几个具体的查询来看看这个切面的应用。
4. 案例分析
我们来看一下 DataScope 注解三个具体的应用大家就明白了。
在 RuoYi-Vue 脚手架中这个注解主要有三个使用场景
查询部门。
查询角色。
查询用户。假设我现在以 ry 这个用户登录这个用户的角色是普通角色普通角色的数据权限是 2即自定义数据权限我们就来看看这个用户是如何查询数据的。
我们分别来看。
4.1 查询部门
首先查询部门的方法位于
org.javaboy.tienchin.system.service.impl.SysDeptServiceImpl#selectDeptList位置具体方法如下
Override
DataScope(deptAlias d)
public ListSysDept selectDeptList(SysDept dept) {return deptMapper.selectDeptList(dept);
}这个参数 SysDept 继承自 BaseEntity而 BaseEntity 中有一个 params 属性这个咱们前面也已经介绍过了不再赘述。
我们来看下这个 selectDeptList 方法对应的 SQL
sql idselectDeptVoselect d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time from sys_dept d
/sql
select idselectDeptList parameterTypeSysDept resultMapSysDeptResultinclude refidselectDeptVo/where d.del_flag 0if testdeptId ! null and deptId ! 0AND dept_id #{deptId}/ifif testparentId ! null and parentId ! 0AND parent_id #{parentId}/ifif testdeptName ! null and deptName ! AND dept_name like concat(%, #{deptName}, %)/ifif teststatus ! null and status ! AND status #{status}/if!-- 数据范围过滤 --${params.dataScope}order by d.parent_id, d.order_num
/select大家可以看到在 SQL 的最后面有一句 ${params.dataScope}就是把在 DataScopeAspect 切面中拼接的 SQL 追加进来。
所以这个 SQL 最终的形式类似下面这样
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time from sys_dept d where d.del_flag 0 AND (d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id 2 ) ) order by d.parent_id, d.order_num可以看到追加了最后面的 SQL 之后就实现了数据过滤这里是根据自定义数据权限进行过滤。那么这里还涉及到一个细节前面 SQL 在定义时用的表别名是什么我们在 DataScope 中指定的别名就要是什么。
4.2 查询角色
首先查询角色的方法位于 org.javaboy.tienchin.system.service.impl.SysRoleServiceImpl#selectRoleList 位置具体方法如下
Override
DataScope(deptAlias d)
public ListSysRole selectRoleList(SysRole role) {return roleMapper.selectRoleList(role);
}这个参数 SysRole 继承自 BaseEntity而 BaseEntity 中有一个 params 属性这个咱们前面也已经介绍过了不再赘述。
我们来看下这个 selectRoleList 方法对应的 SQL
sql idselectRoleVoselect distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,r.status, r.del_flag, r.create_time, r.remark from sys_role rleft join sys_user_role ur on ur.role_id r.role_idleft join sys_user u on u.user_id ur.user_idleft join sys_dept d on u.dept_id d.dept_id
/sql
select idselectRoleList parameterTypeSysRole resultMapSysRoleResultinclude refidselectRoleVo/where r.del_flag 0if testroleId ! null and roleId ! 0AND r.role_id #{roleId}/ifif testroleName ! null and roleName ! AND r.role_name like concat(%, #{roleName}, %)/ifif teststatus ! null and status ! AND r.status #{status}/ifif testroleKey ! null and roleKey ! AND r.role_key like concat(%, #{roleKey}, %)/ifif testparams.beginTime ! null and params.beginTime ! !-- 开始时间检索 --and date_format(r.create_time,%y%m%d) gt; date_format(#{params.beginTime},%y%m%d)/ifif testparams.endTime ! null and params.endTime ! !-- 结束时间检索 --and date_format(r.create_time,%y%m%d) lt; date_format(#{params.endTime},%y%m%d)/if!-- 数据范围过滤 --${params.dataScope}order by r.role_sort
/select大家可以看到在 SQL 的最后面有一句 ${params.dataScope}就是把在 DataScopeAspect 切面中拼接的 SQL 追加进来。
所以这个 SQL 最终的形式类似下面这样
select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, r.status, r.del_flag, r.create_time, r.remark from sys_role r left join sys_user_role ur on ur.role_id r.role_id left join sys_user u on u.user_id ur.user_id left join sys_dept d on u.dept_id d.dept_id where r.del_flag 0 AND (d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id 2 ) ) order by r.role_sort LIMIT ?可以看到追加了最后面的 SQL 之后就实现了数据过滤这里是根据自定义数据权限进行过滤。
过滤的逻辑就是根据用户所属的部门 id 找到用户 id然后根据用户 id 找到对应的角色 id最后再把查询到的角色返回。
其实我觉得查询部门和查询用户进行数据过滤这个都好理解当前登录用户能够操作哪些部门能够操作哪些用户这些都容易理解能操作哪些角色该如何理解呢特别是上面这个查询 SQL 绕了一大圈有的小伙伴可能会说系统本来不就有一个 sys_role_dept 表吗这个表就是关联角色信息和部门信息的直接拿着用户的部门 id 来这张表中查询用户能操作的角色 id 不就行行了此言差矣这里我觉得大家应该这样来理解用户所属的部门这是用户所属的部门用户能操作的部门是能操作的部门这两个之间没有必然联系。sys_user 表中的 dept_id 字段是表示这个用户所属的部门 id而 sys_role_dept 表中是描述某一个角色能够操作哪些部门这是不一样的把这个捋清楚了上面的 SQL 就好懂了。
4.3 查询用户
最后再来看查询用户。
查询用户的方法在 org.javaboy.tienchin.system.service.impl.SysUserServiceImpl#selectUserList 位置对应的 SQL 如下
select idselectUserList parameterTypeSysUser resultMapSysUserResultselect u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, 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 uleft join sys_dept d on u.dept_id d.dept_idwhere u.del_flag 0if testuserId ! null and userId ! 0AND u.user_id #{userId}/ifif testuserName ! null and userName ! AND u.user_name like concat(%, #{userName}, %)/ifif teststatus ! null and status ! AND u.status #{status}/ifif testphonenumber ! null and phonenumber ! AND u.phonenumber like concat(%, #{phonenumber}, %)/ifif testparams.beginTime ! null and params.beginTime ! !-- 开始时间检索 --AND date_format(u.create_time,%y%m%d) gt; date_format(#{params.beginTime},%y%m%d)/ifif testparams.endTime ! null and params.endTime ! !-- 结束时间检索 --AND date_format(u.create_time,%y%m%d) lt; date_format(#{params.endTime},%y%m%d)/ifif testdeptId ! null and deptId ! 0AND (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这个就比较容易了根据部门查询用户或者就是查询当前用户。最终生成的 SQL 类似下面这样这是自定义数据权限即根据用户部门查找用户
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, 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 2 ) ) LIMIT ?5. 小结
好啦通过上面的分析大家对于 DataScope 应该也认识的差不多了吧