前言:

若依是一个很不错的入门级后台管理系统,它里边的好多精妙绝伦的结构设计和代码规范都是值得我们学习的。今天要研究的就是他的数据权限这一块儿的东西,还记得刚接触若依的时候点开角色管理的数据权限都不敢操作了……


若依中的数据权限实现逻辑:

在若依中处理数据权限的时候,它非常巧妙的利用AOP+注解的方式把SQL切入到参数中,进而影响最终的SQL语句。那么我们现在解读一下它的源码(各个版本的java文件位置可能会有变动,找不见的同学可以全局搜索类名查找):

/*** 数据权限过滤注解* * @author ruoyi*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{/*** 部门表的别名*/public String deptAlias() default "";/*** 用户表的别名*/public String userAlias() default "";
}
/*** 数据过滤处理* * @author quanxingyi*/
@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";// 配置织入点@Pointcut("@annotation(com.quanxingyi.framework.aspectj.lang.annotation.DataScope)")public void dataScopePointCut(){}@Before("dataScopePointCut()")public void doBefore(JoinPoint point) throws Throwable{handleDataScope(point);}protected void handleDataScope(final JoinPoint joinPoint){// 获得注解DataScope controllerDataScope = getAnnotationLog(joinPoint);if (controllerDataScope == null){return;}// 获取当前的用户User currentUser = ShiroUtils.getSysUser();if (currentUser != null){// 如果是超级管理员,则不过滤数据if (!currentUser.isAdmin()){dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),controllerDataScope.userAlias());}}}/*** 数据范围过滤* * @param joinPoint 切点* @param user 用户* @param deptAlias 部门别名* @param userAlias 用户别名*/public static void dataScopeFilter(JoinPoint joinPoint, User user, String deptAlias, String userAlias){StringBuilder sqlString = new StringBuilder();for (Role 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 1=0 ");}}}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) + ")");}}}/*** 是否存在注解,如果存在就获取*/private DataScope getAnnotationLog(JoinPoint joinPoint){Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method != null){return method.getAnnotation(DataScope.class);}return null;}
}

以上是若依定义的数据权限注解和AOP方法,这里简单解读一下源码逻辑。AOP 类中定义注解切点,然后在目标方法执行前先从Shiro中获取到用户信息,然后获取用户的所有角色,遍历这些角色,从角色上取到数据范围,针对不同的数据范围拼接出不同的SQL,然后把SQL存入到BaseEntity类的params属性中。

/*** 根据条件分页查询用户列表* * @param user 用户信息* @return 用户信息集合信息*/@Override@DataScope(deptAlias = "d", userAlias = "u")public List<User> selectUserList(User user){// 生成数据权限过滤条件return userMapper.selectUserList(user);}
<select id="selectUserList" parameterType="User" resultMap="UserResult">select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,u.shop_id, d.dept_name, d.leader,s.shop_name from sys_user uleft join sys_dept d on u.dept_id = d.dept_idleft join  shop_info  s on  u.shop_id= s.idwhere u.del_flag = '0'<if test="loginName != null and loginName != ''">AND u.login_name like concat('%', #{loginName}, '%')</if><if test="status != null and status != ''">AND u.status = #{status}</if><if test="shopId != null and shopId != ''">AND u.shop_id = #{shopId}</if><if test="userType != null and userType != ''">AND u.user_type = #{userType}</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') &gt;= 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') &lt;= 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>

使用时只需要在目标方法上加上注解,并在xml文件中的sql最末尾处追加${params.dataScope}就可以了。


自己改编后的具体运用方式:

以上就是若依源码的逻辑。但是,我们使用若依自动生成的代码中,xml文件中的SQL与上面的SQL区别太大了(最明显的别名问题,我们自己生成出来的SQL是没有别名的),它的SQL是连表查询,已经在SQL中写死了,如果我们每次需要做数据权限的时候都大改XML的话代价也太大了,也容易出错。于是我就又写了一个切点出来:

/*** 自己写的数据权限注解* @author ren* @description* @date 2022年01月21日 13:32:33*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataScope {}
/*** 数据过滤处理* * @author ruoyi*/
@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);}@Before("@annotation(myDataScope)")public void doMyBefore(JoinPoint point, MyDataScope myDataScope) throws Throwable{clearDataScope(point);handleDataScope(point, myDataScope);}protected void handleDataScope(final JoinPoint joinPoint, Object dataScope){// 获取当前的用户SysUser currentUser = ShiroUtils.getSysUser();if (currentUser != null){// 如果是超级管理员,则不过滤数据if (!currentUser.isAdmin()){if(dataScope instanceof DataScope){DataScope controllerDataScope = (DataScope)dataScope;dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),controllerDataScope.userAlias());}else if(dataScope instanceof MyDataScope){dataScopeFilter(joinPoint, currentUser);}}}}/*** 数据范围过滤* * @param joinPoint 切点* @param user 用户* @param deptAlias 部门别名* @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 1=0 ");}}}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) + ")");}}}/*** 数据范围过滤** @param joinPoint 切点* @param user 用户*/public static void dataScopeFilter(JoinPoint joinPoint, SysUser user){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 = {} ) ", role.getRoleId()));}else if (DATA_SCOPE_DEPT.equals(dataScope))  //部门数据权限{sqlString.append(StringUtils.format(" OR dept_id = {} ", 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 ) )", user.getDeptId(), user.getDeptId()));}else if (DATA_SCOPE_SELF.equals(dataScope))  //仅本人数据权限{sqlString.append(StringUtils.format(" OR create_user_id = {} ", user.getUserId()));}}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, "");}}
}

