基于Mybatis拦截器实现数据范围权限

mybatis 拦截器
mybatis拦截器优先级
@order
@dependson
@postconstruct
applicationrunner
前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加sql来实现。
比如员工打卡记录表,有 id、name、dpt_id、company_id 等字段,后两个表示部门 id 和分公司 id。
查看员工打卡记录 sql 为:select id,name,dpt_id,company_id from t_record
当一个总部账号可以查看全部数据此时,sql 无需改变。因为他可以看到全部数据。
当一个部门管理员权限员工查看全部数据时,sql 需要在末属添加 where dpt_id = #{dpt_id}
如果每个功能模块都需要手动写代码去拿到当前登陆用户的所属部门,然后手动添加where条件,就显得非常的繁琐。
因此,可以通过 mybatis 的拦截器拿到查询 sql 语句,再自动改写 sql。
mybatis 拦截器
mybatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,mybatis 允许使用插件来拦截的方法调用包括:
executor (update, query, flushstatements, commit, rollback, gettransaction, close, isclosed)
parameterhandler (getparameterobject, setparameters)
resultsethandler (handleresultsets, handleoutputparameters)
statementhandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 mybatis 发行包中的源代码。如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。因为在试图修改或重写已有方法的行为时,很可能会破坏 mybatis 的核心模块。这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 mybatis 提供的强大机制,使用插件是非常简单的,只需实现 interceptor 接口,并指定想要拦截的方法签名即可。
分页插件 pagehelper 就是一个典型的通过拦截器去改写 sql 的。
可以看到它通过注解 @intercepts 和签名 @signature 来实现,拦截 executor 执行器,拦截所有的 query 查询类方法。
我们可以据此也实现自己的拦截器。
import com.skycomm.common.util.user.cpip2userdeptvo;import com.skycomm.common.util.user.cpip2userdeptvoutil;import lombok.extern.slf4j.slf4j;import org.apache.commons.lang3.stringutils;import org.apache.ibatis.cache.cachekey;import org.apache.ibatis.executor.executor;import org.apache.ibatis.mapping.boundsql;import org.apache.ibatis.mapping.mappedstatement;import org.apache.ibatis.mapping.sqlsource;import org.apache.ibatis.plugin.interceptor;import org.apache.ibatis.plugin.intercepts;import org.apache.ibatis.plugin.invocation;import org.apache.ibatis.plugin.signature;import org.apache.ibatis.session.resulthandler;import org.apache.ibatis.session.rowbounds;import org.springframework.stereotype.component;import org.springframework.web.context.request.requestattributes;import org.springframework.web.context.request.requestcontextholder;import org.springframework.web.context.request.servletrequestattributes;import javax.servlet.http.httpservletrequest;import java.lang.reflect.method;@component@intercepts({        @signature(type = executor.class, method = query, args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class}),        @signature(type = executor.class, method = query, args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class, cachekey.class, boundsql.class}),})@slf4jpublic class mysqlinterceptor implements interceptor {    @override    public object intercept(invocation invocation) throws throwable {        mappedstatement statement = (mappedstatement) invocation.getargs()[0];        object parameter = invocation.getargs()[1];        boundsql boundsql = statement.getboundsql(parameter);        string originalsql = boundsql.getsql();        object parameterobject = boundsql.getparameterobject();        sqllimit sqllimit = islimit(statement);        if (sqllimit == null) {            return invocation.proceed();        }        requestattributes req = requestcontextholder.getrequestattributes();        if (req == null) {            return invocation.proceed();        }        //处理request        httpservletrequest request = ((servletrequestattributes) req).getrequest();        cpip2userdeptvo uservo = cpip2userdeptvoutil.getuserdeptinfo(request);        string depid = uservo.getdeptid();        string sql = addtenantcondition(originalsql, depid, sqllimit.alis());        log.info(原sql:{}, 数据权限替换后的sql:{}, originalsql, sql);        boundsql newboundsql = new boundsql(statement.getconfiguration(), sql, boundsql.getparametermappings(), parameterobject);        mappedstatement newstatement = copyfrommappedstatement(statement, new boundsqlsqlsource(newboundsql));        invocation.getargs()[0] = newstatement;        return invocation.proceed();    }    /**     * 重新拼接sql     */    private string addtenantcondition(string originalsql, string depid, string alias) {        string field = dpt_id;        if(stringutils.isnoneblank(alias)){            field = alias + . + field;        }        stringbuilder sb = new stringbuilder(originalsql);        int index = sb.indexof(where);        if (index  1, 数据权限替换后的 sql:select * from person where dpt_id = 234 and id > 1。
但是在使用 pagehelper 进行分页的时候还是有问题。
可以看到先执行了 _count 方法也就是 pagehelper,再执行了自定义的拦截器。
在我们的业务方法中注入 sqlsessionfactory。
@autowired@lazyprivate list sqlsessionfactorylist;  
pageinterceptor 为 1,自定义拦截器为 0,跟 order 相反,pageinterceptor 优先级更高,所以越先执行。
基于 spring boot + mybatis plus + vue & element 实现的后台管理系统 + 用户小程序,支持 rbac 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/yunaiv/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
mybatis拦截器优先级
@order
通过 @order 控制 pageinterceptor 和 mysqlinterceptor 可行吗?
将 mysqlinterceptor 的加载优先级调到最高,但测试证明依然不行。
定义 3 个类。
@component@order(2)public class ordertest1 {    @postconstruct    public void init(){        system.out.println( 00000 init);    }}@component@order(1)public class ordertest2 {    @postconstruct    public void init(){        system.out.println( 00001 init);    }}@component@order(0)public class ordertest3 {    @postconstruct    public void init(){        system.out.println( 00002 init);    }}  
ordertest1,ordertest2,ordertest3 的优先级从低到高。
顺序预期的执行顺序应该是相反的:
00002 init00001 init00000 init  
但事实上执行的顺序是
00000 init00001 init00002 init  
@order 不控制实例化顺序,只控制执行顺序。@order 只跟特定一些注解生效 如:@compent、 @service、@aspect … 不生效的如:@webfilter
所以这里达不到预期效果。
@priority 类似,同样不行。
@dependson
使用此注解将当前类将在依赖类实例化之后再执行实例化。
在 mysqlinterceptor 上标记@dependson(queryinterceptor)
启动报错,
这个时候 queryinterceptor 还没有实例化对象。
@postconstruct
@postconstruct 修饰的方法会在服务器加载 servlet 的时候运行,并且只会被服务器执行一次。在同一个类里,执行顺序为顺序如下:constructor > @autowired > @postconstruct。
但它也不能保证不同类的执行顺序。
pagehelper 的 springboot start 也是通过这个来初始化拦截器的。
applicationrunner
在当前 springboot 容器加载完成后执行,那么这个时候 pagehelper 的拦截器已经加入,在这个时候加入自定义拦截器,就能达到我们想要的效果。
仿照 pagehelper 来写。
@componentpublic class interceptrunner implements applicationrunner {    @autowired    private list sqlsessionfactorylist;    @override    public void run(applicationarguments args) throws exception {        mysqlinterceptor mybatisinterceptor = new mysqlinterceptor();        for (sqlsessionfactory sqlsessionfactory : sqlsessionfactorylist) {            org.apache.ibatis.session.configuration configuration = sqlsessionfactory.getconfiguration();            configuration.addinterceptor(mybatisinterceptor);        }    }}  
再执行,可以看到自定义拦截器在拦截器链当中下标变为了 1(优先级与 order 刚好相反)
后台打印结果,达到了预期效果。


立昂微电子投资50亿晶圆项目开工
有源高精度高隔离变送器
华为云底气十足 属于中国AI的黄金时代正在到来
我国自主研发的首个31.25kW铁-铬液流电池电堆“容和一号”成功下线
185M-185M+RF适配器直通1.85mm公头到1.85mm公脚DC-67GHz 50Ω
基于Mybatis拦截器实现数据范围权限
如何加快产品设计周期和加快市场投放速度
摩托罗拉首款弹出式全面屏机型One Hyper 拥有前置3200万像素摄像头
中国手机在印度销量不降反升 印度被打脸啪啪响
电子专业常见术语50条
北通与中国力量共荣耀,北通游戏厅率先宣布支持鸿蒙系统
浅谈华为的麒麟A1芯片麒麟SoC系列芯片的“技术下沉”
电动拖把好用吗?蒸汽除菌给你更健康的家
SCL程序: 检测存储区的填充量示例介绍
光通信芯片的概念以及我国在光通信行业的地位
IES-LM-79测试失效分析
关于选择合适的线性稳压器的心得分享
分析师:中国率先进入区块链3.0时代
雷蛇锐蝮蛇双模游戏鼠标评测 外观与性能都令人满意
东软载波微电子入选上海市2022年“专精特新”与“创新型”企业名单