Mybatis拦截器如何实现数据权限过滤

2025-01-01 03:50

本文主要是介绍Mybatis拦截器如何实现数据权限过滤,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Mybatis拦截器如何实现数据权限过滤》本文介绍了MyBatis拦截器的使用,通过实现Interceptor接口对SQL进行处理,实现数据权限过滤功能,通过在本地线程变量中存储数据权限相关信息,并...

背景

现在的项目负责人去年年底离职,导致前期规划的数据过滤功能一直没有去实现。

现在项目马上进入试运行时期了,需要根据用户数据权限的配置对数据进行过滤处理。

如果一个个手动去Mapper.XML文件中修改SQL工作量太大了,后面我考虑通过Mybatis对查询的SQL进行处理。

基础知识

Mybatis 拦截器介绍

Interceptor接口源码解析

package org.apache.ibatis.plugin;  
  
import Java.util.Properties;  
  
public interface Interceptor {  
	
    Object intercept(Invocation invocation) throws Throwable;  

    default Object plugin(Object target) {  
        return Plugin.wrap(target, this);  
    }  
    
    default void setProperties(Properties properties) {  
    }  
}
  • intercept 方法
    这个方法是核心,当拦截到调用时会执行。Invocation 对象包含了被拦截方法的所有信息,包括方法本身、参数、目标对象等。在这个方编程法中,你可以做任何预处理或后处理逻辑,然后通过调用 invocation.proceed() 来继续执行原方法,或者直接返回自定义的结果。
  • plugin方法
    这个方法用于决定是否对某个对象应用拦截器。如果返回 target,则表示不进行拦截;如果返回一个新的对象,则表示将使用这个新对象替代原有的对象,通常是在这里返回一个代理对象。
  • setProperties 方法
    用于设置拦截器的属性,这些属性可以在 MyBatis 的配置文件中定义。

Signature 注解源码解析

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target({})  
public @interface Signature {  
	Class<?> type();

	String method();

	Class<?>[] args();
}
  • type:表示目标对象的类型,
  • method:表示要拦截的目标方法的名字。
  • args:表示目标方法的参数类型列表。不同的 @Signature 注解可能有不同的参数类型列表,这取决于具体的方法签名。

代码实战

实现一个类似与PageHelper的一个工具类,在本地线程变量中存储数据权限相关信息

public class DataAccessMethod {  
    private static final ThreadLocal<DataAccessType[]> ACCESS_LOCAL = new ThreadLocal<>();  
  
  
    public DataAccessMethod() {  
  
    }  
    public static void setLocalAccess(DataAccessType... accessType) {  
http://www.chinasem.cn        ACCESS_LOCAL.set(accessType);  
    }  
  
    public static DataAccessType[] getLocalAccess() {  
        return ACCESS_LOCAL.get();  
    }  
  
    public static void clearLocalAccess() {  
        ACCESS_LOCAL.remove();  
    }  
  
    public static void accessData(DataAccessType... accessType) {  
        setLocalAccess(accessType);  
    }  
}

实现 Interceptor接口对SQL进行增强处理

@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  
@Component  
public class DataAccessFilterIntpythonerceptor implements Interceptor {  
  
  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        if (skip()) {  
            return invocation.proceed();  
        }  
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];  
        Object parameter = invocation.getArgs()[1];  
        BoundSql boundSql = statement.getBoundSql(parameter);  
        String originalSql = boundSql.getSql();  
        Object parameterObject = boundSql.getParameterObject();  
        String sql = addTenantCondition(originalSql, "1", "222");  
        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();  
    }  
  
    /**  
     * 判断是否跳过  
     *  
     * @return 是否跳过  
     */  
    private boolean skip() {  
        DataAccessType[] localAccess = DataAccessMethod.getLocalAccess();  
        return localAccess == null;  
    }  
  
    private String addTenantCondition(String originalSql, String depId, String alias) {  
        String field = "id";  
        if (StringUtils.hasText(alias)) {  
            field = alias + "." + field;  
        }  
  
        StringBuilder sb = new StringBuilder(originalSql.toLowerCase());  
        int index = sb.indexOf("where");  
        sb = new StringBuilder(originalSql);  
        if (index < 0) {  
            sb.append(" where ").append(field).append(" = ").append(depId);  
        } else {  
            sb.insert(index + 5, " " + field + " = " + depId + " and ");  
        }  
        return sb.toString();  
    }  
  
    private MappedStatemejsnt 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();  
    }  
  
    public static class BoundSqlSqlSource implements SqlSource {  
        private final BoundSql boundSql;  
  
        public BoundSqlSqlSource(BoundSql boundSql) {  
            this.boundSql = boundSql;  
        }  
  
        @Override  
        puandroidblic BoundSql getBoundSql(Object parameterObject) {  
            return boundSql;  
        }  
    }  
  
  
}

