MyBatis-Plus數(shù)據(jù)權限插件的簡單使用
平時開發(fā)中遇到根據(jù)當前用戶的角色,只能查看數(shù)據(jù)權限范圍的數(shù)據(jù)需求。一般我們采用攔截器在mybatis執(zhí)行sql前修改語句,限定where范圍。
通常攔截器針對注解進行識別,可以更好的對需要的接口進行攔截和轉化。
一般步驟如下:
- 創(chuàng)建注解類
- 創(chuàng)建處理類,獲取數(shù)據(jù)權限 SQL 片段,設置 where條件
- 將攔截器加到MyBatis-Plus插件中
一、數(shù)據(jù)權限插件簡介
官方文檔-數(shù)據(jù)權限插件:https://baomidou.com/plugins/data-permission/
DataPermissionInterceptor 是 MyBatis-Plus 提供的一個插件,用于實現(xiàn)數(shù)據(jù)權限控制。它通過攔截執(zhí)行的 SQL 語句,并動態(tài)拼接權限相關的 SQL 片段,來實現(xiàn)對用戶數(shù)據(jù)訪問的控制。
DataPermissionInterceptor 的工作原理會在 SQL 執(zhí)行前攔截 SQL 語句,并根據(jù)用戶權限動態(tài)添加權限相關的 SQL 片段。這樣,只有用戶有權限訪問的數(shù)據(jù)才會被查詢出來。
JSQLParser 是一個開源的 SQL 解析庫,可方便地解析和修改 SQL 語句。它是插件實現(xiàn)權限邏輯的關鍵工具,MyBatis-Plus 的數(shù)據(jù)權限依托于 JSQLParser 的解析能力。
使用方法:
- 自定義 MultiDataPermissionHandler的實現(xiàn)處理器類,處理自定義數(shù)據(jù)權限邏輯。
- 注冊數(shù)據(jù)權限攔截器
二、數(shù)據(jù)權限插件實現(xiàn)
在項目中,使用的是 spring security + oauth2安全認證,角色來定義數(shù)據(jù)權限類型。
角色的數(shù)據(jù)權限類型:
@Getter
@RequiredArgsConstructor
public enum DataScopeEnum {
/**
* 全部數(shù)據(jù)權限
*/
DATA_SCOPE_ALL("0", "全部數(shù)據(jù)權限"),
/**
* 自定義數(shù)據(jù)權限
*/
DATA_SCOPE_CUSTOM("1", "自定義數(shù)據(jù)權限"),
/**
* 本部門及子級數(shù)據(jù)權限
*/
DATA_SCOPE_DEPT_AND_CHILD("2", "部門及子級數(shù)據(jù)權限"),
/**
* 本部門數(shù)據(jù)權限
*/
DATA_SCOPE_DEPT("3", "部門數(shù)據(jù)權限"),
/**
* 本人數(shù)據(jù)權限
*/
DATA_SCOPE_SELF("4", "本人數(shù)據(jù)權限"),
;
/**
* 對應數(shù)據(jù)庫字典值
*/
private final String dbValue;
/**
* 描述
*/
private final String description;
}
對于 UserDetails用戶信息我們做了擴展。添加了關于數(shù)據(jù)權限的字段信息。
public class SxdhcloudUser extends User implements OAuth2AuthenticatedPrincipal {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 擴展屬性,方便存放oauth 上下文相關信息
*/
private final Map<String, Object> attributes = new HashMap<>();
/**
* 租戶ID
*/
@Getter
@JsonSerialize(using = ToStringSerializer.class)
private final Long tenantId;
/**
* 用戶ID
*/
@Getter
@JsonSerialize(using = ToStringSerializer.class)
private final Long id;
/**
* 部門ID
*/
@Getter
@JsonSerialize(using = ToStringSerializer.class)
private final Long deptId;
/**
* 手機號
*/
@Getter
private final String phone;
/**
* 角色數(shù)據(jù)權限類型,去重
*/
@Getter
private final Set<String> dataScopeTypes;
/**
* 數(shù)據(jù)權限部門ID集合,去重
*/
@Getter
private final Set<Long> dataScopeDeptIds;
/**
* 數(shù)據(jù)權限本人ID
*/
@Getter
private final Long dataScopeCreateId;
}
用戶在登錄認證成功之后,獲取到角色數(shù)據(jù)權限的相關數(shù)據(jù),并放到 UserDetails用戶信息中。
1、自定義注解
自定義數(shù)據(jù)權限注解。
/**
* 數(shù)據(jù)權限注解。
* 可以使用在類上,也可以使用在方法上。
* - 如果 Mapper類加上注解,表示 Mapper提供的方法以及自定義的方法都會被加上數(shù)據(jù)權限
* - 如果 Mapper類的方法加在上注解,表示該方法會被加上數(shù)據(jù)權限
* - 如果 Mapper類和其方法同時加上注解,優(yōu)先級為:【類上 > 方法上】
* - 如果不需要數(shù)據(jù)權限,可以不加注解,也可以使用 @DataScope(enabled = false)
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 是否生效,默認true-生效
*/
boolean enabled() default true;
/**
* 表別名
*/
String tableAlias() default "";
/**
* 部門限制范圍的字段名稱
*/
String deptScopeName() default "dept_id";
/**
* 本人限制范圍的字段名稱
*/
String oneselfScopeName() default "create_id";
}
2、自定義處理器
自定義處理器類并實現(xiàn) MultiDataPermissionHandler接口,在 getSqlSegment()方法中處理自定義數(shù)據(jù)權限邏輯。
注意:mybaits-plus 必須大于 3.5.2版本。
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.sxdh.sxdhcloud.common.mybatis.annotation.DataScope;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
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.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 數(shù)據(jù)權限拼裝邏輯處理
*
*/
public class DataScopeHandler implements MultiDataPermissionHandler {
/**
* 獲取數(shù)據(jù)權限 SQL 片段。
* <p>舊的 {@link MultiDataPermissionHandler#getSqlSegment(Expression, String)} 方法第一個參數(shù)包含所有的 where 條件信息,如果 return 了 null 會覆蓋原有的 where 數(shù)據(jù),</p>
* <p>新版的 {@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆蓋原有的 where 數(shù)據(jù),如果 return 了 null 則表示不追加任何 where 條件</p>
*
* @param table 所執(zhí)行的數(shù)據(jù)庫表信息,可以通過此參數(shù)獲取表名和表別名
* @param where 原有的 where 條件信息
* @param mappedStatementId Mybatis MappedStatement Id 根據(jù)該參數(shù)可以判斷具體執(zhí)行方法
* @return JSqlParser 條件表達式,返回的條件表達式會拼接在原有的表達式后面(不會覆蓋原有的表達式)
*/
@Override
public 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注解優(yōu)先級:【類上 > 方法上】
*/
// 獲取 DataScope注解
DataScope dataScopeAnnotationClazz = mapperClazz.getAnnotation(DataScope.class);
if (ObjectUtils.isNotEmpty(dataScopeAnnotationClazz) && dataScopeAnnotationClazz.enabled()) {
return buildDataScopeByAnnotation(dataScopeAnnotationClazz);
}
// 獲取自身類中的所有方法,不包括繼承。與訪問權限無關
Method[] methods = mapperClazz.getDeclaredMethods();
for (Method method : methods) {
DataScope dataScopeAnnotationMethod = method.getAnnotation(DataScope.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注解方式,拼裝數(shù)據(jù)權限
*
* @param dataScope
* @return
*/
private Expression buildDataScopeByAnnotation(DataScope dataScope) {
// 獲取 UserDetails用戶信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
Map<String, Object> userDetailsMap = BeanUtil.beanToMap(authentication.getPrincipal());
Set<String> dataScopeTypes = (Set<String>) userDetailsMap.get("dataScopeTypes");
Set<Long> dataScopeDeptIds = (Set<Long>) userDetailsMap.get("dataScopeDeptIds");
Long dataScopeCreateId = (Long) userDetailsMap.get("dataScopeCreateId");
// 獲取注解信息
String tableAlias = dataScope.tableAlias();
String deptScopeName = dataScope.deptScopeName();
String oneselfScopeName = dataScope.oneselfScopeName();
Expression expression = buildDataScopeExpression(tableAlias, deptScopeName, oneselfScopeName, dataScopeDeptIds, dataScopeCreateId);
return expression == null ? null : new Parenthesis(expression);
}
/**
* 拼裝數(shù)據(jù)權限
*
* @param tableAlias 表別名
* @param deptScopeName 部門限制范圍的字段名稱
* @param oneselfScopeName 本人限制范圍的字段名稱
* @param dataScopeDeptIds 數(shù)據(jù)權限部門ID集合,去重
* @param dataScopeCreateId 數(shù)據(jù)權限本人ID
* @return
*/
private Expression buildDataScopeExpression(String tableAlias, String deptScopeName, String oneselfScopeName, Set<Long> dataScopeDeptIds, Long dataScopeCreateId) {
/**
* 構造部門in表達式。
*/
InExpression deptIdInExpression = null;
if (CollectionUtils.isNotEmpty(dataScopeDeptIds)) {
deptIdInExpression = new InExpression();
ExpressionList deptIds = new ExpressionList(dataScopeDeptIds.stream().map(LongValue::new).collect(Collectors.toList()));
// 設置左邊的字段表達式,右邊設置值。
deptIdInExpression.setLeftExpression(buildColumn(tableAlias, deptScopeName));
deptIdInExpression.setRightExpression(new Parenthesis(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.isNotEmpty(tableAlias)) {
columnName = tableAlias + "." + columnName;
}
return new Column(columnName);
}
}
3、注冊數(shù)據(jù)權限攔截器
MybatisPlusConfig配置類中將自定義的處理器注冊到 DataPermissionInterceptor 中。并將攔截器加到MyBatis-Plus插件中。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1.添加數(shù)據(jù)權限插件
interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataScopeHandler()));
// 2.添加分頁插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
// 設置數(shù)據(jù)庫方言類型
pageInterceptor.setDbType(DbType.MYSQL);
// 下面配置根據(jù)需求自行設置
// 設置請求的頁面大于最大頁后操作,true調(diào)回到首頁,false繼續(xù)請求。默認false
pageInterceptor.setOverflow(false);
// 單頁分頁條數(shù)限制,默認無限制
pageInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
4、注解使用
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
/**
* 通過用戶名查詢用戶信息(含有角色信息)
*
* @param username 用戶名
* @return userVo
*/
UserVO getUserVoByUsername(String username);
/**
* 分頁查詢用戶信息(含角色)
*
* @param page 分頁
* @param userDTO 查詢參數(shù)
* @return list
*/
@DataScope(tableAlias = "u")
IPage<UserVO> getUserVosPage(Page page, @Param("query") UserDTO userDTO, @Param("userIds") List<Long> userIds);
}
代碼中的注釋寫的挺清楚,大家自行理解。
參考文章:
- 關于JSqlparser使用攻略(高效的SQL解析工具):http://www.dbjr.com.cn/article/267850.htm
到此這篇關于MyBatis-Plus數(shù)據(jù)權限插件的簡單使用的文章就介紹到這了,更多相關MyBatis-Plus數(shù)據(jù)權限內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
利用Spring Social輕松搞定微信授權登錄的方法示例
這篇文章主要介紹了利用Spring Social輕松搞定微信授權登錄的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
詳解Spring Cloud Finchley版中Consul多實例注冊的問題處理
這篇文章主要介紹了詳解Spring Cloud Finchley版中Consul多實例注冊的問題處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
Java自定義注解實現(xiàn)數(shù)據(jù)脫敏
在實際開發(fā)中經(jīng)常會遇到有一些信息不能全部展示用戶,需要隱藏(可以叫脫敏),所以本文為大家分享了利用自定義注解實現(xiàn)數(shù)據(jù)脫敏的示例代碼,需要的可以參考下2023-07-07
Spring?Security實現(xiàn)分布式系統(tǒng)授權方案詳解
這篇文章主要介紹了Spring?Security實現(xiàn)分布式系統(tǒng)授權,本節(jié)完成注冊中心的搭建,注冊中心采用Eureka,本文通過示例代碼圖文相結合給大家介紹的非常詳細,需要的朋友可以參考下2022-02-02