这里我们自定义一个注解,然后新加了一个切点进来(这个示例和之前的例子不一样是因为两个版本,但他们的操作逻辑是一样的),然后新写了一个dataScopeFilter方法,重新组织了一下SQL的拼接。

/*** 查询商品列表* * @param goods 商品* @return 商品*/@Override@MyDataScopepublic List<Goods> selectGoodsList(Goods goods){return goodsMapper.selectGoodsList(goods);}
<sql id="selectGoodsVo">select id, create_date, create_user, create_user_id, dept_id, goods_name, goods_price from goods</sql><select id="selectGoodsList" parameterType="Goods" resultMap="GoodsResult"><include refid="selectGoodsVo"/><where>  <if test="goodsName != null  and goodsName != ''"> and goods_name like concat('%', #{goodsName}, '%')</if>${params.dataScope}</where></select>

使用的时候也是直接在目标方法上加上注解就可以,也是在XML文件中SQL语句的后面追加${params.dataScope},这样做的好处就是我们的XML不用做太大改变,亲测有效,包括多角色也是支持的。为了一劳永逸,我们可以修改代码生成的vm文件,一般情况下只是做列表查询时才需要做数据权限,所以我们只改这两个地方就可以:
serviceImpl.java.vm文件中上面加上导包的代码,下面加上我们自定义的注解

package ${packageName}.service.impl;import java.util.List;
import org.kangjia.common.annotation.MyDataScope;
#if($table.tree)......
/*** 查询${functionName}列表* * @param ${className} ${functionName}* @return ${functionName}*/@Override@MyDataScopepublic List<${ClassName}> select${ClassName}List(${ClassName} ${className}){return ${className}Mapper.select${ClassName}List(${className});}

mapper.xml.vm文件中只需要在查询列表的select中追加${params.dataScope}就好

<select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result"><include refid="select${ClassName}Vo"/><where>
#foreach($column in $columns)
#set($queryType=$column.queryType)
#set($javaField=$column.javaField)
#set($javaType=$column.javaType)
#set($columnName=$column.columnName)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.query)
#if($column.queryType == "EQ")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName = #{$javaField}</if>
#elseif($queryType == "NE")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName != #{$javaField}</if>
#elseif($queryType == "GT")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt; #{$javaField}</if>
#elseif($queryType == "GTE")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt;= #{$javaField}</if>
#elseif($queryType == "LT")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt; #{$javaField}</if>
#elseif($queryType == "LTE")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt;= #{$javaField}</if>
#elseif($queryType == "LIKE")<if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName like concat('%', #{$javaField}, '%')</if>
#elseif($queryType == "BETWEEN")<if test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''"> and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}</if>
#end
#end
#end${params.dataScope}</where>
#if($table.tree)order by ${tree_parent_code}
#end</select>

需要注意的点:

如果理解了以上的内容,那么你就可以自己在若依中自定义数据权限了,或者掌握精髓后可以自己设计一套如此的数据权限模块。这里给大家留个拓展,必要的时候可以给@MyDataScope加属性,然后在DataScopeAspect中动态决定什么条件下即时有权限也不能访问数据。

最后给大家说几点需要注意的:

  • 加注解的方法的参数必须是BaseEntity类型或是它的子类
  • 我们的数据库表结构必须要有的字段:create_user_id 和 dept_id (字段名可以修改但必须和我们重载的dataScopeFilter方法中的SQL字段一致)

如何在若依中做数据权限相关推荐

  1. sql 账号查询一个表勾选那个权限_Spring Cloud微服务架构中的数据权限DataPermision实现方案...

    Spring Cloud微服务架构中的数据权限DataPermision实现方案 一.出现原因 在Spring Cloud的微服务架构中,常见的权限控制除了菜单权限外,还有数据权限DataPermis ...

  2. 如何在应用系统中实现数据权限的控制功能

    在很多应用行业里面,都对数据的权限做了特别的声明,如对于销售,财务的数据,它们是非常敏感的,因此要求对数据权限进行控制,对于基于集团性的应用系统而言,就更多需要控制好各自公司的数据了.如默认只能看本公 ...

  3. 权限系统中的数据权限就该这么设计,yyds!

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  4. 权限系统中的数据权限就该这么设计,yyds

    在项目实际开发中我们不光要控制一个用户能访问哪些资源,还需要控制用户只能访问资源中的某部分数据. 控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC,但是控制用户只能访问某部分资源(即我们 ...

  5. 使用DataX将mysql中做数据导出时 提示 java.sql.SQLException: Could not retrieve transation read-only status server

    原因是我的数据库是8.0以上版本,但是datax本身提供的jar包是5.1版本,将datax/plugins目录下的reader/writer中的mysqlreader/mysqlwriter的lib ...

  6. 中后台学习笔记 – 数据权限

    编辑导语:我们常常在数据权限中找不到合适的门路,中后台的数据权限该怎么设计才能够满足我们所需业务的数据权限架构体系?作者给我们从三个方面讲解了有关数据权限的知识,我们一起来看下吧. 曾经看到过这样一个 ...

  7. 【权限设计】最好的权限设计,是先区分功能权限和数据权限

    本文为我们介绍了功能权限和数据权限的不同点.以及不同部分中的要点与注意事项. 做2B的系统总是不可回避的遇上权限问题,他不是核心业务却又必不可少,而且总是牵一发而动全身,更要命的是不同客户组织架构完全 ...

  8. 数据权限框架:一个实现数据权限与业务模块完全分离,让数据权限变成独立功能模块的数据权限框架

    meng框架 介绍 meng框架是一个实现了行数据权限和列数据权限的数据权限框架: meng框架能让数据权限变成一个独立的功能模块,与业务模块完全分离,在已经实现的业务逻辑中添加数据权限不需要对原来的 ...

  9. 从零开始java数据权限篇:数据权限

    目录 一:数据权限的产生 二:数据权限的数据切割 1.数据对应的层级图 2.用户数据查询 3.用户流程管理 4.部门-岗位-公司查询拓扑图 三.说明 一:数据权限的产生 在一个后管系统中,由2个最重要 ...

最新文章

  1. 2019牛客暑期多校训练营(第九场)
  2. app inventor离线版_百度要哭了!今日头条出了搜索引擎了,还做了APP
  3. AsyncHttpClien访问网络案例分析
  4. Nvidia GPU驱动与CUDA、Ubuntu内核兼容性问题的解决日志
  5. 精通Android自定义View(十六)invalidate方法和requestLayout方法
  6. iPhone 12系列又有新变化:免费的有线耳机可能不再有
  7. vb 字符串长度_学习VB编程第5天 基础知识需要一点点积累
  8. java log4j trace_关于LOG4J中的日志级别TRACE
  9. Java程序员修炼之道 人民邮电出版社 吴海星译
  10. 轻型货车悬架系统的设计(设计说明书+CAD图纸+开题报告+任务书+答辩相关材料)
  11. 【7】win10 病毒和威胁防护服务停止,立即重启报错
  12. python数据不足位数补0
  13. 关于会声会影导入视频出现卡顿花屏的解决办法
  14. 勒索病毒来自美国网络武器库 破解靠重装系统
  15. ​力扣解法汇总1728-猫和老鼠 II
  16. 五年级上册计算机教案闽教版,闽教版五年级上册信息技术教案
  17. 计量经济学计算机答案14章,伍德里奇---计量经济学第8章部分计算机习题详解(STATA)...
  18. H3C smart-link实验 C套拆解
  19. 集算器入门之安装与基本使用
  20. 工程项目管理丁士昭第二版_《工程项目管理》丁士昭第二版 重点内容

热门文章

  1. hitcon2014_stkof
  2. C#中两个常用委托类型
  3. mysql删除constraint_删除数据库表有约束(constraint)的列
  4. I2C走线技巧、及上拉电阻、电源电压、总线电容三者间的函数关系
  5. mockit基础使用
  6. dubbo(开源分布式服务框架)1---------Dubbo需要四大基本组件
  7. 2.6.30内核Netfilter的简单例子、四(filterIp)
  8. 超级搞笑~~好色的美女
  9. Jenkins部署瘦身jar包
  10. DBeaver执行sql脚本报错:CreateProcess error=193, %1 不是有效的 Win32 应用程序。