之前写过一篇关于数据权限的文章【【若依】开源框架学习笔记 06 - 数据权限 】,在原版若依和 3.4.0 及其以下版本的 RuoYi-Vue-Plus 中使用的都是基于切面方式实现的数据权限功能。
在 RuoYi-Vue-Plus 3.5.0 中,狮子大佬 重写了数据权限的实现。
对于新的数据权限的使用方法也在框架wiki中有说明,所以本文只是在此基础上做简单的分析,仅作为学习之用,如有错误也请大佬们指出。
框架中没有直接使用 Mybatis Plus 原生的数据权限插件,但是从写法来看应该对此有所借鉴,所以也可以参考一下 Mybatis Plus 的数据权限插件源码实现自己的数据权限功能。
框架wiki中关于数据权限的简单说明:
MybatisPlusConfig
在 MybatisPlusInterceptor 拦截器中加入了自定义的数据权限拦截器组件 PlusDataPermissionInterceptor。
MybatisPlusInterceptor
PlusDataPermissionInterceptor
数据权限拦截器继承了 JsqlParserSupport Jsql 解析器,实现了 InnerInterceptor 拦截器接口:
JsqlParserSupport
InnerInterceptor
beforeQuery
Executor.query
beforePrepare
StatementHandler.prepare
processSelect
processUpdate
processDelete
在 Mybatis Plus 数据权限插件源码中,也有类似的拦截器 DataPermissionInterceptor。不过只重写了两个方法 beforeQuery 和 processSelect。
DataPermissionInterceptor
PlusDataPermissionHandler
在拦截器处理查询的方法中有调用 setWhere 方法进行 SQL 语句 Where 条件的设置。主要的逻辑就是调用处理器获取 SQL 的方法 getSqlSegment。
setWhere
getSqlSegment
PlusDataPermissionHandler#getSqlSegment 当然这个方法也是根据框架进行了扩展,源码中只是一个简单的接口。
PlusDataPermissionHandler#getSqlSegment
TestDemoController#list() 这是一个新增的方法,原来 Demo 中有一个加上了分页的测试方法,这里暂时排除分页进行测试。
TestDemoController#list()
TestDemoServiceImpl#queryList() buildQueryWrapper 是根据请求参数构建条件构造器,测试方法中不传参数,则会查询全部数据。
TestDemoServiceImpl#queryList()
buildQueryWrapper
ServicePlusImpl#listVo() ServicePlusImpl 是封装的通用实现类。
ServicePlusImpl#listVo()
ServicePlusImpl
BaseMapperPlus#selectVoList() this.selectList(wrapper) 就是调用对应的 Mapper 的查询列表方法。
BaseMapperPlus#selectVoList()
this.selectList(wrapper)
TestDemoMapper#selectList 这里是对原生方法的重写,加入了自定义数据权限注解 @DataPermission 以及 @DataColumn。
TestDemoMapper#selectList
@DataPermission
@DataColumn
如果 SQL 是自定义方法也是在对应方法上加上注解即可。
beforeQuery()
PlusDataPermissionInterceptor#beforeQuery()
首先判断忽略注解。 InterceptorIgnoreHelper#willIgnoreDataPermission() InterceptorIgnoreHelper#willIgnore() 结果为 false。
InterceptorIgnoreHelper#willIgnoreDataPermission()
InterceptorIgnoreHelper#willIgnore()
false
回到 beforeQuery 中,继续判断注解是否有效。 PlusDataPermissionHandler#isInvalid() 结果为 false。
PlusDataPermissionHandler#isInvalid()
最终得到 SQL 语句。 SQL 解析器 JsqlParserSupport#parserSingle 执行SQL解析。
JsqlParserSupport#parserSingle
JsqlParserSupport#processParser()
调用处理查询的方法。
PlusDataPermissionInterceptor#processSelect()
设置 Where 条件。 PlusDataPermissionInterceptor#setWhere()
PlusDataPermissionInterceptor#setWhere()
PlusDataPermissionHandler#getSqlSegment()
根据注解以及权限获取 SQL 片段。
1、通过方法 findAnnotation(mappedStatementId) 获取到注解内容。 PlusDataPermissionHandler#findAnnotation() 首先从 dataPermissionCacheMap 中获取,没有则通过 AnnotationUtil.getAnnotation() 方法获取,并存到 dataPermissionCacheMap 中。 然后返回得到的注解内容。
findAnnotation(mappedStatementId)
PlusDataPermissionHandler#findAnnotation()
AnnotationUtil.getAnnotation()
2、获取当前用户并判断权限。 首先通过 DataPermissionHelper.getVariable("user") 方法获取用户信息,如果没有,则通过查询数据库获取,并把用户信息通过 DataPermissionHelper 存到上下文中。 此处为超级管理员直接返回 where 语句。
DataPermissionHelper.getVariable("user")
DataPermissionHelper
至此完成了 beforeQuery() 整个方法的流程。
beforePrepare()
注:非超级管理员测试的流程和超级管理员基本相同,下面只分析不同的部分(主要是在where条件的获取不一样),如果有不了解的地方可以多 Debug。
PlusDataPermissionHandler#getSqlSegment() 非超级管理员用户,通过 buildDataFilter() 方法构造 SQL 查询条件。
buildDataFilter()
PlusDataPermissionHandler#buildDataFilter()
@sdss.getDeptAndChild
SysDataScopeServiceImpl#getDeptAndChild()
where OR ...
SQL 查询条件构造完成,返回 PlusDataPermissionHandler#getSqlSegment() 方法。 由上面可得到最终的 SQL 语句查询条件。其余流程和上面超级管理员基本一致。