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();
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/547057.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网安小白成长之路】7.burp基本使用

&#x1f42e;博主syst1m 带你 acquire knowledge&#xff01; ✨博客首页——syst1m的博客&#x1f498; &#x1f51e; 《网安小白成长之路(我要变成大佬&#x1f60e;&#xff01;&#xff01;)》真实小白学习历程&#xff0c;手把手带你一起从入门到入狱&#x1f6ad; &…

计算机网络3——数据链路层1

文章目录 一、介绍1、基础2、内容 二、数据链路层的几个共同问题1、数据链路和帧2、三个基本问题1&#xff09;封装成帧2&#xff09;透明传输3&#xff09;差错检测 三、点对点协议 PPP1、PPP协议的特点1&#xff09;PPP 协议应满足的需求2&#xff09;PPP 协议的组成 2、PPP协…

vmware安装ubuntu-18.04系统

一、软件下载 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1fK2kygRdSux1Sr1sOKOtJQ 提取码&#xff1a;twsb 二、安装ubuntu系统 1、把ubuntu-18.04的压缩包下载下来&#xff0c;并且解压 2、打开vmware软件&#xff0c;点击文件-打开 3、选择我们刚刚解…

限制登录Linux服务器的几种方式

一.第一种方法 通过修改TCP Wrappers服务访问控制来实现限制登录Linux 1.这里以sshd服务为例&#xff0c;配置完成后&#xff0c;只允许配置允许的IP才能ssh连接本机服务器&#xff0c;其他IP拒绝判断某一个基于tcp协议的服务是否支持tcp_wrapper&#xff0c;要先判断它是否支…

【C语言】<动态内存管理>我的C语言终末章

&#xff1c;动态内存管理&#xff1e; 1. 为什么要有动态内存分配2. malloc和free2.1 malloc2.2 free 3. calloc和realloc3.1 calloc3.2 realloc 4.常见的动态内存错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释…

Linux下SPI设备驱动实验:实现SPI发送/接收数据的函数

一. 简介 前面文章介绍了SPI设备数据收发处理流程&#xff0c;后面几篇文章实现了SPI设备驱动框架&#xff0c;加入了字符设备驱动框架代码。文章如下&#xff1a; SPI 设备驱动编写流程&#xff1a;SPI 设备数据收发处理流程中涉及的结构体与函数-CSDN博客 SPI 设备驱动编写…

PgSQL之WITH Queries/Statement

PostgreSQL WITH 子句 在 PostgreSQL 中&#xff0c;WITH 子句提供了一种编写辅助语句的方法&#xff0c;以便在更大的查询中使用。 WITH 子句有助于将复杂的大型查询分解为更简单的表单&#xff0c;便于阅读。这些语句通常称为通用表表达式&#xff08;Common Table Express…

基于有序抖动块截断编码的水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 噪声测试 旋转测试 压缩测试 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................…

Spring(24) Json序列化的三种方式(Jackson、FastJSON、Gson)史上最全!

目录 一、Jackson 方案&#xff08;SpringBoot默认支持&#xff09;1.1 Jackson 库的特点1.2 Jackson 的核心模块1.3 Maven依赖1.4 代码示例1.5 LocalDateTime 格式化1.6 统一配置1.7 常用注解1.8 自定义序列化和反序列化1.9 Jackson 工具类 二、FastJSON 方案2.1 FastJSON 的特…

折叠面板组件(vue)

代码 <template><div class"collapse-info"><div class"collapse-title"><div class"title-left">{{ title }}</div><div click"changeHide"> <Button size"small" v-if"sho…

LeetCode-706. 设计哈希映射【设计 数组 哈希表 链表 哈希函数】

LeetCode-706. 设计哈希映射【设计 数组 哈希表 链表 哈希函数】 题目描述&#xff1a;解题思路一&#xff1a;超大数组解题思路二&#xff1a;拉链法解题思路三&#xff1a; 题目描述&#xff1a; 不使用任何内建的哈希表库设计一个哈希映射&#xff08;HashMap&#xff09;。…

SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普 在此之前&#xff0c;简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中&#xff0c;延时队列可以实现很多功能&#xff0c;此类业务中&#xff0c;一般上是非实时的&#xff0c;需要延迟处理的&a…

【讲解下常见的Web前端框架】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Linux-管道

目录 无名管道关闭未使用的管道文件描述符 管道对应的内存大小与shell命令进行通信&#xff08;popen&#xff09;命名管道FIFO创建FIFO文件打开FIFO文件 无名管道 管道是最早出现的进程间通信的手段。 管道的作用是在有亲缘关系的进程之间传递消息。所谓有亲缘关系&#xff…

【YOLOV5 入门】——Pyside6/PyQt5可视化UI界面后端逻辑

声明&#xff1a;笔记是做项目时根据B站博主视频学习时自己编写&#xff0c;请勿随意转载&#xff01; 一、环境安装 VScode/Pycharm终端进入虚拟环境后&#xff0c;输入下面代码安装pyside6&#xff0c;若用的Pycharm作为集成开发环境&#xff0c;也下载个pyqt5&#xff1a; …

移动Web学习07-适配单位vw/vh哔哩哔哩移动端vw单位适配案例

1.1、VW相对单位 前面我们已经学习了rem单位 &#xff0c;他是一个相对单位、相对于HTML表格字号大小 VW/VH也是一个相对单位&#xff0c;他是相对于视口的尺寸计算结果 VW&#xff1a;viewport width VH: viewport height <meta name"viewport" content"…

C语言之探秘:访问结构体空指针与结构体空指针的地址的区别(九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

华为HarmonyOS 4.2公测升级计划扩展至15款新机型

华为近日宣布&#xff0c;HarmonyOS 4.2操作系统的公测升级计划将扩展到包括华为P50系列在内的15款设备。这一更新旨在为用户提供更优化的系统性能和增强的功能。 参与此次公测的机型包括华为P50、华为P50 Pro及其典藏版、华为P50E、华为P50 Pocket及其艺术定制版、华为nova系…

学习STM32第十四天

软件SPI读写W25Q64 一、简介 对W25Q64模块进行读写操作时&#xff0c;输出引脚配置为推挽输出&#xff0c;输入引脚配置为浮空或上拉输入。时钟、主机输出和片选都是输出引脚&#xff0c;主机输入是输入引脚。SPI协议是通过命令和数据进行通信&#xff0c;在硬件中使用移位寄…

将自己的项目上传至Git

一、安装Git 官网:Git (git-scm.com) 二、注册gitee 官网:工作台 - Gitee.com 进入“我的”出现以下界面 三、创建仓库 点击加号&#xff0c;新建仓库 根据自己的需求取名&#xff0c;描述仓库&#xff0c;开源还是私有&#xff0c;点击创建即可&#xff0c;点击我的即可…