DataPermissionInterceptor源码解读

打印 上一主题 下一主题

主题 1880|帖子 1880|积分 5640

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本文首发在我的博客:https://blog.liuzijian.com/post/mybatis-plus-source-data-permission-interceptor.html
一、概述

DataPermissionInterceptor是MyBatis-Plus中的一个拦截器插件,用于实现数据权限功能,它将查询、删除和修改的SQL进行拦截并获得要执行的SQL,并剖析出SQL中的表和原有条件,通过一个DataPermissionHandler接口来回调获取每个表的数据权限条件,再和原有的条件拼接在一起形成新的SQL,执行重写后的新SQL,从而实现数据权限功能。因为添加操作无需数据权限控制,因此不处理添加的情况。
本类的实现较为简朴,因为对于数据权限来说,对于比较复杂的查询SQL的剖析逻辑基本已经由父类完成,具体见:BaseMultiTableInnerInterceptor源码解读,本类作为子类将查询SQL调用父类进行剖析重写即可,对于删除和更新的SQL仅仅针对delete和update本身的where条件进行处理,而且是单表操作,因此对于删除和更新来说,只是将表原有条件和数据权限条件做简朴的拼接即可。
本文基于MyBatis-Plus的3.5.9版本的源码,并fork了代码: https://github.com/changelzj/mybatis-plus/tree/lzj-3.5.9
  1. public class DataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
  2.     private DataPermissionHandler dataPermissionHandler;
  3.     @SuppressWarnings("RedundantThrows")
  4.     @Override
  5.     public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {...}
  6.     @Override
  7.     public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {...}
  8.     @Override
  9.     protected void processSelect(Select select, int index, String sql, Object obj) {...}
  10.     protected void setWhere(PlainSelect plainSelect, String whereSegment) {...}
  11.     @Override
  12.     protected void processUpdate(Update update, int index, String sql, Object obj) {...}
  13.     @Override
  14.     protected void processDelete(Delete delete, int index, String sql, Object obj) {...}
  15.     protected Expression getUpdateOrDeleteExpression(final Table table, final Expression where, final String whereSegment) {...}
  16.     @Override
  17.     public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {...}
  18. }
复制代码
二、源码解读

2.1 beforeQuery

该方法从InnerInterceptor接口继承而来,是剖析查询SQL的起点,MyBatis-Plus执行时就是对实现InnerInterceptor接口的类中的对应方法进行回调的,会传入要执行的SQL并接收重写后的SQL来实现对SQL的修改,在查询SQL执行前进行拦截并调用beforeQuery(),beforeQuery()中再去调用parserSingle()
parserSingle()是从父类BaseMultiTableInnerInterceptor自JsqlParserSupport抽象类间接继承而来的,JsqlParserSupport类的功能非常简朴,作用是判断SQL是增删改查的哪一种类型,然后分别调用对应的方法开始剖析。
当调用parserSingle()并传入SQL时,会在JsqlParserSupport的processParser()方法中先判断是哪一种Statement,然后分别强转为具体的Select、Update、Delete、Insert对象,再调用该类间接继承并重写的processSelect()方法并传入Select对象。
processSelect()方法会再调用父类的processSelectBody()对查询SQL进行剖析,对于剖析到的每张表和已有条件,再去调用父类的builderExpression()进而再调用buildTableExpression()获取当前表对应的数据权限过滤条件再和已有条件进行拼接。
  1. @SuppressWarnings("RedundantThrows")
  2. @Override
  3. public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  4.     if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
  5.         return;
  6.     }
  7.     PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
  8.     mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
  9. }
复制代码
2.2 beforePrepare

该方法和beforeQuery()一样,也是从InnerInterceptor接口中继承而来,因为添加修改和删除SQL都要预编译,因此该方法可作为剖析删除和修改SQL的起点,差别的是beforePrepare()调用的是JsqlParserSupport中继承来的parserMulti(),因为查询语句只能一次执行一条,但是增删改语句可以用分号隔断一次执行多条,故需调用parserMulti()将多个语句循环拆开,然后判断并分别强转为具体的Select、Update、Delete、Insert对象,再分别调用该类间接继承并重写的processDelete()、processUpdate()方法并分别传入Delete,Update对象,然后直接剖析出要删除和更新数据的表和已有删除更新条件,调用父类的andExpression()进而在调用buildTableExpression()来拼接数据权限过滤条件。
  1. @Override
  2. public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
  3.     PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
  4.     MappedStatement ms = mpSh.mappedStatement();
  5.     SqlCommandType sct = ms.getSqlCommandType();
  6.     if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
  7.         if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
  8.             return;
  9.         }
  10.         PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
  11.         mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
  12.     }
  13. }
