0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何利用MyBatis Plus去实现数据权限控制呢?

jf_ro2CN3Fa 来源:CSDN 2023-08-23 10:40 次阅读

前言背景

平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求。列表实现方案有两种,一是在开发初期就做好判断赛选,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式。在mybatis执行sql前修改语句,限定where范围。

当然拦截器生效后是全局性的,如何保证只对需要的接口进行拦截和转化,就可以应用注解进行识别

因此具体需要哪些步骤就明确了

创建注解类

创建拦截器实现InnerInterceptor接口,重写查询方法

创建处理类,获取数据权限 SQL 片段,设置where

将拦截器加到MyBatis-Plus插件中

自定义注解

importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceUserDataPermission{
}

拦截器

importcom.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
importcom.baomidou.mybatisplus.core.toolkit.PluginUtils;
importcom.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
importlombok.*;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.statement.select.PlainSelect;
importnet.sf.jsqlparser.statement.select.Select;
importnet.sf.jsqlparser.statement.select.SelectBody;
importnet.sf.jsqlparser.statement.select.SetOperationList;
importorg.apache.ibatis.executor.Executor;
importorg.apache.ibatis.mapping.BoundSql;
importorg.apache.ibatis.mapping.MappedStatement;
importorg.apache.ibatis.session.ResultHandler;
importorg.apache.ibatis.session.RowBounds;

importjava.sql.SQLException;
importjava.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper=true)
@EqualsAndHashCode(callSuper=true)
publicclassMyDataPermissionInterceptorextendsJsqlParserSupportimplementsInnerInterceptor{

/**
*数据权限处理器
*/
privateMyDataPermissionHandlerdataPermissionHandler;

@Override
publicvoidbeforeQuery(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{
if(InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())){
return;
}
PluginUtils.MPBoundSqlmpBs=PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(),ms.getId()));
}

@Override
protectedvoidprocessSelect(Selectselect,intindex,Stringsql,Objectobj){
SelectBodyselectBody=select.getSelectBody();
if(selectBodyinstanceofPlainSelect){
this.setWhere((PlainSelect)selectBody,(String)obj);
}elseif(selectBodyinstanceofSetOperationList){
SetOperationListsetOperationList=(SetOperationList)selectBody;
ListselectBodyList=setOperationList.getSelects();
selectBodyList.forEach(s->this.setWhere((PlainSelect)s,(String)obj));
}
}

/**
*设置where条件
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*/
privatevoidsetWhere(PlainSelectplainSelect,StringwhereSegment){

ExpressionsqlSegment=this.dataPermissionHandler.getSqlSegment(plainSelect,whereSegment);
if(null!=sqlSegment){
plainSelect.setWhere(sqlSegment);
}
}
}

拦截器处理器

基础只涉及 = 表达式,要查询集合范围 in 看进阶版用例

importcn.hutool.core.collection.CollectionUtil;
importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