总结

以上代码只是示例,在实际生产中还需要考虑多表查询、SQL注入等相关问题。

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Mybatis拦截器如何实现数据权限过滤的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1152882

相关文章

Spring Boot 中整合 MyBatis-Plus详细步骤(最新推荐)

《SpringBoot中整合MyBatis-Plus详细步骤(最新推荐)》本文详细介绍了如何在SpringBoot项目中整合MyBatis-Plus,包括整合步骤、基本CRUD操作、分页查询、批... 目录一、整合步骤1. 创建 Spring Boot 项目2. 配置项目依赖3. 配置数据源4. 创建实体类

Django中使用SMTP实现邮件发送功能

《Django中使用SMTP实现邮件发送功能》在Django中使用SMTP发送邮件是一个常见的需求,通常用于发送用户注册确认邮件、密码重置邮件等,下面我们来看看如何在Django中配置S... 目录1. 配置 Django 项目以使用 SMTP2. 创建 Django 应用3. 添加应用到项目设置4. 创建

java poi实现Excel多级表头导出方式(多级表头,复杂表头)

《javapoi实现Excel多级表头导出方式(多级表头,复杂表头)》文章介绍了使用javapoi库实现Excel多级表头导出的方法,通过主代码、合并单元格、设置表头单元格宽度、填充数据、web下载... 目录Java poi实现Excel多级表头导出(多级表头,复杂表头)上代码1.主代码2.合并单元格3.

Python读取TIF文件的两种方法实现

《Python读取TIF文件的两种方法实现》本文主要介绍了Python读取TIF文件的两种方法实现,包括使用tifffile库和Pillow库逐帧读取TIFF文件,具有一定的参考价值,感兴趣的可以了解... 目录方法 1:使用 tifffile 逐帧读取安装 tifffile:逐帧读取代码:方法 2:使用

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

Spring Boot实现多数据源连接和切换的解决方案

《SpringBoot实现多数据源连接和切换的解决方案》文章介绍了在SpringBoot中实现多数据源连接和切换的几种方案,并详细描述了一个使用AbstractRoutingDataSource的实... 目录前言一、多数据源配置与切换方案二、实现步骤总结前言在 Spring Boot 中实现多数据源连接

Redis KEYS查询大批量数据替代方案

《RedisKEYS查询大批量数据替代方案》在使用Redis时,KEYS命令虽然简单直接,但其全表扫描的特性在处理大规模数据时会导致性能问题,甚至可能阻塞Redis服务,本文将介绍SCAN命令、有序... 目录前言KEYS命令问题背景替代方案1.使用 SCAN 命令2. 使用有序集合(Sorted Set)

使用 Python 和 LabelMe 实现图片验证码的自动标注功能

《使用Python和LabelMe实现图片验证码的自动标注功能》文章介绍了如何使用Python和LabelMe自动标注图片验证码,主要步骤包括图像预处理、OCR识别和生成标注文件,通过结合Pa... 目录使用 python 和 LabelMe 实现图片验证码的自动标注环境准备必备工具安装依赖实现自动标注核心

使用Python实现生命之轮Wheel of life效果

《使用Python实现生命之轮Wheeloflife效果》生命之轮Wheeloflife这一概念最初由SuccessMotivation®Institute,Inc.的创始人PaulJ.Meyer... 最近看一个生命之轮的视频,让我们珍惜时间,因为一生是有限的。使用python创建生命倒计时图表,珍惜时间

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异