Mybatis-Plus之实现数据权限插件使用

需求为部门经理只能看自己部门的数据,管理员可以看全部人的数据,普通员工只能看自己的数据,本文并不能直接复制代码就能用,仅提供核心代码,请结合自己项目的实际情况参考使用。

官方文档:https://baomidou.com/plugins/data-permission/

要实现数据权限相关业务表需要有create_id和dept_id,即数据创建人用户id和创建人所在部门id,也可以采用只有create_id字段,也就是通过create_id查询该用户所在部门下的所有员工用户id。两种方案都可以,本文采用只有create_id字段的方案。

核心代码如下:

在项目中,使用的是 spring security + oauth2安全认证,角色来定义数据权限类型。所以这种方法你只能从token里拿用户相关数据。

一、编写自己业务的handle,实现MultiDataPermissionHandler

注意下面代码中存在关于用户token相关类,包括LoginUser、SysUser、SysRole、SecurityUtils等相关类本文将不提供源码,请自行处理。

DataScopePlusHandler.java
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.risk.control.common.annotation.DataScopePlus;
import com.risk.control.common.constant.DataScopeConstants;
import com.risk.control.common.core.domain.entity.SysRole;
import com.risk.control.common.core.domain.entity.SysUser;
import com.risk.control.common.core.domain.model.LoginUser;
import com.risk.control.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** 数据权限拼装逻辑处理**/
@Slf4j
public class DataScopePlusHandler implements MultiDataPermissionHandler {/*** 获取数据权限 SQL 片段。* <p>旧的 {@link MultiDataPermissionHandler#getSqlSegment(Expression, String)} 方法第一个参数包含所有的 where 条件信息,如果 return 了 null 会覆盖原有的 where 数据,</p>* <p>新版的 {@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆盖原有的 where 数据,如果 return 了 null 则表示不追加任何 where 条件</p>** @param table             所执行的数据库表信息,可以通过此参数获取表名和表别名* @param where             原有的 where 条件信息* @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法* @return JSqlParser 条件表达式,返回的条件表达式会拼接在原有的表达式后面(不会覆盖原有的表达式)*/@Overridepublic Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {try {Class<?> mapperClazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);/*** DataScope注解优先级:【类上 > 方法上】*/// 获取 DataScope注解DataScopePlus dataScopeAnnotationClazz = mapperClazz.getAnnotation(DataScopePlus.class);if (ObjectUtils.isNotEmpty(dataScopeAnnotationClazz) && dataScopeAnnotationClazz.enabled()) {return buildDataScopeByAnnotation(dataScopeAnnotationClazz);}// 获取自身类中的所有方法,不包括继承。与访问权限无关Method[] methods = mapperClazz.getDeclaredMethods();for (Method method : methods) {DataScopePlus dataScopeAnnotationMethod = method.getAnnotation(DataScopePlus.class);if (ObjectUtils.isEmpty(dataScopeAnnotationMethod) || !dataScopeAnnotationMethod.enabled()) {continue;}if (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName) || (method.getName() + "_count").equals(methodName)) {return buildDataScopeByAnnotation(dataScopeAnnotationMethod);}}} catch (ClassNotFoundException e) {e.printStackTrace();}return null;}/*** DataScope注解方式,拼装数据权限** @param controllerDataScope* @return*/private Expression buildDataScopeByAnnotation(DataScopePlus controllerDataScope) {// 获取当前的用户LoginUser loginUser = SecurityUtils.getLoginUser();if (com.risk.control.common.utils.StringUtils.isNotNull(loginUser)){SysUser currentUser = loginUser.getUser();// 如果是超级管理员,则不过滤数据if (com.risk.control.common.utils.StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()){List<SysRole> roleList = currentUser.getRoles();if(roleList.isEmpty()){return null;}//List<Long> dataScopeDeptIds = new ArrayList<>();List<Long> createIdList = new ArrayList<>();Long dataScopeCreateId = null;// 目前用户只能是一种角色SysRole role = roleList.get(0);String dataScope = role.getDataScope();if(DataScopeConstants.DATA_SCOPE_ALL.equals(dataScope)){return null;}// 处理所在本部门的数据if(DataScopeConstants.DATA_SCOPE_DEPT.equals(dataScope)){createIdList = loginUser.getCreateIdList();}// 仅查询自己创建的数据if(DataScopeConstants.DATA_SCOPE_SELF.equals(dataScope)){dataScopeCreateId = currentUser.getUserId();}Expression expression = dataScopeFilter(controllerDataScope.tableAlias(),controllerDataScope.oneselfScopeName(),controllerDataScope.oneselfScopeName(),createIdList,dataScopeCreateId);log.info("【DataScopeHandlerPlus】数据权限处理的sql语句:" + expression.toString());return dataScopeFilter(controllerDataScope.tableAlias(),controllerDataScope.oneselfScopeName(),controllerDataScope.oneselfScopeName(),createIdList,dataScopeCreateId);}}return null;}/*** 拼装数据权限** @param tableAlias        表别名* @param deptScopeName     部门限制范围的字段名称* @param oneselfScopeName  本人限制范围的字段名称* @param createIdList  该用户部门下创建人id集合* @param dataScopeCreateId 数据权限本人ID* @return*/private Expression dataScopeFilter(String tableAlias, String deptScopeName, String oneselfScopeName, List<Long> createIdList, Long dataScopeCreateId) {/*** 构造部门in表达式。*/InExpression deptIdInExpression = null;if (CollectionUtils.isNotEmpty(createIdList)) {deptIdInExpression = new InExpression();ExpressionList deptIds = new ExpressionList(createIdList.stream().map(LongValue::new).collect(Collectors.toList()));// 设置左边的字段表达式,右边设置值。deptIdInExpression.setLeftExpression(buildColumn(tableAlias, deptScopeName));//deptIdInExpression.setRightExpression(new Parenthesis(deptIds));deptIdInExpression.setRightItemsList(deptIds);}/*** 构造本人eq表达式*/EqualsTo oneselfEqualsTo = null;if (dataScopeCreateId != null) {oneselfEqualsTo = new EqualsTo();oneselfEqualsTo.withLeftExpression(buildColumn(tableAlias, oneselfScopeName));oneselfEqualsTo.setRightExpression(new LongValue(dataScopeCreateId));}if (deptIdInExpression != null && oneselfEqualsTo != null) {return new OrExpression(deptIdInExpression, oneselfEqualsTo);} else if (deptIdInExpression != null && oneselfEqualsTo == null) {return deptIdInExpression;} else if (deptIdInExpression == null && oneselfEqualsTo != null) {return oneselfEqualsTo;}return null;}/*** 构建Column** @param tableAlias 表别名* @param columnName 字段名称* @return 带表别名字段*/public static Column buildColumn(String tableAlias, String columnName) {if (StringUtils.isNotBlank(tableAlias)) {columnName = tableAlias + "." + columnName;}return new Column(columnName);}}
DataScopePlus.java

import java.lang.annotation.*;/*** @author: * @date: * @Description: 数据权限注解。仅在Mapper层使用* 可以使用在类上,也可以使用在方法上。* - 如果 Mapper类加上注解,表示 Mapper提供的方法以及自定义的方法都会被加上数据权限* - 如果 Mapper类的方法加在上注解,表示该方法会被加上数据权限* - 如果 Mapper类和其方法同时加上注解,优先级为:【类上 > 方法上】* - 如果不需要数据权限,可以不加注解,也可以使用 @DataScopePlus(enabled = false)* - 如果使用的是mybatis-plus自带的查询,需要在Mapper层重写对应的方法,然后在方法上加注解,*   也可以不重写,在整个Mapper类上加注解,但是会影响所有方法包括baseMapper里的。*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScopePlus {/*** 是否生效,默认true-生效*/boolean enabled() default true;/*** 表别名 自定义sql语句有表别名就设置*/String tableAlias() default "";/*** 用户表的别名*///public String userAlias() default "";/*** 部门限制范围的字段名称*///String deptScopeName() default "dept_id";/*** 本人限制范围的字段名称*/String oneselfScopeName() default "create_id";}

二、在MybatisPlusConfig.java配置文件中注册拦截器

