mybatis 自定义数据权限拦截器和springboot-starter-pageHelper分页拦截器执行先后顺序的问题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

需求:
自定义mybatis拦截器,要求可以通过注解的形式灵活配置
踩坑:
自定义拦截器和springboot-starter-pageHelper中的分页拦截器执行顺序问题。正常应该先执行自定义拦截器MapperInterceptor,后执行PageInterceptor。

一、自定义mybatis权限拦截器相关组件

1.自定义注解

@ClassMapperPermission
写在Mapper接口类上,表示此类的所有查询方法都需要数据拦截
@MethodMapperPermission
写在Mapper接口方法上,表示此查询方法需要查询数据拦截
@MapperPermissionIgnore
写在Mapper接口方法上,表示此方法忽略数据权限查询
具体代码如下:

import java.lang.annotation.*;

/**
 * 描述:
 *
 * @author ppliu-life
 * created in 2022/7/18 9:03
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ClassMapperPermission {
    /**
     * 需要维护sql片段,取值于sql片段里面的key
     */
    String value();

    /**
     * 需要排除的查询方法
     */
    String[] exclude() default {
            "selectByPrimaryKey",
            "selectByExample",
            "selectOneByExample",
            "selectByRowBounds",
            "selectOne",
            "select",
            "selectAll"
    };
}
``

# 二、使用步骤
## 1.引入库
代码如下(示例):

```c
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context
import java.lang.annotation.*;

/**
 * 描述:
 *
 * @author ppliu-life
 * created in 2022/7/18 9:04
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MapperPermissionIgnore {
}
import java.lang.annotation.*;

/**
 * 描述:
 *
 * @author ppliu-life
 * created in 2022/7/18 9:02
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodMapperPermission {
    /**
     * 需要维护sql片段,取值于sql片段里面的key
     */
    String value();
}

2.自定义拦截器MapperInterceptor

import com.szx.core.annotation.ClassMapperPermission;
import com.szx.core.annotation.MapperPermissionIgnore;
import com.szx.core.annotation.MethodMapperPermission;
import com.szx.core.jwt.bean.UserLocalContext;
import lombok.extern.slf4j.Slf4j;
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.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;

/**
 * 描述: sql拦截器.
 *
 * @author ppliu-life
 * created in 2022/7/18 9:37
 */
@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 MapperInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        //验证是否需要拦截
        if (!needToBeIntercepted(statement.getId())) {
            return invocation.proceed();
        }
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        //获取原始sql
        String originalSql = boundSql.getSql();
        //获取sql片段
        String sqlFragment = getSqlFragment(statement.getId());
        //专注编辑sql
        String newSql = splicing(originalSql, sqlFragment);
        //替换原有sql
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", newSql);
        args[0] = newStatement;
        try {
            args[5] = boundSql;
        } catch (Exception e) {
            //执行分页查询,没有args[5]
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 判断是否需要被拦截
     */
    private boolean needToBeIntercepted(String methodFullPath) throws ClassNotFoundException {
        String classFullPath = methodFullPath.substring(0, methodFullPath.lastIndexOf("."));
        String methodSimpleName = methodFullPath.substring(methodFullPath.lastIndexOf(".") + 1);
        if ("insert!selectKey".equals(methodSimpleName)) {
            return false;
        }
        ClassMapperPermission classMapperPermission = Class.forName(classFullPath).getAnnotation(ClassMapperPermission.class);
        if (classMapperPermission == null) {
            for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
                if (method.getName().equals(methodSimpleName)) {
                    if (method.getAnnotation(MethodMapperPermission.class) == null || method.getAnnotation(MapperPermissionIgnore.class) != null) {
                        return false;
                    }
                }
            }
            return true;
        }
        String[] excludeList = classMapperPermission.exclude();
        for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
            if (method.getName().equals(methodSimpleName) && method.getAnnotation(MapperPermissionIgnore.class) != null) {
                return false;
            }
            if (method.getName().equals(methodSimpleName) && Arrays.stream(excludeList).anyMatch(s -> s.equals(method.getName()))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取注解里面的sql片段
     */
    private String getSqlFragment(String methodFullPath) throws ClassNotFoundException {
        String classFullPath = methodFullPath.substring(0, methodFullPath.lastIndexOf("."));
        String methodSimpleName = methodFullPath.substring(methodFullPath.lastIndexOf(".") + 1);

        ClassMapperPermission classMapperPermission = Class.forName(classFullPath).getAnnotation(ClassMapperPermission.class);
        String key = null;
        if (classMapperPermission != null) {
            key = classMapperPermission.value();
        }
        for (Method method : Class.forName(classFullPath).getDeclaredMethods()) {
            if (method.getName().equals(methodSimpleName) && method.getAnnotation(MethodMapperPermission.class) != null) {
                MethodMapperPermission methodMapperPermission = method.getAnnotation(MethodMapperPermission.class);
                key = methodMapperPermission.value();
                break;
            }
        }
        if (key == null) {
            throw new RuntimeException("sql 片段丢失");
        }
        return key;
    }

    /**
     * sql拼接.
     *
     * @param originalSql 原始sql.
     * @param sqlFragment 权限sql片段.
     * @return 新的sql.
     */
    private String splicing(String originalSql, String sqlFragment) {
        return sqlFragment.replaceAll("#userId#", UserLocalContext.getUserSubject().getUserId() + "")
                .replaceAll("originalSql", originalSql);
    }


    public static MappedStatement newMappedStatement(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());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    //    定义一个内部辅助类,作用是包装sq
    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;
        }

    }

3.注册到spring容器中

踩坑的地方来了:
在执行拦截器的是先注册的后执行,后注册和先执行,类似于现实生活中剥洋葱的例子。
解决思路:一直等pageInterceptor进去注册进去以后我们再注册
代码如下:

import com.github.pagehelper.PageInterceptor;
import com.szx.core.interceptor.MapperInterceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * 描述:
 *
 * @author ppliu-life
 * created in 2022/7/18 14:47
 */
@SpringBootConfiguration
public class MapperPermissionConfiguration {
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    private void initInterceptor() {
        new Thread(() -> {
            boolean hasPageInterceptor = false;
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryList.get(0);
            Configuration configuration = sqlSessionFactory.getConfiguration();
            while (!hasPageInterceptor) {
                if (configuration.getInterceptors().stream().anyMatch(interceptor -> interceptor.getClass().equals(PageInterceptor.class))) {
                    hasPageInterceptor = true;
                }
            }
            MapperInterceptor mapperInterceptor = new MapperInterceptor();
            configuration.addInterceptor(mapperInterceptor);
        }).start();
    }
}

总结

以上就是mybatis分页插件的创建以及注册时候踩的坑,欢迎留言关注讨论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ppliu-life

可以为你提供帮助,我真的很开心

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值