1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 4537字,需瀏覽 10分鐘

         ·

        2023-06-17 15:29

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

        你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!

        編輯:業(yè)余草

        來源:juejin.cn/post/7242596585330343992

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

        自律才 能自由

        前端的菜單和按鈕權(quán)限都可以通過配置來實(shí)現(xiàn),但很多時(shí)候,后臺(tái)查詢數(shù)據(jù)庫數(shù)據(jù)的權(quán)限需要通過手動(dòng)添加SQL來實(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 無需改變。因?yàn)樗梢钥吹饺繑?shù)據(jù)。

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

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

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

        mybatis 攔截器

        MyBatis 允許你在映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(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é)可以通過查看每個(gè)方法的簽名來發(fā)現(xiàn),或者直接查看 MyBatis 發(fā)行包中的源代碼。如果你想做的不僅僅是監(jiān)控方法的調(diào)用,那么你最好相當(dāng)了解要重寫的方法的行為。因?yàn)樵谠噲D修改或重寫已有方法的行為時(shí),很可能會(huì)破壞 MyBatis 的核心模塊。這些都是更底層的類和方法,所以使用插件的時(shí)候要特別當(dāng)心。

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

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

        e3011810ded61047c29456bf25fe494a.webp

        可以看到它通過注解 @Intercepts 和簽名 @Signature 來實(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();
        ????}

        ????/**
        ?????*?通過注解判斷是否需要限制數(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)限過濾。
        同時(shí)注解有兩個(gè)屬性,

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

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

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

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

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

        alis 用于標(biāo)注 sql 表別名,如 針對 sql select * from tablea as a left join tableb as b on a.id = b.id 進(jìn)行改寫,如果不知道表別名,會(huì)直接在后面拼接 where dpt_id = #{dptId},
        那此 SQL 就會(huì)錯(cuò)誤的,通過別名 @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)行分頁的時(shí)候還是有問題。

        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

        通過 @Order 控制 PageInterceptor 和 MySqlInterceptor 可行嗎?

        a6728fe31e810932fe8ba730559cbb49.webp

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

        定義 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 還沒有實(shí)例化對象。

        @PostConstruct

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

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

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

        84d5f28afe57dca05b6ebcb0c4b870ee.webp

        ApplicationRunner

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

        仿照 PageHelper 來寫。

              
              @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)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            免费av网站 | 免费区欧美一级毛片私人教师 | 私人玩物麻酥酥 | 另类图片另类小说 | 尤物网站在线观看 | 囯产精品一区二区三区线一牛影视1 | 免费看黄 片 | 操人在线 | 女人被到嗷嗷叫视频 | 欧美一卡二卡在线观看 |