/**
*获取数据权限SQL片段
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*@returnJSqlParser条件表达式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
//待执行SQLWhere条件表达式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("开始进行权限过滤,where:{},mappedStatementId:{}",where,whereSegment);
//获取mapper名称
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//获取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有别名用别名,无别名用表名,防止字段冲突报错
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//获取当前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍历判断mapper的所以方法,判断方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、当前用户Code
Useruser=SecurityUtils.getUser();
//查看自己的数据
//=表达式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
}
}
//说明无权查看,
where=newHexValue("1=2");
returnwhere;
}

}

将拦截器加到MyBatis-Plus插件中

如果你之前项目配插件 ,直接用下面方式就行

@Bean
publicMybatisPlusInterceptormybatisPlusInterceptor(){
MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();
//添加数据权限插件
MyDataPermissionInterceptordataPermissionInterceptor=newMyDataPermissionInterceptor();
//添加自定义的数据权限处理器
dataPermissionInterceptor.setDataPermissionHandler(newMyDataPermissionHandler());
interceptor.addInnerInterceptor(dataPermissionInterceptor);
interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));
returninterceptor;
}

但如果你项目之前是依赖包依赖,或有公司内部统一拦截设置好,也可以往MybatisPlusInterceptor进行插入,避免影响原有项目配置

@Bean
publicMyDataPermissionInterceptormyInterceptor(MybatisPlusInterceptormybatisPlusInterceptor){
MyDataPermissionInterceptorsql=newMyDataPermissionInterceptor();
sql.setDataPermissionHandler(newMyDataPermissionHandler());
Listlist=newArrayList<>();
//添加数据权限插件
list.add(sql);
//分页插件
mybatisPlusInterceptor.setInterceptors(list);
list.add(newPaginationInnerInterceptor(DbType.MYSQL));
returnsql;
}

以上就是简单版的是拦截器修改语句使用

使用方式

在mapper层添加注解即可

@UserDataPermission
ListselectAllCustomerPage(IPagepage,@Param("customerName")StringcustomerName);

基础班只是能用,业务功能没有特别约束,先保证能跑起来

进阶版 解决两个问题:

加了角色,用角色决定范围

解决不是mapper层自定义sql查询问题。

两个是完全独立的问题 ,可根据情况分开解决

解决不是mapper层自定义sql查询问题。

例如我们名称简单的sql语句 直接在Service层用mybatisPluse自带的方法

xxxxService.list(WrapperqueryWrapper)
xxxxService.page(newPage<>(),WrapperqueryWrapper)

以上这种我应该把注解加哪里呢

因为service层,本质上还是调mapper层, 所以还是在mapper层做文章,原来的mapper实现了extends BaseMapper 接口,所以能够查询,我们要做的就是在 mapper层中间套一个中间接口,来方便我们加注解

xxxxxMapper——》DataPermissionMapper(中间)——》BaseMapper

根据自身需要,在重写的接口方法上加注解即可,这样就影响原先的代码

4d7a8f2c-40d8-11ee-a2ef-92fbcf53809c.png

importcom.baomidou.mybatisplus.core.conditions.Wrapper;
importcom.baomidou.mybatisplus.core.mapper.BaseMapper;
importcom.baomidou.mybatisplus.core.metadata.IPage;
importcom.baomidou.mybatisplus.core.toolkit.Constants;
importorg.apache.ibatis.annotations.Param;

importjava.io.Serializable;
importjava.util.Collection;
importjava.util.List;
importjava.util.Map;

publicinterfaceDataPermissionMapperextendsBaseMapper{

/**
*根据ID查询
*
*@paramid主键ID
*/
@Override
@UserDataPermission
TselectById(Serializableid);

/**
*查询(根据ID批量查询)
*
*@paramidList主键ID列表(不能为null以及empty)
*/
@Override
@UserDataPermission
ListselectBatchIds(@Param(Constants.COLLECTION)CollectionidList);

/**
*查询(根据columnMap条件)
*
*@paramcolumnMap表字段map对象
*/
@Override
@UserDataPermission
ListselectByMap(@Param(Constants.COLUMN_MAP)MapcolumnMap);

/**
*根据entity条件,查询一条记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
TselectOne(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询总记录数
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
IntegerselectCount(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据entity条件,查询全部记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
ListselectList(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询全部记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
List>selectMaps(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询全部记录
*

注意:只返回第一个字段的值

* *@paramqueryWrapper实体对象封装操作类(可以为null) */ @Override @UserDataPermission ListselectObjs(@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根据entity条件,查询全部记录(并翻页) * *@parampage分页查询条件(可以为RowBounds.DEFAULT) *@paramqueryWrapper实体对象封装操作类(可以为null) */ @Override @UserDataPermission >EselectPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根据Wrapper条件,查询全部记录(并翻页) * *@parampage分页查询条件 *@paramqueryWrapper实体对象封装操作类 */ @Override @UserDataPermission >>EselectMapsPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); }

解决角色控制查询范围

