mybatis拦截器实现数据权限

这篇具有很好参考价值的文章主要介绍了mybatis拦截器实现数据权限。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现。
比如员工打卡记录表,有id,name,dpt_id,company_id等字段,后两个表示部门ID和分公司ID。
查看员工打卡记录SQL为:select id,name,dpt_id,company_id from t_record

当一个总部账号可以查看全部数据此时,sql无需改变。因为他可以看到全部数据。
当一个部门管理员权限员工查看全部数据时,sql需要在末属添加 where dpt_id = #{dpt_id}

如果每个功能模块都需要手动写代码去拿到当前登陆用户的所属部门,然后手动添加where条件,就显得非常的繁琐。
因此,可以通过mybatis的拦截器拿到查询sql语句,再自动改写sql。

mybatis 拦截器

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

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

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

分页插件pagehelper就是一个典型的通过拦截器去改写SQL的。

mybatis拦截器实现数据权限

可以看到它通过注解 @Intercepts 和签名 @Signature 来实现,拦截Executor执行器,拦截所有的query查询类方法。
我们可以据此也实现自己的拦截器。

点击查看代码
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:{}, 数据权限替换后的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();
    }


    /**
     * 通过注解判断是否需要限制数据
     * @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;
        }
    }
}

顺便加了个注解 @SqlLimit,在mapper方法上加了此注解才进行数据权限过滤。
同时注解有两个属性,

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SqlLimit {
    /**
     * sql表别名
     * @return
     */
    String alis() default "";

    /**
     * 通过此列名进行限制
     * @return
     */
    String columnName() default "";
}

columnName表示通过此列名进行限制,一般来说一个系统,各表当中的此列是统一的,可以忽略。

alis用于标注sql表别名,如 针对sql select * from tablea as a left join tableb as b on a.id = b.id 进行改写,如果不知道表别名,会直接在后面拼接 where dpt_id = #{dptId},
那此SQL就会错误的,通过别名 @SqlLimit(alis = "a") 就可以知道需要拼接的是 where a.dpt_id = #{dptId}

执行结果

原SQL:select * from person, 数据权限替换后的SQL:select * from person where dpt_id = 234
原SQL:select * from person where id > 1, 数据权限替换后的SQL:select * from person where dpt_id = 234 and id > 1

但是在使用PageHelper进行分页的时候还是有问题。

mybatis拦截器实现数据权限

可以看到先执行了_COUNT方法也就是PageHelper,再执行了自定义的拦截器。

在我们的业务方法中注入SqlSessionFactory

@Autowired
@Lazy
private List<SqlSessionFactory> sqlSessionFactoryList;

mybatis拦截器实现数据权限

PageInterceptor为1,自定义拦截器为0,跟order相反,PageInterceptor优先级更高,所以越先执行。


mybatis拦截器优先级


@Order


通过@Order控制PageInterceptor和MySqlInterceptor可行吗?

mybatis拦截器实现数据权限

将MySqlInterceptor的加载优先级调到最高,但测试证明依然不行。

定义3个类

@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的优先级从低到高。
顺序预期的执行顺序应该是相反的:

00002 init
00001 init
00000 init

但事实上执行的顺序是

00000 init
00001 init
00002 init

@Order 不控制实例化顺序,只控制执行顺序。
@Order 只跟特定一些注解生效 如:@Compent @Service @Aspect … 不生效的如: @WebFilter

所以这里达不到预期效果。

@Priority 类似,同样不行。


@DependsOn


使用此注解将当前类将在依赖类实例化之后再执行实例化。

在MySqlInterceptor上标记@DependsOn("queryInterceptor")

mybatis拦截器实现数据权限

启动报错,
这个时候queryInterceptor还没有实例化对象。


@PostConstruct


@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
在同一个类里,执行顺序为顺序如下:Constructor > @Autowired > @PostConstruct。

但它也不能保证不同类的执行顺序。

PageHelper的springboot start也是通过这个来初始化拦截器的。

mybatis拦截器实现数据权限


ApplicationRunner


在当前springboot容器加载完成后执行,那么这个时候pagehelper的拦截器已经加入,在这个时候加入自定义拦截器,就能达到我们想要的效果。

仿照PageHelper来写

@Component
public class InterceptRunner implements ApplicationRunner {

    @Autowired
    private List<SqlSessionFactory> 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);
        }
    }
}

再执行,可以看到自定义拦截器在拦截器链当中下标变为了1(优先级与order刚好相反)

mybatis拦截器实现数据权限

