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

