Mybatis自定义拦截器实现权限功能

 更新时间:2024年12月12日 10:20:33   作者:喔喔咿哈哈  
本文主要介绍了Mybatis自定义拦截器实现权限功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1、Mybatis 拦截器介绍

1.1 Mybatis 执行流程

  • 首先读取配置文件,然后加载映射文件,由SqlSessionFactory工厂对象去创建核心对象SqlSession,SqlSession对象会通过Executor执行器对象执行sql。然后Executor执行器对象会调用StatementHandler对象去真正的访问数据库执行sql语句。
  • 在执行sql语句前MapperStatement会先对映射信息进行封装,然后StatementHandler调用ParameterHandler去设置编译参数【#{},${}】,编译在StatementHandler中进行。然后StatementHandler调用JBDC原生API进行处理,获取执行结果,这个执行结果交给ResultSetHandler 来进行结果集封装,然后将结果返回给StatementHandler。
  • 注意: 这里MapperStatement是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。TypeHandler进行数据库类型和JavaBean类型映射处理。

1.2 Mybatis中可以被拦截的类型

  • Executor:拦截执行器的方法。
  • ParameterHandler:拦截参数的处理。
  • ResultHandler:拦截结果集的处理。
  • StatementHandler:拦截Sql语法构建的处理。

1.3 使用规则

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * 定义拦截点
     * 只有符合拦截点的条件才会进入到拦截器
     */
    Signature[] value();
}

Signature来指定咱们需要拦截那个类对象的哪个方法

- type:上述四种类型中的一种;
- method:对应接口中的哪类方法(因为可能存在重载方法);
- args:对应哪一个方法的入参;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
   */
  Class<?> type();

  /**
   * 在定义拦截类的基础之上,在定义拦截的方法
   */
  String method();

  /**
   * 在定义拦截方法的基础之上在定义拦截的方法对应的参数,
   * JAVA里面方法可能重载,故注意参数的类型和顺序
   */
  Class<?>[] args();
}

1.4 拦截器重写的方法

public interface Interceptor {
    //起拦截作用,在此定义一些功能
    Object intercept(Invocation var1) throws Throwable;

    //这个方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理
    Object plugin(Object var1);

    //拦截器需要一些变量对象,而且这个对象是支持可配置的。
    void setProperties(Properties var1);
}

2、实战部分:拦截实现

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DepartAuth {
    /**
     * 添加查询条件的字段名
     * @return
     */
    String field();

    EnumDepartAuthType authType() default EnumDepartAuthType.DEPART_ID;
    
}

在所在接口上添加注解

@DepartAuth(field = "xxx", authType = )

切面(AuthAspect)

@Slf4j
@Aspect
@Component
public class DepartAuthAspect {

    @Autowired
    private DepartAuthHandler departAuthHandler;

    @Pointcut("@annotation(org.jeecg.common.auth.depart.annotation.DepartAuth)")
    public void departAuthPoint() {

    }

    @Before("departAuthPoint()")
    public void before(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Annotation[] annotations = methodSignature.getMethod().getAnnotations();

        for (Annotation annotation : annotations) {
            if (annotation instanceof DepartAuth) {
                String field = ((DepartAuth) annotation).field();
                departAuthHandler.beforeHandler((DepartAuth) annotation);
            }
        }

    }

    @After("departAuthPoint()")
    public void after(JoinPoint pjp) {
        departAuthHandler.afterHandler();
    }
}

DepartAuthHandler

将信息存储到 TreadLocalhost 中,方便后续修改sql的时候可以读取到需要修改的字段

@Slf4j
@Component
public class DepartAuthHandler {

    public static ThreadLocal<DepartAuth> DEPART_AUTH_CACHE = new ThreadLocal<>();
    public static ThreadLocal<Integer> DEPART_AUTH_COUNT = new ThreadLocal<>();

    public void beforeHandler(DepartAuth departAuth) {
        String field = departAuth.field();
        if(StringUtils.isNotBlank(field)) {
            DEPART_AUTH_CACHE.set(departAuth);
            DEPART_AUTH_COUNT.remove();
        }
        PriorityQueue queue = new PriorityQueue<>();
        queue.peek();
    }

    public void afterHandler() {
        DEPART_AUTH_CACHE.remove();
        DEPART_AUTH_COUNT.remove();
    }
}

拦截器部分

  • 获取StatementHandler:通过 invocation.getTarget() 获取当前被拦截的 StatementHandler 对象。由于 MyBatis 使用了代理模式,因此这里得到的是一个代理对象。接着,通过反射获取其实际的代理对象 ,即最终执行SQL的 StatementHandler 实例。
  • 获取MappedStatement:通过反射从 StatementHandler 中获取 mappedStatement 对象。这个对象包含了关于即将执行的SQL语句的所有信息,包括SQL类型、参数类型等。
  • 判断SQL类型:通过mappedStatement.getSqlCommandType()获取SQL命令类型。如果类型是SELECT,说明当前是一个查询操作,需要进行权限检查和处理。
  • 获取并解析原始SQL:通过delegate.getBoundSql()获取BoundSql对象,它包含了实际执行的SQL语句和相关的参数信息。然后使用CCJSqlParserUtil.parse()解析这个SQL语句,得到一个抽象语法树(AST)。
  • 修改SQL:从AST中提取出PlainSelect对象(即SELECT语句的主体)。然后调用自定义的buildWhereClause方法,根据departAuth中的权限信息构建一个权限检查条件,并将其注入到原始的SELECT语句中。这通常是通过在WHERE子句后追加额外的条件来实现的。
  • 更新BoundSql对象:将修改后的SQL语句重新设置回BoundSql对象中,以便MyBatis在执行时能够使用修改后的SQL。
  • 继续执行后续流程:在完成SQL修改后,调用invocation.proceed()继续执行MyBatis的后续处理流程,包括实际的SQL执行、结果集处理等。
