mybatis plus數(shù)據(jù)權(quán)限插件在項(xiàng)目中的使用方式
前言
平時(shí)開(kāi)發(fā)中遇到根據(jù)當(dāng)前用戶的角色,只能查看數(shù)據(jù)權(quán)限范圍的數(shù)據(jù)需求。實(shí)現(xiàn)方案有兩種,一是在開(kāi)發(fā)初期就做好判斷,但如果這個(gè)需求是中途加的,或不希望每個(gè)接口都加一遍,就可以方案二加攔截器的方式。在mybatis執(zhí)行sql前修改語(yǔ)句,限定where范圍。
當(dāng)然攔截器生效后是全局性的,如何保證只對(duì)需要的接口進(jìn)行攔截和轉(zhuǎn)化,就可以應(yīng)用注解進(jìn)行識(shí)別
因此具體需要哪些步驟就明確了:
- 創(chuàng)建注解類
- 創(chuàng)建處理類,獲取數(shù)據(jù)權(quán)限 SQL 片段,設(shè)置條件
- 將攔截器加到MyBatis-Plus插件中
數(shù)據(jù)權(quán)限插件
DataPermissionInterceptor
插件原理和租戶插件類似動(dòng)態(tài)攔截執(zhí)行 SQl 然后拼接權(quán)限部分 SQL片段 , 該插件一直是免費(fèi)開(kāi)源的,企業(yè)高級(jí)特性-數(shù)據(jù)范圍功能也是基于該原理實(shí)現(xiàn),只不過(guò)添加了注解支持。
使用DataPermissionHandler
/**
* 數(shù)據(jù)權(quán)限處理器
*
* @author hubin
* @since 3.4.1 +
*/
public interface DataPermissionHandler {
/**
* 獲取數(shù)據(jù)權(quán)限 SQL 片段
*
* @param where 待執(zhí)行 SQL Where 條件表達(dá)式
* @param mappedStatementId Mybatis MappedStatement Id 根據(jù)該參數(shù)可以判斷具體執(zhí)行方法
* @return JSqlParser 條件表達(dá)式
*/
Expression getSqlSegment(Expression where, String mappedStatementId);
}
先定義一個(gè)注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 表前綴
*/
String tableAlis() default "";
/**
* 機(jī)構(gòu)前綴
*/
String orgAlis() default "org_id";
/**
* 是否生效
*/
boolean enabled() default true;
}
新建一個(gè)OrgScopeHandler,通過(guò)實(shí)現(xiàn)以上接口:
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
DataScope annotation = method.getAnnotation(DataScope.class);
if (ObjectUtils.isNotEmpty(annotation)
&& (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
if (annotation.enabled()) {
return dataScopeFilter(annotation, where);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return where;
}
在項(xiàng)目中的MybatisPlusConfig中加上自定義的OrgScopeHandler:
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 數(shù)據(jù)權(quán)限
interceptor.addInnerInterceptor(new DataPermissionInterceptor(new OrgScopeHandler()));
getSqlSegment中的實(shí)現(xiàn)是遍歷Mybatis MappedStatement類的所有方法,對(duì)每一個(gè)方法執(zhí)行以下操作:
獲取該方法上的 DataScope 注解。
檢查注解是否存在且不為空(ObjectUtils.isNotEmpty(annotation))。
檢查方法名是否與之前獲取的 methodName 相同,或者是方法名后加上 _COUNT 后綴的情況。
如果以上條件都滿足,再檢查 DataScope 注解的 enabled() 屬性是否為 true。
如果找到了匹配的方法并且注解啟用,那么調(diào)用 dataScopeFilter 方法,傳入該方法上的 DataScope 注解和 where 參數(shù),進(jìn)行數(shù)據(jù)范圍過(guò)濾。
自定義的dataScopeFilter方法
/**
* 根據(jù)注解中的內(nèi)容構(gòu)造 拼接sql,用于數(shù)據(jù)權(quán)限過(guò)濾
*
* @param dataPermission 數(shù)據(jù)權(quán)限注解,用于指定表和組織別名等信息
* @param where 原始sql的where條件表達(dá)式
* @return 返回拼接后的sql表達(dá)式,添加了數(shù)據(jù)權(quán)限的過(guò)濾條件
*/
private Expression dataScopeFilter(DataScope dataPermission, Expression where) {
// 獲取當(dāng)前登錄的用戶
NutUser user = SecurityUtil.getNutUser();
String tableAlias = dataPermission.tableAlis(); // 表別名
String orgAlias = dataPermission.orgAlis(); // 組織別名
// 構(gòu)造數(shù)據(jù)過(guò)濾的in表達(dá)式
InExpression inExpression = new InExpression();
// 設(shè)置左邊的字段表達(dá)式,根據(jù)表和組織別名構(gòu)建
inExpression.setLeftExpression(buildColumn(tableAlias, orgAlias));
// 使用JSQLParser解析組織ID的查詢語(yǔ)句
CCJSqlParserManager parserManager = new CCJSqlParserManager();
Statement statement;
try {
// 構(gòu)造查詢當(dāng)前用戶所屬組織及其子組織ID的sql語(yǔ)句
StringReader statementReader = new StringReader(
"select id from sys_org where path like '%" + user.getOrgId() + "%'");
statement = parserManager.parse(statementReader);
} catch (JSQLParserException e) {
// 處理解析異常
throw new RuntimeException(e);
}
Select selectStatement = (Select) statement;
SubSelect subSelect = new SubSelect();
subSelect.setSelectBody(selectStatement.getSelectBody()); // 設(shè)置子查詢的查詢體
inExpression.setRightExpression(subSelect); // 設(shè)置右邊的子查詢表達(dá)式
// 如果原有where條件不為空,則將數(shù)據(jù)權(quán)限過(guò)濾條件和原有條件用and連接
return ObjectUtils.isNotEmpty(where) ? new AndExpression(where, new Parenthesis(inExpression)) : inExpression;
}
buildColumn方法:
/**
* 構(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);
}
使用:
/**
* 分頁(yè)查詢
*
* @param page page
* @param query query
*/
@DataScope(tableAlis = "item")
Page<InfoBO> selectPageQuery(Page page, @Param("query") HandleQuery query);
結(jié)果是實(shí)際的sql上將拼接上:
and (org_id IN (SELECT id FROM sys_org WHERE path LIKE concat('%', 1, '%')))CCJSqlParserManager類解析
CCJSqlParserManager 類是 JSqlParser 庫(kù)中的一個(gè)關(guān)鍵類,它主要用于管理和執(zhí)行 SQL 語(yǔ)句的解析操作。
JSqlParser 是一個(gè) Java 庫(kù),用于解析 SQL 語(yǔ)言,并將其轉(zhuǎn)化為可遍歷、操作和修改的 Java 對(duì)象模型。
CCJSqlParserManager 主要功能如下:
- 初始化解析器資源:該類負(fù)責(zé)初始化和管理 JSqlParser 解析器所需的資源。
- 解析 SQL 語(yǔ)句:它可以接受字符串形式的 SQL 查詢,并通過(guò) parse() 方法將其轉(zhuǎn)換成 Statement 對(duì)象,這是一個(gè)抽象類,代表了 SQL 語(yǔ)句的不同類型(如 Select, Update, Delete, Insert 等)。
- 處理并發(fā)請(qǐng)求:CCJSqlParserManager 可能會(huì)實(shí)現(xiàn)資源池或其他機(jī)制來(lái)更有效地處理多個(gè)并發(fā)的 SQL 解析請(qǐng)求。
例如,在上述代碼片段中:
CCJSqlParserManager parserManager = new CCJSqlParserManager();
Statement statement;
try {
StringReader statementReader = new StringReader("select id from sys_org where path like '%" + user.getOrgId() + "%'");
statement = parserManager.parse(statementReader);
} catch (JSQLParserException e) {
throw new RuntimeException(e);
}
這段代碼就是創(chuàng)建了一個(gè) CCJSqlParserManager 實(shí)例,然后使用它來(lái)解析一個(gè) SQL 語(yǔ)句,這個(gè) SQL 語(yǔ)句是為了查詢當(dāng)前用戶及其子組織的 ID。
通過(guò)解析得到的 statement 對(duì)象可以進(jìn)一步被分析或修改,以便進(jìn)行動(dòng)態(tài) SQL 生成、權(quán)限檢查、SQL 優(yōu)化等各種操作。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟記錄
這篇文章主要給大家介紹了關(guān)于IDEA2020.2.3中創(chuàng)建JavaWeb工程的完整步驟,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
java創(chuàng)建jar包并被項(xiàng)目引用步驟詳解
這篇文章主要介紹了java創(chuàng)建jar包并被項(xiàng)目引用步驟詳解,jar包實(shí)現(xiàn)了特定功能的,java字節(jié)碼文件的壓縮包,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-07-07
SpringCloud Gateway使用redis實(shí)現(xiàn)動(dòng)態(tài)路由的方法
這篇文章主要介紹了SpringCloud Gateway使用redis實(shí)現(xiàn)動(dòng)態(tài)路由的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Spring IoC容器常見(jiàn)獲取Bean的方式匯總示例解析
這篇文章主要為大家介紹了Spring IoC容器常見(jiàn)獲取Bean的方式匯總示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java設(shè)計(jì)模式之模版方法模式簡(jiǎn)介
這篇文章主要介紹了Java設(shè)計(jì)模式之模版方法模式,需要的朋友可以參考下2014-07-07
Java輕松實(shí)現(xiàn)在Word中插入頁(yè)眉頁(yè)腳的實(shí)用指南
在現(xiàn)代企業(yè)應(yīng)用中,Java 開(kāi)發(fā)者經(jīng)常需要處理各種文檔操作,本文將深入探討如何利用功能強(qiáng)大的 Spire.Doc for Java 庫(kù)輕松實(shí)現(xiàn)各種頁(yè)眉頁(yè)腳的插入需求,需要的小伙伴可以了解下2025-09-09

