Mybatis攔截器如何實現(xiàn)數(shù)據(jù)權限過濾
背景
現(xiàn)在的項目負責人去年年底離職,導致前期規(guī)劃的數(shù)據(jù)過濾功能一直沒有去實現(xiàn)。
現(xiàn)在項目馬上進入試運行時期了,需要根據(jù)用戶數(shù)據(jù)權限的配置對數(shù)據(jù)進行過濾處理。
如果一個個手動去Mapper.xml文件中修改SQL工作量太大了,后面我考慮通過Mybatis對查詢的SQL進行處理。
基礎知識
Mybatis 攔截器介紹
Interceptor接口源碼解析
package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { } }
intercept
方法
這個方法是核心,當攔截到調(diào)用時會執(zhí)行。Invocation 對象包含了被攔截方法的所有信息,包括方法本身、參數(shù)、目標對象等。在這個方法中,你可以做任何預處理或后處理邏輯,然后通過調(diào)用 invocation.proceed() 來繼續(xù)執(zhí)行原方法,或者直接返回自定義的結(jié)果。plugin
方法
這個方法用于決定是否對某個對象應用攔截器。如果返回 target,則表示不進行攔截;如果返回一個新的對象,則表示將使用這個新對象替代原有的對象,通常是在這里返回一個代理對象。setProperties
方法
用于設置攔截器的屬性,這些屬性可以在 MyBatis 的配置文件中定義。
Signature 注解源碼解析
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature { Class<?> type(); String method(); Class<?>[] args(); }
type
:表示目標對象的類型,method
:表示要攔截的目標方法的名字。args
:表示目標方法的參數(shù)類型列表。不同的 @Signature 注解可能有不同的參數(shù)類型列表,這取決于具體的方法簽名。
代碼實戰(zhàn)
實現(xiàn)一個類似與PageHelper
的一個工具類,在本地線程變量中存儲數(shù)據(jù)權限相關信息
public class DataAccessMethod { private static final ThreadLocal<DataAccessType[]> ACCESS_LOCAL = new ThreadLocal<>(); public DataAccessMethod() { } public static void setLocalAccess(DataAccessType... accessType) { ACCESS_LOCAL.set(accessType); } public static DataAccessType[] getLocalAccess() { return ACCESS_LOCAL.get(); } public static void clearLocalAccess() { ACCESS_LOCAL.remove(); } public static void accessData(DataAccessType... accessType) { setLocalAccess(accessType); } }
實現(xiàn) Interceptor
接口對SQL進行增強處理
@Intercepts({@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} )}) @Slf4j @Component public class DataAccessFilterInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { if (skip()) { return invocation.proceed(); } MappedStatement statement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = statement.getBoundSql(parameter); String originalSql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); String sql = addTenantCondition(originalSql, "1", "222"); log.info("原SQL:{}, 數(shù)據(jù)權限替換后的SQL:{}", originalSql, sql); BoundSql newBoundSql = new BoundSql(statement.getConfiguration(), sql, boundSql.getParameterMappings(), parameterObject); MappedStatement newStatement = copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql)); invocation.getArgs()[0] = newStatement; return invocation.proceed(); } /** * 判斷是否跳過 * * @return 是否跳過 */ private boolean skip() { DataAccessType[] localAccess = DataAccessMethod.getLocalAccess(); return localAccess == null; } private String addTenantCondition(String originalSql, String depId, String alias) { String field = "id"; if (StringUtils.hasText(alias)) { field = alias + "." + field; } StringBuilder sb = new StringBuilder(originalSql.toLowerCase()); int index = sb.indexOf("where"); sb = new StringBuilder(originalSql); if (index < 0) { sb.append(" where ").append(field).append(" = ").append(depId); } else { sb.insert(index + 5, " " + field + " = " + depId + " and "); } return sb.toString(); } private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.fetchSize(ms.getFetchSize()); builder.statementType(ms.getStatementType()); builder.keyGenerator(ms.getKeyGenerator()); builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.cache(ms.getCache()); builder.useCache(ms.isUseCache()); return builder.build(); } public static class BoundSqlSqlSource implements SqlSource { private final BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } @Override public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } }
總結(jié)
以上代碼只是示例,在實際生產(chǎn)中還需要考慮多表查詢、SQL注入等相關問題。
這些僅為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
深入理解Java中的volatile關鍵字(總結(jié)篇)
volatile這個關鍵字,不僅僅在Java語言中有,在很多語言中都有的,而且其用法和語義也都是不盡相同的。這篇文章主要介紹了Java中的volatile關鍵字,需要的朋友可以參考下2018-10-10使用原生JDBC動態(tài)解析并獲取表格列名和數(shù)據(jù)的方法
這篇文章主要介紹了使用原生JDBC動態(tài)解析并獲取表格列名和數(shù)據(jù),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08Java基礎知識精通循環(huán)結(jié)構(gòu)與break及continue
循環(huán)結(jié)構(gòu)是指在程序中需要反復執(zhí)行某個功能而設置的一種程序結(jié)構(gòu)。它由循環(huán)體中的條件,判斷繼續(xù)執(zhí)行某個功能還是退出循環(huán),選擇結(jié)構(gòu)用于判斷給定的條件,根據(jù)判斷的結(jié)果判斷某些條件,根據(jù)判斷的結(jié)果來控制程序的流程2022-04-04