@Data
@Slf4j
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DepartAuthMapperInterceptor implements Interceptor {

    private Properties properties;




    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        DepartAuth departAuth = DepartAuthHandler.DEPART_AUTH_CACHE.get();
        Integer count = DepartAuthHandler.DEPART_AUTH_COUNT.get();
        if(departAuth != null && count == null) {
            // 说明当前线程已经执行了过滤条件,避免递归调用
            DepartAuthHandler.DEPART_AUTH_COUNT.set(1);
            RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
            //获取StatementHandler构造器
            StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
            // 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
            MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
            SqlCommandType commandType = mappedStatement.getSqlCommandType();
            // 处理select对象
            if (SqlCommandType.SELECT.equals(commandType)) {
                // 获取原始sql
                BoundSql boundSql = delegate.getBoundSql();
                Statement statement = CCJSqlParserUtil.parse(boundSql.getSql());
                PlainSelect selectBody = (PlainSelect) ((Select) statement).getSelectBody();
                log.info("原始 sql:{}", boundSql.getSql());
                // 拼接新条件
                buildWhereClause(selectBody, getSql(departAuth));
                ReflectUtil.setFieldValue(boundSql, "sql", statement.toString());
            }
            return invocation.proceed();

        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    /**
     * 添加查询条件
     * @param select
     * @param dataFilter
     * @throws JSQLParserException
     */
    private void buildWhereClause(PlainSelect select, String dataFilter) throws JSQLParserException {
        if(StringUtils.isBlank(dataFilter)) {
            return;
        }
        if (select.getWhere() == null) {
            select.setWhere(CCJSqlParserUtil.parseCondExpression(dataFilter));
        } else {
            AndExpression and = new AndExpression(
                    CCJSqlParserUtil.parseCondExpression(dataFilter), select.getWhere());
            select.setWhere(and);
        }
    }

    private String getSql(DepartAuth departAuth) {
        //结合自己的业务,拼接相对应的sql语句
    }
}

到此这篇关于Mybatis自定义拦截器实现权限功能的文章就介绍到这了,更多相关Mybatis 权限内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Spring中@ControllerAdvice注解的用法解析

    Spring中@ControllerAdvice注解的用法解析

    这篇文章主要介绍了Spring中@ControllerAdvice注解的用法解析,顾名思义,@ControllerAdvice就是@Controller 的增强版,@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用,需要的朋友可以参考下
    2023-10-10
  • Java语言实现基数排序代码分享

    Java语言实现基数排序代码分享

    这篇文章主要介绍了Java语言实现基数排序代码分享,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • SpringBoot异步实现的8种方式

    SpringBoot异步实现的8种方式

    异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景多会使用到异步,本文主要介绍了SpringBoot异步实现的8种方式,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Java实现将CSV转为Excel的示例代码

    Java实现将CSV转为Excel的示例代码

    CSV(Comma Separated Values)文件是一种纯文本文件,包含用逗号分隔的数据,常用于将数据从一个应用程序导入或导出到另一个应用程序。本文将利用Java实现CSV转为Excel,感兴趣的可以了解一下
    2022-03-03
  • Spring框架学习之AOP详解

    Spring框架学习之AOP详解

    这篇文章主要介绍了Spring框架学习之AOP详解,文中有非常详细的代码示例,对正在学习Spring框架的小伙伴们有一定的帮助,需要的朋友可以参考下
    2021-05-05
  • Eclipse 导出可执行Java工程/可执行Jar文件(包含第三方Jar包)

    Eclipse 导出可执行Java工程/可执行Jar文件(包含第三方Jar包)

    这篇文章主要介绍了Eclipse 导出可执行Java工程/可执行Jar文件(包含第三方Jar包)的相关资料,需要的朋友可以参考下
    2016-11-11
  • SpringBoot2.x设置Session失效时间及失效跳转方式

    SpringBoot2.x设置Session失效时间及失效跳转方式

    这篇文章主要介绍了SpringBoot2.x设置Session失效时间及失效跳转方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Spring实现对象注入的三种方法详解

    Spring实现对象注入的三种方法详解

    这篇文章主要为大家学习介绍了Spring中实现对象注入的三种常用方法,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下
    2023-07-07
  • Java编码算法与哈希算法深入分析使用方法

    Java编码算法与哈希算法深入分析使用方法

    首先,我们一起来学习一下编码算法,举例说明,ASCII码就是我们常见的一种编码,字母a的编码是十六进制的0x61,字母b是0x62,以此类推。哈希算法,可被称为摘要算法。因此,哈希算法的加密是单向的,不可用密文解密得到明文
    2022-11-11
  • Java+Selenium实现控制浏览器的启动选项Options

    Java+Selenium实现控制浏览器的启动选项Options

    这篇文章主要为大家详细介绍了如何使用java代码利用selenium控制浏览器的启动选项Options的代码操作,文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-01-01

最新评论