引入角色,我们先假设有三种角色,按照常规的业务需求,一种是管理员查看全部、一种是部门管理查看本部门、一种是仅查看自己。

有了以上假设,就可以设置枚举类编写业务逻辑, 对是业务逻辑,所以我们只需要更改”拦截器处理器类“

建立范围枚举

建立角色枚举以及范围关联关系

重写拦截器处理方法

范围枚举

@AllArgsConstructor
@Getter
publicenumDataScope{
//Scope数据权限范围:ALL(全部)、DEPT(部门)、MYSELF(自己)
ALL("ALL"),
DEPT("DEPT"),
MYSELF("MYSELF");
privateStringname;
}

角色枚举

@AllArgsConstructor
@Getter
publicenumDataPermission{

//枚举类型根据范围从前往后排列,避免影响getScope
//Scope数据权限范围:ALL(全部)、DEPT(部门)、MYSELF(自己)
DATA_MANAGER("数据管理员","DATA_MANAGER",DataScope.ALL),
DATA_AUDITOR("数据审核员","DATA_AUDITOR",DataScope.DEPT),
DATA_OPERATOR("数据业务员","DATA_OPERATOR",DataScope.MYSELF);

privateStringname;
privateStringcode;
privateDataScopescope;


publicstaticStringgetName(Stringcode){
for(DataPermissiontype:DataPermission.values()){
if(type.getCode().equals(code)){
returntype.getName();
}
}
returnnull;
}

publicstaticStringgetCode(Stringname){
for(DataPermissiontype:DataPermission.values()){
if(type.getName().equals(name)){
returntype.getCode();
}
}
returnnull;
}

publicstaticDataScopegetScope(Collectioncode){
for(DataPermissiontype:DataPermission.values()){
for(Stringv:code){
if(type.getCode().equals(v)){
returntype.getScope();
}
}
}
returnDataScope.MYSELF;
}
}

重写拦截器处理类 MyDataPermissionHandler

importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

privateRemoteRoleServiceremoteRoleService;
privateRemoteUserServiceremoteUserService;