    @Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 数据权限插件interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataScopePlusHandler()));}

三、在Mapper层使用注解

public interface TestMapper extends BaseMapper<Test> {@DataScopePlusList<Test> selectList();}

四、Mybatis-Plus自带的方法

如果在Service层用是plus自带的方法,则需要在Mapper重写该方法,并且使用权限注解

public interface TestMapper extends BaseMapper<Test> {@DataScopePlusList<Test> selectList();@Override@DataScopePlusList<Report> selectList(Wrapper<Report> queryWrapper);}

也可以直接在Mapper层类上使用权限注解,那么所有方法都将进行数据权限过滤。(不推荐)

@DataScopePlus
public interface TestMapper extends BaseMapper<Test> {List<Test> selectList();}

其他

常量类

DataScopeConstants.java

/*** @Author: * @Time: * @Description: 数据权限常量类*/
public class DataScopeConstants {/*** 全部数据权限*/public static final String DATA_SCOPE_ALL = "1";/*** 自定数据权限*/public static final String DATA_SCOPE_CUSTOM = "2";/*** 部门数据权限*/public static final String DATA_SCOPE_DEPT = "3";/*** 部门及以下数据权限*/public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";/*** 仅本人数据权限*/public static final String DATA_SCOPE_SELF = "5";/*** 数据权限过滤关键字*/public static final String DATA_SCOPE = "dataScope";}

主要依赖

<!-- mybatis-plus 依賴 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version>
</dependency><!-- JSQLParser 4.6 对应 mybatis-plus 3.5.5-->
<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.6</version>
</dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version>
</dependency>

延伸

有几个需要注意的点:

1.这种实现方法注解只能在Mapper层使用,service层使用无效。

2.如果注解了BaseMapper自带的方法,那么该方法任何地方使用都会进行数据权限过滤,这个地方可以会发生其他方法调用该方法时并不需要数据过滤的时候过滤了。

3.这种实现方法不管Mapper的实现是不是在对应的xml里进行了sql语句都管用,也就是均适用于mybatis-plus和原生mybatis。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【软考】设计模式之生成器模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.生成器模式&#xff08;Builder Pattern&#xff09;&#xff0c;也称为建造者模式&#xff0c;是设计模式中的一种创建型模式。2.将一个复杂对象的构建与它的表示分离&#xff0c;使得…

RT-Thread Studio搭建 Renesa Version Board开发环境

目录 概述 1 认识Version Board 1.1 Vision-Board简介 1.2 Vision-Board的资源 2 搭建Version Board开发环境 2.1 RT Thread Studio 2.2 安装SDK 3 开发环境验证 3.1 创建项目 3.2 编译和下载 概述 本文主要介绍使用RT-Thread Studio搭建 Renesa Version Board开发环…

MySQL服务启动与关闭

1. 服务启动与关闭 在生产环境中&#xff0c;数据库服务的运行状态一般是不会进行随意调整的&#xff0c;在特殊场景下需要提前审批后&#xff0c;才能进行调整。在进行数据库服务关闭前&#xff0c;可以将业务先切换到备库&#xff08;从库&#xff09;&#xff0c;再停止原有…

自定义prometheus监控获取nginx_upstream指标

1、前言 上篇文章介绍了nginx通过nginx_upstream_check_module模块实现后端健康检查&#xff0c;这篇介绍一下如何自定义prometheus监控获取nginx的upstream指标来实时监控nginx。 2、nginx_upstream_status状态 支持以下三种方式查看nginx_upstream的状态 /status?formatht…

day05 Router、vuex、axios

配置 router和vuex需要在创建vue项目的时候&#xff0c;开始的时候选择Manually select features&#xff0c;于是就可以在下一个创建配置讯问中选择router和vuex。 axios则需要执行命令行&#xff1a; npm install axios -S 之后再在需要发送请求的view导入即可。 router…

【H.264】H.264详解(二)—— H264视频码流解析示例源码

文章目录 一、前言二、示例源码【1】目录结构【2】Makefile源码【3】h264parser.c源码【4】编译运行【5】源码下载地址 声明&#xff1a;此篇示例源码非原创&#xff0c;原作者雷霄骅。雷霄骅&#xff0c;中国传媒大学通信与信息系统专业博士生&#xff0c;在此向雷霄骅雷神致敬…

放大电路总结

补充: 只有直流移动时才有Rbe动态等效电阻 从RsUs看进去,实际上不管接了什么东西都能够看成是一个Ri(输入电阻) Ri Ui/Ii Rb//Rbe Ui/Us Ri/(RiRs) Aus (Uo/Ui)*(Ui/Us) Au *Ri/(RiRs) 当前面是一个电压源的信号 我们就需要输入电阻更大 Ro--->输出电阻--->将…

RustDesk远程控屏软件使用教学

RustDesk自建服务器使用教学RustDesk远程控屏软件使用教学 下载软件后 右键管理员运行 点击右上角设置按钮 管理员运行 保证启动服务 点击左侧导航栏网络按钮 复制域名或者ip地址到 ID服务器 输入框 然后点击应用即可

C语言第三天笔记

变量 概念 表面&#xff1a;程序运行过程中取值可以改变的数据 实质&#xff1a;变量其实代表了一块内存区域/单元/空间。变量名可视为该区域的标识。 整个变量分为三部分&#xff1a; 变量名&#xff1a;这个只是变量的一个标识&#xff0c;我们借助变量名来存取数据。 变…

数据库实例迁移实践

背景 随着业务发展&#xff0c;数据库实例磁盘逐渐升高&#xff0c;告警频繁&#xff0c;且后续可能会对DDL产生影响&#xff08;尤其是借助ghost等工具执行的DDL&#xff09;。 该实例有多个库&#xff0c;则需要迁移其中的一个或几个单库到其他实例&#xff0c;为什么不做分…

第G4周:CGAN|生成手势图像 | 可控制生成

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊 理论知识&#xff1a; 条件生成对抗网络&#xff08;CGAN&#xff09;是在生成对抗网络&#xff08;GAN&#xff09;的基础上进行了一些改进。对于原始GAN的生成器而言&#xff0c;其生成的…

探索 SPL-404 协议标准:NFT 与 DeFi 的融合

在快速发展的数字资产领域中&#xff0c;NFT 协议标准持续演变&#xff0c;改变了我们对数字所有权和互动方式的理解。从 Art 到 Gamefi 等等&#xff0c;NFT 已经演变成数字经济的重要组成部分&#xff0c;吸引了广泛关注。遵循 ERC404 协议&#xff0c;SPL404 概念在 Solana …

昇思25天学习打卡营第22天|CV-Vision Transformer图像分类

打卡 目录 打卡 ViT简介 模型结构 基于ViT实现ImageNet分类任务 环境准备与数据读取 模型解析 Transformer基本原理 Self-Attention模块 代码实现 Transformer Encoder 代码实现 ViT模型的输入 Patch Embedding代码处理输入 整体构建ViT 模型训练与推理 模型训…

金字塔监督在人脸反欺骗中的应用

介绍 论文地址&#xff1a;https://arxiv.org/pdf/2011.12032.pdf 近年来&#xff0c;人脸识别技术越来越普及。在智能手机解锁和进出机场时&#xff0c;理所当然地会用到它。人脸识别也有望被用于管理今年奥运会的相关人员。但与此同时&#xff0c;人们对人脸欺骗的关注度也…

【无标题】Git(仓库,分支,分支冲突)

Git 一种分布式版本控制系统&#xff0c;用于跟踪和管理代码的变更 一&#xff0e;Git的主要功能&#xff1a; 二&#xff0e;准备git机器 修改静态ip&#xff0c;主机名 三&#xff0e;git仓库的建立&#xff1a; 1.安装git [rootgit ~]# yum -y install git 2.创建一个…

【Linux】syscall sys_write流程摸索

这是通过tty进行摸索sys_write的流程。 在前面的博客里&#xff0c;我们可以看到基于内核C语言源代码日志打印&#xff0c;在打印的日志里边包含&#xff1a;日期&#xff0c;时间&#xff0c;当前文件所在代码目录&#xff0c;当前执行函数名&#xff0c;当前文件执行行号&am…

苦学Opencv的第十一天:图像的形态学操作

Python OpenCV从入门到精通学习日记&#xff1a;图像的形态学操作 前言 图像形态学是图像处理中的一个重要分支&#xff0c;主要关注图像中物体的形状和结构。通过形态学操作&#xff0c;我们可以对图像进行有效的分析和处理&#xff0c;例如图像的腐蚀与膨胀、开运算与闭运算…

nginx的学习(一):nginx的基本概念和反向代理

简介 nginx的基本概念&#xff0c;以及反向代理的配置 nginx 是一个高性能的http和反向代理web服务器及电子邮件&#xff08;IMAP/POP3/SMTP&#xff09;代理服务器。 相关的基本概念 正向代理 客户端配置代理服务器&#xff0c;通过代理服务器访问互联网。 反向代理 客…

【8月EI会议推荐】第四届区块链技术与信息安全国际会议

一、会议信息 大会官网&#xff1a;http://www.bctis.nhttp://www.icbdsme.org/ 官方邮箱&#xff1a;icbctis126.com 组委会联系人&#xff1a;杨老师 19911536763 支持单位&#xff1a;中原工学院、西安工程大学、齐鲁工业大学&#xff08;山东省科学院&#xff09;、澳门…

Xinstall揭秘:一键拉起服务如何助力App提升用户体验和下载转化率

在移动互联网时代&#xff0c;App的运营和推广显得尤为重要。而在这个过程中&#xff0c;如何提升用户体验和下载转化率成为了每个App运营者关注的焦点。今天&#xff0c;我们就来揭秘一下Xinstall的一键拉起服务&#xff0c;看看它是如何助力App提升用户体验和下载转化率的。 …