前言背景
平時開發中遇到根據當前用戶的角色,只能查看數據權限范圍的數據需求。列表實現方案有兩種,一是在開發初期就做好判斷賽選,但如果這個需求是中途加的,或不希望每個接口都加一遍,就可以方案二加攔截器的方式。在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(IPage page,@Param("customerName")StringcustomerName);
基礎班只是能用,業務功能沒有特別約束,先保證能跑起來
進階版 解決兩個問題:
加了角色,用角色決定范圍
解決不是mapper層自定義sql查詢問題。
兩個是完全獨立的問題 ,可根據情況分開解決
解決不是mapper層自定義sql查詢問題。
例如我們名稱簡單的sql語句 直接在Service層用mybatisPluse自帶的方法
xxxxService.list(WrapperqueryWrapper) xxxxService.page(newPage<>(),Wrapper queryWrapper)
以上這種我應該把注解加哪里呢
因為service層,本質上還是調mapper層, 所以還是在mapper層做文章,原來的mapper實現了extends BaseMapper 接口,所以能夠查詢,我們要做的就是在 mapper層中間套一個中間接口,來方便我們加注解
xxxxxMapper——》DataPermissionMapper(中間)——》BaseMapper
根據自身需要,在重寫的接口方法上加注解即可,這樣就影響原先的代碼

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 List selectBatchIds(@Param(Constants.COLLECTION)Collection?extends?Serializable>idList); /** *查詢(根據columnMap條件) * *@paramcolumnMap表字段map對象 */ @Override @UserDataPermission List selectByMap(@Param(Constants.COLUMN_MAP)Map columnMap); /** *根據entity條件,查詢一條記錄 * *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission TselectOne(@Param(Constants.WRAPPER)Wrapper queryWrapper); /** *根據Wrapper條件,查詢總記錄數 * *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission IntegerselectCount(@Param(Constants.WRAPPER)Wrapper queryWrapper); /** *根據entity條件,查詢全部記錄 * *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission List selectList(@Param(Constants.WRAPPER)Wrapper queryWrapper); /** *根據Wrapper條件,查詢全部記錄 * *@paramqueryWrapper實體對象封裝操作類(可以為null) */ @Override @UserDataPermission List
解決角色控制查詢范圍
引入角色,我們先假設有三種角色,按照常規的業務需求,一種是管理員查看全部、一種是部門管理查看本部門、一種是僅查看自己。
有了以上假設,就可以設置枚舉類編寫業務邏輯, 對是業務邏輯,所以我們只需要更改”攔截器處理器類“
建立范圍枚舉
建立角色枚舉以及范圍關聯關系
重寫攔截器處理方法
范圍枚舉
@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文章
20250瀏覽量
252209 -
SQL
+關注
關注
1文章
789瀏覽量
46695
原文標題:巧用 MyBatis Plus 實現數據權限控制
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
MyBatis的實現原理
MyBatis-Plus的使用與測試
Fluent Mybatis、原生Mybatis和Mybatis Plus對比
手動實現SpringBoot日志鏈路追蹤
SpringBoot 實現異步記錄復雜日志
SpringBoot+ElasticSearch實現模糊查詢功能
你還在手寫join聯表查詢?MyBatis-Plus這樣寫太香了!
不好意思,list.contain 去重該換換了!
如何利用MyBatis Plus去實現數據權限控制呢?
評論