/**
*获取数据权限SQL片段
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*@returnJSqlParser条件表达式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
remoteRoleService=SpringUtil.getBean(RemoteRoleService.class);
remoteUserService=SpringUtil.getBean(RemoteUserService.class);

//待执行SQLWhere条件表达式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("开始进行权限过滤,where:{},mappedStatementId:{}",where,whereSegment);
//获取mapper名称
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//获取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有别名用别名,无别名用表名,防止字段冲突报错
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//获取当前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍历判断mapper的所以方法,判断方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、当前用户Code
Useruser=SecurityUtils.getUser();
//2、当前角色即角色或角色类型(可能多种角色)
SetroleTypeSet=remoteRoleService.currentUserRoleType();

DataScopescopeType=DataPermission.getScope(roleTypeSet);
switch(scopeType){
//查看全部
caseALL:
returnwhere;
caseDEPT:
//查看本部门用户数据
//创建IN表达式
//创建IN范围的元素集合
ListdeptUserList=remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
//把集合转变为JSQLParser需要的元素列表
ItemsListdeptList=newExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
InExpressioninExpressiondept=newInExpression(newColumn(mainTableName+".creator_code"),deptList);
returnnewAndExpression(where,inExpressiondept);
caseMYSELF:
//查看自己的数据
//=表达式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
default:
break;
}
}

}
//说明无权查看,
where=newHexValue("1=2");
returnwhere;
}
}

以上就是全篇知识点, 需要注意的点可能有:

记得把拦截器加到MyBatis-Plus的插件中,确保生效

要有一个业务赛选标识字段, 这里用的创建人 creator_code, 也可以用dept_code 等等。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19284

    浏览量

    229796
  • SQL
    SQL
    +关注

    关注

    1

    文章

    764

    浏览量

    44127

原文标题:巧用 MyBatis Plus 实现数据权限控制

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何利用STM32接口接收和探测实现数据的接收和发送

    如何利用STM32接口接收和探测实现数据的接收和发送?其代码是如何
    发表于 11-17 07:12

    如何利用DW1000实现数据的接受与发送

    如何利用DW1000实现数据的接受与发送
    发表于 02-11 06:40

    Lora sx1278是怎样利用串口协议实现数据传输的

    Lora sx1278是怎样利用串口协议实现数据传输的?其代码该怎样
    发表于 02-21 06:37

    MyBatis实现原理

    本文主要详细介绍了MyBatis实现原理。mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Execut
    的头像 发表于 02-24 11:25 6488次阅读
    <b class='flag-5'>MyBatis</b>的<b class='flag-5'>实现</b>原理

    Mybatis-Plus Mybatis增强工具包

    ./oschina_soft/gitee-mybatis-plus.zip
    发表于 06-13 11:34 1次下载
    <b class='flag-5'>Mybatis-Plus</b> <b class='flag-5'>Mybatis</b>增强工具包

    MyBatis-Plus的使用与测试

    本文主要介绍mybatis-plus这款插件,针对springboot用户。包括引入,配置,使用,以及扩展等常用的方面做一个汇总整理,尽量包含大家常用的场景内容。
    的头像 发表于 08-22 11:56 1320次阅读

    Fluent Mybatis、原生MybatisMybatis Plus对比

    mapper中再组装参数。那对比原生Mybatis, Mybatis Plus或者其他框架,FluentMybatis提供了哪些便利
    的头像 发表于 09-15 15:41 1433次阅读

    SpringBoot 实现异步记录复杂日志

    基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、
    发表于 12-22 10:35 470次阅读

    SpringBoot+ElasticSearch实现模糊查询功能

    基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、
    的头像 发表于 12-30 14:00 1013次阅读

    MyBatis-Plus为什么不支持联表

    `的所有功能`MyBatis Plus Join`同样拥有;框架的使用方式和`MyBatis Plus`一样简单,几行代码就能实现联表查询的
    的头像 发表于 02-28 15:19 2468次阅读
    <b class='flag-5'>MyBatis-Plus</b>为什么不支持联表

    基于Mybatis拦截器实现数据范围权限

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据数据权限需要通过手动添加SQL来
    的头像 发表于 06-20 09:57 1336次阅读
    基于<b class='flag-5'>Mybatis</b>拦截器<b class='flag-5'>实现</b><b class='flag-5'>数据</b>范围<b class='flag-5'>权限</b>

    如何实现基于Mybatis拦截器实现数据范围权限

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据数据权限需要通过手动添加SQL来
    的头像 发表于 06-20 09:59 1238次阅读
    如何<b class='flag-5'>实现</b>基于<b class='flag-5'>Mybatis</b>拦截器<b class='flag-5'>实现</b><b class='flag-5'>数据</b>范围<b class='flag-5'>权限</b><b class='flag-5'>呢</b>?

    你还在手写join联表查询?MyBatis-Plus这样写太香了!

    众所周知,mybatis plus 封装的 mapper 不支持 join,如果需要支持就必须自己实现。但是对于大部分的业务场景来说,都需要多表 join,要不然就没必要采用关系型
    的头像 发表于 07-07 10:19 2730次阅读
    你还在手写join联表查询?<b class='flag-5'>MyBatis-Plus</b>这样写太香了!

    不好意思,list.contain 重该换换了!

    基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、
    的头像 发表于 09-14 15:50 478次阅读
    不好意思,list.contain <b class='flag-5'>去</b>重该换换了!

    使用mybatis切片实现数据权限控制

    一、使用方式 数据权限控制需要对查询出的数据进行筛选,对业务入侵最少的方式就是利用mybatis
    的头像 发表于 07-09 17:26 369次阅读
    使用<b class='flag-5'>mybatis</b>切片<b class='flag-5'>实现</b><b class='flag-5'>数据</b><b class='flag-5'>权限</b><b class='flag-5'>控制</b>