后台打印结果,达到了预期效果。

mybatis拦截器实现数据权限文章来源地址https://www.toymoban.com/news/detail-477092.html

到了这里,关于mybatis拦截器实现数据权限的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

    Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

    目录 1. 用户登录权限校验 1.1 最初用户登录权限效验 1.2 Spring AOP 用户统⼀登录验证 1.3 Spring 拦截器 (1)创建自定义拦截器 (2)将自定义拦截器添加到系统配置中,并设置拦截的规则 1.4 练习:登录拦截器 (1)实现 UserController 实体类 (2)返回的登录页面:login.html (3)实

    2024年02月12日
    浏览(29)
  • 利用Mybatis拦截器实现自定义的ID增长器

    利用Mybatis拦截器实现自定义的ID增长器

    原生的Mybatis框架是没有ID自增器,但例如国产的Mybatis Plus却是支持,不过,Mybatis Plus却是缺少了自定属性的填充;例如:我们需要自定义填充一些属性,updateDate、createDate等,这时Mybatis Plus自带的ID自增器就无法满足需求;这种时候我们就需要自定义的ID增加器,可以自定义

    2024年02月19日
    浏览(14)
  • 关于MyBatis拦截器失效问题的解决(多数据源、分页插件)

    最近做了一个备份被delete语句删除的数据的小插件,是用MyBatis的拦截器去做的。 但是发现在一个项目中会失效,没有去备份删除的数据,查看日志发现请求并没有进入到拦截器中,换句话说就是拦截器失效了。 百度了一下(哎,百度)发现说的最多的就是分页插件导致的,

    2024年02月14日
    浏览(13)
  • 掌握Spring MVC拦截器整合技巧,实现灵活的请求处理与权限控制!

    掌握Spring MVC拦截器整合技巧,实现灵活的请求处理与权限控制!

    (1)浏览器发送一个请求会先到Tomcat的web服务器。 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源。 (3)如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问。 (4)如果是动态资源,就需要交给项目的后台代码进行处理。 (5)在找到具体的方法之前

    2024年01月22日
    浏览(13)
  • Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)

    Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)

    ps:最近在参与3100保卫战,战况很激烈,刚刚打完仗,来更新一下之前写了一半的博客。 该篇针对日常写查询的时候,那些动态条件sql 做个简单的封装,自动生成(抛砖引玉,搞个小玩具,不喜勿喷)。 来看看我们平时写那些查询,基本上都要写的一些动态sql:   一个字段

    2024年02月12日
    浏览(14)
  • MyBatis 拦截器介绍

    MyBatis 提供了一种插件 (plugin) 的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截 MyBatis 中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor

    2024年02月15日
    浏览(13)
  • SpingMVC拦截器-用户登录权限控制分析

    SpingMVC拦截器-用户登录权限控制分析

    1.1 这个后台工程,没有进行相关操作也能够进行登录: 4.1 5.1  选择test那个文件: 17.1.var可以获得session对象: 17.2 获取谁啊!获取user,返回的是user对象  17.3 强转之后做判断:  17.4 如果判断没有登录,这里跳转,这里我要设置重定向命令:   17.5 重定向 17.6 内部requestCon

    2024年02月11日
    浏览(7)
  • 自定义MyBatis拦截器更改表名

    by emanjusaka from ​ https://www.emanjusaka.top/2023/10/mybatis-interceptor-update-tableName 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下原文地址。 自定义MyBatis拦截器可以在方法执行前后插入自己的逻辑,这非常有利于扩展和定制 MyBatis 的功能。本篇文章实现自定义一个拦截器去改

    2024年02月08日
    浏览(12)
  • 7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

    7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

    在【 7.1 】管理员图书录入和修改API,当时预告过:并没有写【校验是否是管理员】的逻辑,因为是通用逻辑,会单写一篇来细讲,那么今天就来安排! 角色权限校验 ,是保证接口安全必备的能力:有权限才可以操作!所以,一般对于这种通用逻辑,推荐不与主业务逻辑耦合

    2024年02月16日
    浏览(6)
  • Mybatis拦截器注解@Intercepts与@Signature注解属性说明

    可能有些新手使用mybatis拦截器的时候可能没太懂@Signature注解中type,method,args的用法 首先mybatis拦截器可以拦截如下4中类型 Executor sql的内部执行器 ParameterHandler 拦截参数的处理 StatementHandler 拦截sql的构建 ResultSetHandler 拦截结果的处理 type:就是指定拦截器类型(ParameterHandl

    2024年02月05日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包