复制代码
2.3 processSelect

开始一个对查询SQL的剖析,当前版本走的是if (dataPermissionHandler instanceof MultiDataPermissionHandler)的新版本的逻辑,先调用processSelectBody()进行剖析,对于WITH中的结构,又在调用processSelectBody()后单独组织了一段针对WITH中的查询的剖析逻辑。旧版本应该是直接获取where后面的条件直接传递给dataPermissionHandler,在dataPermissionHandler中对where进行追加,而新版本代码是将剖析到的表传到dataPermissionHandler,传入的是表名返回表的数据权限条件
  1. @Override
  2. protected void processSelect(Select select, int index, String sql, Object obj) {
  3.     if (dataPermissionHandler == null) {
  4.         return;
  5.     }
  6.     if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
  7.         // 参照 com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.processSelect 做的修改
  8.         final String whereSegment = (String) obj;
  9.         processSelectBody(select, whereSegment);
  10.         List<WithItem> withItemsList = select.getWithItemsList();
  11.         if (!CollectionUtils.isEmpty(withItemsList)) {
  12.             withItemsList.forEach(withItem -> processSelectBody(withItem, whereSegment));
  13.         }
  14.     } else {
  15.         // 兼容原来的旧版 DataPermissionHandler 场景
  16.         if (select instanceof PlainSelect) {
  17.             this.setWhere((PlainSelect) select, (String) obj);
  18.         } else if (select instanceof SetOperationList) {
  19.             SetOperationList setOperationList = (SetOperationList) select;
  20.             List<Select> selectBodyList = setOperationList.getSelects();
  21.             selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
  22.         }
  23.     }
  24. }
复制代码
2.4 setWhere

这段代码应该是为旧版本用的,没有走到
  1. /**
  2. * 设置 where 条件
  3. *
  4. * @param plainSelect  查询对象
  5. * @param whereSegment 查询条件片段
  6. */
  7. protected void setWhere(PlainSelect plainSelect, String whereSegment) {
  8.     if (dataPermissionHandler == null) {
  9.         return;
  10.     }
  11.     // 兼容旧版的数据权限处理
  12.     final Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), whereSegment);
  13.     if (null != sqlSegment) {
  14.         plainSelect.setWhere(sqlSegment);
  15.     }
  16. }
复制代码
2.5 processUpdate
  1. /**
  2. * update 语句处理
  3. */
  4. @Override
  5. protected void processUpdate(Update update, int index, String sql, Object obj) {
  6.     final Expression sqlSegment = getUpdateOrDeleteExpression(update.getTable(), update.getWhere(), (String) obj);
  7.     if (null != sqlSegment) {
  8.         update.setWhere(sqlSegment);
  9.     }
  10. }
复制代码
2.6 processDelete
  1. /**
  2. * delete 语句处理
  3. */
  4. @Override
  5. protected void processDelete(Delete delete, int index, String sql, Object obj) {
  6.     final Expression sqlSegment = getUpdateOrDeleteExpression(delete.getTable(), delete.getWhere(), (String) obj);
  7.     if (null != sqlSegment) {
  8.         delete.setWhere(sqlSegment);
  9.     }
  10. }
复制代码
2.7 getUpdateOrDeleteExpression

针对更新和删除的SQL,差别于查询,当更新后的值是子查询或更新删除条件的值是一个子查询的时候,不会为这个子查询中的表追加条件,仅把针对整个update或delete语句的条件本身和要追加的数据权限过滤条件进行AND和OR拼接,因此会直接把表名和WHERE条件调用父类的andExpression(table, where, whereSegment)进行拼接,方法的返回值即为拼接后的结果,直接返回。
  1. protected Expression getUpdateOrDeleteExpression(final Table table, final Expression where, final String whereSegment) {
  2.     if (dataPermissionHandler == null) {
  3.         return null;
  4.     }
  5.     if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
  6.         return andExpression(table, where, whereSegment);
  7.     } else {
  8.         // 兼容旧版的数据权限处理
  9.         return dataPermissionHandler.getSqlSegment(where, whereSegment);
  10.     }
  11. }
复制代码
2.8 buildTableExpression

传入表名,返回表要追加的数据权限过滤条件,具体哪个表必要怎样的数据权限条件,会通过回调dataPermissionHandler.getSqlSegment()让DataPermissionHandler的实现类根据具体业务来确定
  1. @Override
  2. public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
  3.     if (dataPermissionHandler == null) {
  4.         return null;
  5.     }
  6.     // 只有新版数据权限处理器才会执行到这里
  7.     final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
  8.     return handler.getSqlSegment(table, where, whereSegment);
  9. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

老婆出轨

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表