<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)范圍權(quán)限

          共 4537字,需瀏覽 10分鐘

           ·

          2023-06-17 15:29

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          來(lái)源:juejin.cn/post/7242596585330343992

          推薦:https://www.xttblog.com/?p=5367

          自律才 能自由

          前端的菜單和按鈕權(quán)限都可以通過(guò)配置來(lái)實(shí)現(xiàn),但很多時(shí)候,后臺(tái)查詢數(shù)據(jù)庫(kù)數(shù)據(jù)的權(quán)限需要通過(guò)手動(dòng)添加SQL來(lái)實(shí)現(xiàn)。

          比如員工打卡記錄表,有 id、name、dpt_id、company_id 等字段,后兩個(gè)表示部門 ID 和分公司 ID。

          查看員工打卡記錄 SQL 為:select id,name,dpt_id,company_id from t_record

          當(dāng)一個(gè)總部賬號(hào)可以查看全部數(shù)據(jù)此時(shí),sql 無(wú)需改變。因?yàn)樗梢钥吹饺繑?shù)據(jù)。

          當(dāng)一個(gè)部門管理員權(quán)限員工查看全部數(shù)據(jù)時(shí),sql 需要在末屬添加 where dpt_id = #{dpt_id}

          如果每個(gè)功能模塊都需要手動(dòng)寫(xiě)代碼去拿到當(dāng)前登陸用戶的所屬部門,然后手動(dòng)添加where條件,就顯得非常的繁瑣。

          因此,可以通過(guò) mybatis 的攔截器拿到查詢 sql 語(yǔ)句,再自動(dòng)改寫(xiě) sql。

          mybatis 攔截器

          MyBatis 允許你在映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:

          • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
          • ParameterHandler (getParameterObject, setParameters)
          • ResultSetHandler (handleResultSets, handleOutputParameters)
          • StatementHandler (prepare, parameterize, batch, update, query)

          這些類中方法的細(xì)節(jié)可以通過(guò)查看每個(gè)方法的簽名來(lái)發(fā)現(xiàn),或者直接查看 MyBatis 發(fā)行包中的源代碼。如果你想做的不僅僅是監(jiān)控方法的調(diào)用,那么你最好相當(dāng)了解要重寫(xiě)的方法的行為。因?yàn)樵谠噲D修改或重寫(xiě)已有方法的行為時(shí),很可能會(huì)破壞 MyBatis 的核心模塊。這些都是更底層的類和方法,所以使用插件的時(shí)候要特別當(dāng)心。

          通過(guò) MyBatis 提供的強(qiáng)大機(jī)制,使用插件是非常簡(jiǎn)單的,只需實(shí)現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。

          分頁(yè)插件 pagehelper 就是一個(gè)典型的通過(guò)攔截器去改寫(xiě) SQL 的。

          e3011810ded61047c29456bf25fe494a.webp

          可以看到它通過(guò)注解 @Intercepts 和簽名 @Signature 來(lái)實(shí)現(xiàn),攔截 Executor 執(zhí)行器,攔截所有的 query 查詢類方法。

          我們可以據(jù)此也實(shí)現(xiàn)自己的攔截器。

                
                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}),
          })
          @Slf4j
          public?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:{},?數(shù)據(jù)權(quán)限替換后的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?0)?{
          ????????????sb.append("?where?")?.append(field).append("?=?").append(depId);
          ????????}?else?{
          ????????????sb.insert(index?+?5,?"?"?+?field?+"?=?"?+?depId?+?"?and?");
          ????????}
          ????????return?sb.toString();
          ????}

          ????private?MappedStatement?copyFromMappedStatement(MappedStatement?ms,?SqlSource?newSqlSource)?{
          ????????MappedStatement.Builder?builder?=?new?MappedStatement.Builder(ms.getConfiguration(),?ms.getId(),?newSqlSource,?ms.getSqlCommandType());
          ????????builder.resource(ms.getResource());
          ????????builder.fetchSize(ms.getFetchSize());
          ????????builder.statementType(ms.getStatementType());
          ????????builder.keyGenerator(ms.getKeyGenerator());
          ????????builder.timeout(ms.getTimeout());
          ????????builder.parameterMap(ms.getParameterMap());
          ????????builder.resultMaps(ms.getResultMaps());
          ????????builder.cache(ms.getCache());
          ????????builder.useCache(ms.isUseCache());
          ????????return?builder.build();
          ????}

          ????/**
          ?????*?通過(guò)注解判斷是否需要限制數(shù)據(jù)
          ?????*?@return
          ?????*/

          ????private?SqlLimit?isLimit(MappedStatement?mappedStatement)?{
          ????????SqlLimit?sqlLimit?=?null;
          ????????try?{
          ????????????String?id?=?mappedStatement.getId();
          ????????????String?className?=?id.substring(0,?id.lastIndexOf("."));
          ????????????String?methodName?=?id.substring(id.lastIndexOf(".")?+?1,?id.length());
          ????????????final?Class?cls?=?Class.forName(className);
          ????????????final?Method[]?method?=?cls.getMethods();
          ????????????for?(Method?me?:?method)?{
          ????????????????if?(me.getName().equals(methodName)?&&?me.isAnnotationPresent(SqlLimit.class))?{
          ????????????????????sqlLimit?=?me.getAnnotation(SqlLimit.class);
          ????????????????}
          ????????????}
          ????????}?catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????return?sqlLimit;
          ????}

          ????public?static?class?BoundSqlSqlSource?implements?SqlSource?{

          ????????private?final?BoundSql?boundSql;

          ????????public?BoundSqlSqlSource(BoundSql?boundSql)?{
          ????????????this.boundSql?=?boundSql;
          ????????}

          ????????@Override
          ????????public?BoundSql?getBoundSql(Object?parameterObject)?{
          ????????????return?boundSql;
          ????????}
          ????}
          }

          順便加了個(gè)注解 @SqlLimit,在 mapper 方法上加了此注解才進(jìn)行數(shù)據(jù)權(quán)限過(guò)濾。
          同時(shí)注解有兩個(gè)屬性,

                
                @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Inherited
          public?@interface?SqlLimit?{
          ????/**
          ?????*?sql表別名
          ?????*?@return
          ?????*/

          ????String?alis()?default?"";

          ????/**
          ?????*?通過(guò)此列名進(jìn)行限制
          ?????*?@return
          ?????*/

          ????String?columnName()?default?"";
          }

          columnName 表示通過(guò)此列名進(jìn)行限制,一般來(lái)說(shuō)一個(gè)系統(tǒng),各表當(dāng)中的此列是統(tǒng)一的,可以忽略。

          alis 用于標(biāo)注 sql 表別名,如 針對(duì) sql select * from tablea as a left join tableb as b on a.id = b.id 進(jìn)行改寫(xiě),如果不知道表別名,會(huì)直接在后面拼接 where dpt_id = #{dptId},
          那此 SQL 就會(huì)錯(cuò)誤的,通過(guò)別名 @SqlLimit(alis = "a") 就可以知道需要拼接的是 where a.dpt_id = #{dptId}

          執(zhí)行結(jié)果。

          原 SQL:select * from person, 數(shù)據(jù)權(quán)限替換后的SQL:select * from person where dpt_id = 234

          原 SQL:select * from person where id > 1, 數(shù)據(jù)權(quán)限替換后的 SQL:select * from person where dpt_id = 234 and id > 1。

          但是在使用 PageHelper 進(jìn)行分頁(yè)的時(shí)候還是有問(wèn)題。

          8769b2133b4e72195fd93dbac7448ea4.webp

          可以看到先執(zhí)行了 _COUNT 方法也就是 PageHelper,再執(zhí)行了自定義的攔截器。

          在我們的業(yè)務(wù)方法中注入 SqlSessionFactory。

                
                @Autowired
          @Lazy
          private?List?sqlSessionFactoryList;
          d1be42af71f6c523f1ee5fa7dbb98692.webp

          PageInterceptor 為 1,自定義攔截器為 0,跟 order 相反,PageInterceptor 優(yōu)先級(jí)更高,所以越先執(zhí)行。

          mybatis攔截器優(yōu)先級(jí)

          @Order

          通過(guò) @Order 控制 PageInterceptor 和 MySqlInterceptor 可行嗎?

          a6728fe31e810932fe8ba730559cbb49.webp

          將 MySqlInterceptor 的加載優(yōu)先級(jí)調(diào)到最高,但測(cè)試證明依然不行。

          定義 3 個(gè)類。

                
                @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 的優(yōu)先級(jí)從低到高。

          順序預(yù)期的執(zhí)行順序應(yīng)該是相反的:

                
                00002?init
          00001?init
          00000?init

          但事實(shí)上執(zhí)行的順序是

                
                00000?init
          00001?init
          00002?init

          @Order 不控制實(shí)例化順序,只控制執(zhí)行順序。@Order 只跟特定一些注解生效 如:@Compent、 @Service、@Aspect … 不生效的如:@WebFilter

          所以這里達(dá)不到預(yù)期效果。

          @Priority 類似,同樣不行。

          @DependsOn

          使用此注解將當(dāng)前類將在依賴類實(shí)例化之后再執(zhí)行實(shí)例化。

          在 MySqlInterceptor 上標(biāo)記@DependsOn("queryInterceptor")

          98eb22d93f1046a0238cde35a10e39d6.webp

          啟動(dòng)報(bào)錯(cuò),

          這個(gè)時(shí)候 queryInterceptor 還沒(méi)有實(shí)例化對(duì)象。

          @PostConstruct

          @PostConstruct 修飾的方法會(huì)在服務(wù)器加載 Servlet 的時(shí)候運(yùn)行,并且只會(huì)被服務(wù)器執(zhí)行一次。
          在同一個(gè)類里,執(zhí)行順序?yàn)轫樞蛉缦拢篊onstructor > @Autowired > @PostConstruct。

          但它也不能保證不同類的執(zhí)行順序。

          PageHelper 的 springboot start 也是通過(guò)這個(gè)來(lái)初始化攔截器的。

          84d5f28afe57dca05b6ebcb0c4b870ee.webp

          ApplicationRunner

          在當(dāng)前 springboot 容器加載完成后執(zhí)行,那么這個(gè)時(shí)候 pagehelper 的攔截器已經(jīng)加入,在這個(gè)時(shí)候加入自定義攔截器,就能達(dá)到我們想要的效果。

          仿照 PageHelper 來(lái)寫(xiě)。

                
                @Component
          public?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);
          ????????}
          ????}
          }

          再執(zhí)行,可以看到自定義攔截器在攔截器鏈當(dāng)中下標(biāo)變?yōu)榱?1(優(yōu)先級(jí)與 order 剛好相反)

          ae1118a29437f42730a6dbdde2466be9.webp

          后臺(tái)打印結(jié)果,達(dá)到了預(yù)期效果。

          1194c224c1b9b0c4dd2699444b6f2646.webp

          瀏覽 126
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  伊人99re | 麻豆成人久久 | 中文字幕在线观看第二页 | 91成人一区二区三区 | 性少妇暴力猛交69HD |