Mybatis自定义拦截器实现权限功能
作者:喔喔咿哈哈
本文主要介绍了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 权限内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!