前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加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 优先级更高,所以越先执行。
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 刚好相反)
后台打印结果,达到了预期效果。
变频电源谐波的危害与处理
Buck芯片改Buck-boost注意事项
CMOS低中频蓝牙射频收发器电路模块设计
音圈马达无人机在长天广场上演了一场震撼人心的无人机灯光秀
比科奇携手中国科学院微电子所推动5G研究和人才培养
如何实现基于Mybatis拦截器实现数据范围权限呢?
三星Galaxy S21 Ultra的详细参数都在这了
苹果iOS 13.1系统加入非原装配件检测机制,但不限制使用
松下为何在2020年停产水银灯
陶瓷天线的种类和工作原理
魅族note5抢春晚风头?魅族要在除夕夜开“演唱会”!
新加坡科学家创造出一种能在水下畅游10小时的模拟蝠鲼机器人
厦门柔性电子研究院联合厦门大学共同成立联合研发中心
小间距LED为高端会议室提供高效解决方案
电力管理系统在生物制药生产平台装修项目中的应用
Nokia9PureView将在近期登陆欧洲市场 8月发布高配版同时支持5G网络
董明珠未放弃格力手机:将跟上柔性屏与5G
MATHWORKS应用基于模型的设计为ISO 26262项目提供定制服务
华谊堪称“赔本大甩卖”,损益约-14734.34 万元
Tesla 被曝储存大量未加密个人数据,你的特斯拉知道些什么