MybatisPlus实现数据权限隔离

引言

Mybatis Plus对Mybatis做了无侵入的增强,非常的好用,今天就给大家介绍它的其中一个实用功能:数据权限插件。

数据权限插件的应用场景和多租户的动态拦截拼接SQL一样。建议点赞+收藏+关注,方便以后复习查阅。

依赖

首先导入Mybatis Plus的maven依赖,我使用的是3.5.3.2版本。

<properties>
    <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
</properties>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

数据权限拦截器

写一个自定义的权限注解,该注解用来标注被拦截方法,注解上可以配置数据权限的表别名和表字段,它们会在拼接sql的时候用到。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDataScope {
    /**
     * 表别名设置
     */
    String alias() default "";
    
    /**
     * 数据权限表字段名
     */
    String dataId() default "";
}

接下来就是写最核心的拦截器的处理逻辑了。创建一个接口实现类,实现Mybatis Plus的DataPermissionHandler接口。DataPermissionHandler的接口方法getSqlSegment有两个参数。

  • Expression where。where参数是mapper接口在xml中定义的sql的where条件表达式,在拦截处理器中我们可以给where条件表达式添加一些 and 或 or 的条件。
  • String mappedStatementId。mappedStatementId参数是mapper接口方法的全限定名,通过它我们可以得到mapper接口的Class类名以及接口方法名。

DataPermissionHandler的接口方法getSqlSegment会返回一个Expression类型的结果,即通过拦截器方法我们将原始的where条件表达式做了修改之后返回给Mybatis Plus并在代码运行时生效。

在拦截器方法中还使用到了一开始我们自定义的MyDataScope注解,没有被MyDataScope注解标注过的mapper方法我们直接返回原始的where条件表达式即可。

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.itguoguo.annotation.MyDataScope;
import com.itguoguo.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;

import java.lang.reflect.Method;
import java.util.Objects;

import static com.itguoguo.utils.LoginUserUtils.getLoginUser;

@Slf4j
public class MyDataScopeHandler implements DataPermissionHandler {
    /**
     * 获取数据权限 SQL 片段表达式
     * @param where             待执行 SQL Where 条件表达式
     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
     * @return 数据权限 SQL 片段表达式
     */
    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        try {
            String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
            String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
            Method[] methods = Class.forName(className).getMethods();
            for (Method m : methods) {
                if (StrUtil.isBlank(m.getName()) || !m.getName().equals(methodName)) {
                    continue;
                }
                MyDataScope annotation = m.getAnnotation(MyDataScope.class);
                if (Objects.isNull(annotation)) {
                    return where;
                }
                String sqlSegment = getSqlSegment(annotation);
                return StrUtil.isBlank(sqlSegment) ? where : getExpression(where, sqlSegment);
            }
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 拼接需要在业务 SQL 中额外追加的数据权限 SQL
     * @param annotation
     * @return 数据权限 SQL
     */
    private String getSqlSegment(MyDataScope annotation) {
        LoginUser loginUser = getLoginUser();
        String userType = loginUser.getSysUser().getUserType();
        Long userId = loginUser.getSysUser().getUserId();
        String sqlSegment = "";
        if (StrUtil.isBlank(userType)) {
            return sqlSegment;
        }
        if ("0".equals(userType)) {
            return sqlSegment;
        } else {
            sqlSegment = StrUtil.format(" {}.{} IN (SELECT project_id FROM sys_user where user_id = '{}') ",
                    annotation.alias(), annotation.dataId(), userId);
        }
        return sqlSegment;
    }

    /**
     * 将数据权限 SQL 语句追加到数据权限 SQL 片段表达式里
     * @param where         待执行 SQL Where 条件表达式
     * @param sqlSegment    数据权限 SQL 片段
     * @return
     */
    private Expression getExpression(Expression where, String sqlSegment) {
        try {
            Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sqlSegment);
            return (null != where) ? new AndExpression(where, sqlSegmentExpression) : sqlSegmentExpression;
        } catch (JSQLParserException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }
}

拦截器配置

配置Mybatis Plus拦截器,数据权限handler作为参数传给拦截器构造方法。

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataScopeHandler()));
        return interceptor;
    }
}

使用

使用时,在mapper接口的方法上标注MyDataScope注解,给注解标上表别名和表字段。

public interface MyMapper {
    @MyDataScope(alias = "a", dataId = "id")
    List findList();
}

建议点赞+收藏+关注,方便以后复习查阅。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
MyBatis-Plus本身并不直接提供数据脱敏的功能,但可以通过自定义SQL语句和插件来实现数据脱敏。 一种常见的方法是使用数据库的内置函数或自定义函数来对敏感字段进行脱敏处理。你可以在SQL语句中使用这些函数,例如使用MySQL的`SUBSTRING`或`REPLACE`函数来截取或替换字段的部分内容。 另一种方法是通过MyBatis-Plus的拦截器机制来实现数据脱敏。你可以创建一个自定义的拦截器,在查询数据库之前或之后对结果进行处理。在拦截器中,你可以访问到返回的结果对象,然后对敏感字段进行处理,例如将手机号码中的中间几位替换为星号。 以下是一个简单的示例,展示了如何通过自定义拦截器来实现数据脱敏: ```java @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class DataDesensitizationInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object result = invocation.proceed(); // 对返回结果进行处理,例如将手机号码进行脱敏 if (result instanceof List) { List<?> resultList = (List<?>) result; for (Object obj : resultList) { if (obj instanceof User) { User user = (User) obj; user.setPhone(desensitizePhoneNumber(user.getPhone())); } } } return result; } private String desensitizePhoneNumber(String phoneNumber) { // 实现自己的脱敏逻辑,例如将中间四位替换为星号 return phoneNumber.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } // 省略其他方法实现... } ``` 在以上示例中,我们创建了一个拦截器 `DataDesensitizationInterceptor`,并在 `intercept` 方法中对查询结果进行了脱敏处理。你可以根据自己的需求以及具体的数据脱敏规则进行定制化开发。 最后,记得在MyBatis的配置文件中配置该拦截器: ```xml <configuration> <!-- 其他配置项 --> <plugins> <plugin interceptor="com.example.DataDesensitizationInterceptor"/> </plugins> </configuration> ``` 这样,当执行查询操作时,拦截器将会被触发,从而实现数据脱敏的效果。当然,你可以根据具体需求和场景进行更复杂的数据脱敏操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

IT果果日记

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值