MyBatis-Plus數(shù)據(jù)權(quán)限插件的簡(jiǎn)單使用
平時(shí)開發(fā)中遇到根據(jù)當(dāng)前用戶的角色,只能查看數(shù)據(jù)權(quán)限范圍的數(shù)據(jù)需求。一般我們采用攔截器在mybatis執(zhí)行sql前修改語句,限定where范圍。
通常攔截器針對(duì)注解進(jìn)行識(shí)別,可以更好的對(duì)需要的接口進(jìn)行攔截和轉(zhuǎn)化。
一般步驟如下:
- 創(chuàng)建注解類
- 創(chuàng)建處理類,獲取數(shù)據(jù)權(quán)限 SQL 片段,設(shè)置 where條件
- 將攔截器加到MyBatis-Plus插件中
一、數(shù)據(jù)權(quán)限插件簡(jiǎn)介
官方文檔-數(shù)據(jù)權(quán)限插件:https://baomidou.com/plugins/data-permission/
DataPermissionInterceptor
是 MyBatis-Plus 提供的一個(gè)插件,用于實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制。它通過攔截執(zhí)行的 SQL 語句,并動(dòng)態(tài)拼接權(quán)限相關(guān)的 SQL 片段,來實(shí)現(xiàn)對(duì)用戶數(shù)據(jù)訪問的控制。
DataPermissionInterceptor 的工作原理會(huì)在 SQL 執(zhí)行前攔截 SQL 語句,并根據(jù)用戶權(quán)限動(dòng)態(tài)添加權(quán)限相關(guān)的 SQL 片段。這樣,只有用戶有權(quán)限訪問的數(shù)據(jù)才會(huì)被查詢出來。
JSQLParser
是一個(gè)開源的 SQL 解析庫,可方便地解析和修改 SQL 語句。它是插件實(shí)現(xiàn)權(quán)限邏輯的關(guān)鍵工具,MyBatis-Plus 的數(shù)據(jù)權(quán)限依托于 JSQLParser 的解析能力。
使用方法:
- 自定義 MultiDataPermissionHandler的實(shí)現(xiàn)處理器類,處理自定義數(shù)據(jù)權(quán)限邏輯。
- 注冊(cè)數(shù)據(jù)權(quán)限攔截器
二、數(shù)據(jù)權(quán)限插件實(shí)現(xiàn)
在項(xiàng)目中,使用的是 spring security + oauth2安全認(rèn)證,角色來定義數(shù)據(jù)權(quán)限類型。
角色的數(shù)據(jù)權(quán)限類型:
@Getter @RequiredArgsConstructor public enum DataScopeEnum { /** * 全部數(shù)據(jù)權(quán)限 */ DATA_SCOPE_ALL("0", "全部數(shù)據(jù)權(quán)限"), /** * 自定義數(shù)據(jù)權(quán)限 */ DATA_SCOPE_CUSTOM("1", "自定義數(shù)據(jù)權(quán)限"), /** * 本部門及子級(jí)數(shù)據(jù)權(quán)限 */ DATA_SCOPE_DEPT_AND_CHILD("2", "部門及子級(jí)數(shù)據(jù)權(quán)限"), /** * 本部門數(shù)據(jù)權(quán)限 */ DATA_SCOPE_DEPT("3", "部門數(shù)據(jù)權(quán)限"), /** * 本人數(shù)據(jù)權(quán)限 */ DATA_SCOPE_SELF("4", "本人數(shù)據(jù)權(quán)限"), ; /** * 對(duì)應(yīng)數(shù)據(jù)庫字典值 */ private final String dbValue; /** * 描述 */ private final String description; }
對(duì)于 UserDetails用戶信息我們做了擴(kuò)展。添加了關(guān)于數(shù)據(jù)權(quán)限的字段信息。
public class SxdhcloudUser extends User implements OAuth2AuthenticatedPrincipal { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * 擴(kuò)展屬性,方便存放oauth 上下文相關(guān)信息 */ 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; /** * 手機(jī)號(hào) */ @Getter private final String phone; /** * 角色數(shù)據(jù)權(quán)限類型,去重 */ @Getter private final Set<String> dataScopeTypes; /** * 數(shù)據(jù)權(quán)限部門ID集合,去重 */ @Getter private final Set<Long> dataScopeDeptIds; /** * 數(shù)據(jù)權(quán)限本人ID */ @Getter private final Long dataScopeCreateId; }
用戶在登錄認(rèn)證成功之后,獲取到角色數(shù)據(jù)權(quán)限的相關(guān)數(shù)據(jù),并放到 UserDetails用戶信息中。
1、自定義注解
自定義數(shù)據(jù)權(quán)限注解。
/** * 數(shù)據(jù)權(quán)限注解。 * 可以使用在類上,也可以使用在方法上。 * - 如果 Mapper類加上注解,表示 Mapper提供的方法以及自定義的方法都會(huì)被加上數(shù)據(jù)權(quán)限 * - 如果 Mapper類的方法加在上注解,表示該方法會(huì)被加上數(shù)據(jù)權(quán)限 * - 如果 Mapper類和其方法同時(shí)加上注解,優(yōu)先級(jí)為:【類上 > 方法上】 * - 如果不需要數(shù)據(jù)權(quán)限,可以不加注解,也可以使用 @DataScope(enabled = false) */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataScope { /** * 是否生效,默認(rèn)true-生效 */ boolean enabled() default true; /** * 表別名 */ String tableAlias() default ""; /** * 部門限制范圍的字段名稱 */ String deptScopeName() default "dept_id"; /** * 本人限制范圍的字段名稱 */ String oneselfScopeName() default "create_id"; }
2、自定義處理器
自定義處理器類并實(shí)現(xiàn) MultiDataPermissionHandler接口
,在 getSqlSegment()方法
中處理自定義數(shù)據(jù)權(quán)限邏輯。
注意: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ù)權(quán)限拼裝邏輯處理 * */ public class DataScopeHandler implements MultiDataPermissionHandler { /** * 獲取數(shù)據(jù)權(quán)限 SQL 片段。 * <p>舊的 {@link MultiDataPermissionHandler#getSqlSegment(Expression, String)} 方法第一個(gè)參數(shù)包含所有的 where 條件信息,如果 return 了 null 會(huì)覆蓋原有的 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 條件表達(dá)式,返回的條件表達(dá)式會(huì)拼接在原有的表達(dá)式后面(不會(huì)覆蓋原有的表達(dá)式) */ @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)先級(jí):【類上 > 方法上】 */ // 獲取 DataScope注解 DataScope dataScopeAnnotationClazz = mapperClazz.getAnnotation(DataScope.class); if (ObjectUtils.isNotEmpty(dataScopeAnnotationClazz) && dataScopeAnnotationClazz.enabled()) { return buildDataScopeByAnnotation(dataScopeAnnotationClazz); } // 獲取自身類中的所有方法,不包括繼承。與訪問權(quán)限無關(guān) 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ù)權(quán)限 * * @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ù)權(quán)限 * * @param tableAlias 表別名 * @param deptScopeName 部門限制范圍的字段名稱 * @param oneselfScopeName 本人限制范圍的字段名稱 * @param dataScopeDeptIds 數(shù)據(jù)權(quán)限部門ID集合,去重 * @param dataScopeCreateId 數(shù)據(jù)權(quán)限本人ID * @return */ private Expression buildDataScopeExpression(String tableAlias, String deptScopeName, String oneselfScopeName, Set<Long> dataScopeDeptIds, Long dataScopeCreateId) { /** * 構(gòu)造部門in表達(dá)式。 */ InExpression deptIdInExpression = null; if (CollectionUtils.isNotEmpty(dataScopeDeptIds)) { deptIdInExpression = new InExpression(); ExpressionList deptIds = new ExpressionList(dataScopeDeptIds.stream().map(LongValue::new).collect(Collectors.toList())); // 設(shè)置左邊的字段表達(dá)式,右邊設(shè)置值。 deptIdInExpression.setLeftExpression(buildColumn(tableAlias, deptScopeName)); deptIdInExpression.setRightExpression(new Parenthesis(deptIds)); } /** * 構(gòu)造本人eq表達(dá)式 */ 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; } /** * 構(gòu)建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、注冊(cè)數(shù)據(jù)權(quán)限攔截器
MybatisPlusConfig配置類中將自定義的處理器注冊(cè)到 DataPermissionInterceptor
中。并將攔截器加到MyBatis-Plus插件中。
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 1.添加數(shù)據(jù)權(quán)限插件 interceptor.addInnerInterceptor(new DataPermissionInterceptor(new DataScopeHandler())); // 2.添加分頁插件 PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(); // 設(shè)置數(shù)據(jù)庫方言類型 pageInterceptor.setDbType(DbType.MYSQL); // 下面配置根據(jù)需求自行設(shè)置 // 設(shè)置請(qǐng)求的頁面大于最大頁后操作,true調(diào)回到首頁,false繼續(xù)請(qǐng)求。默認(rèn)false pageInterceptor.setOverflow(false); // 單頁分頁條數(shù)限制,默認(rèn)無限制 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); }
代碼中的注釋寫的挺清楚,大家自行理解。
參考文章:
- 關(guān)于JSqlparser使用攻略(高效的SQL解析工具):http://www.dbjr.com.cn/article/267850.htm
到此這篇關(guān)于MyBatis-Plus數(shù)據(jù)權(quán)限插件的簡(jiǎn)單使用的文章就介紹到這了,更多相關(guān)MyBatis-Plus數(shù)據(jù)權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用Spring Social輕松搞定微信授權(quán)登錄的方法示例
這篇文章主要介紹了利用Spring Social輕松搞定微信授權(quán)登錄的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12詳解Spring Cloud Finchley版中Consul多實(shí)例注冊(cè)的問題處理
這篇文章主要介紹了詳解Spring Cloud Finchley版中Consul多實(shí)例注冊(cè)的問題處理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08Struts中使用validate()輸入校驗(yàn)方法詳解
這篇文章主要介紹了Struts中使用validate()輸入校驗(yàn)方法,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09Java自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏
在實(shí)際開發(fā)中經(jīng)常會(huì)遇到有一些信息不能全部展示用戶,需要隱藏(可以叫脫敏),所以本文為大家分享了利用自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏的示例代碼,需要的可以參考下2023-07-07Spring?Security實(shí)現(xiàn)分布式系統(tǒng)授權(quán)方案詳解
這篇文章主要介紹了Spring?Security實(shí)現(xiàn)分布式系統(tǒng)授權(quán),本節(jié)完成注冊(cè)中心的搭建,注冊(cè)中心采用Eureka,本文通過示例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02