Mybatis攔截器如何實(shí)現(xiàn)數(shù)據(jù)權(quán)限過濾
背景
現(xiàn)在的項(xiàng)目負(fù)責(zé)人去年年底離職,導(dǎo)致前期規(guī)劃的數(shù)據(jù)過濾功能一直沒有去實(shí)現(xiàn)。
現(xiàn)在項(xiàng)目馬上進(jìn)入試運(yùn)行時(shí)期了,需要根據(jù)用戶數(shù)據(jù)權(quán)限的配置對(duì)數(shù)據(jù)進(jìn)行過濾處理。
如果一個(gè)個(gè)手動(dòng)去Mapper.xml文件中修改SQL工作量太大了,后面我考慮通過Mybatis對(duì)查詢的SQL進(jìn)行處理。
基礎(chǔ)知識(shí)
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
方法
這個(gè)方法是核心,當(dāng)攔截到調(diào)用時(shí)會(huì)執(zhí)行。Invocation 對(duì)象包含了被攔截方法的所有信息,包括方法本身、參數(shù)、目標(biāo)對(duì)象等。在這個(gè)方法中,你可以做任何預(yù)處理或后處理邏輯,然后通過調(diào)用 invocation.proceed() 來繼續(xù)執(zhí)行原方法,或者直接返回自定義的結(jié)果。plugin
方法
這個(gè)方法用于決定是否對(duì)某個(gè)對(duì)象應(yīng)用攔截器。如果返回 target,則表示不進(jìn)行攔截;如果返回一個(gè)新的對(duì)象,則表示將使用這個(gè)新對(duì)象替代原有的對(duì)象,通常是在這里返回一個(gè)代理對(duì)象。setProperties
方法
用于設(shè)置攔截器的屬性,這些屬性可以在 MyBatis 的配置文件中定義。
Signature 注解源碼解析
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature { Class<?> type(); String method(); Class<?>[] args(); }
type
:表示目標(biāo)對(duì)象的類型,method
:表示要攔截的目標(biāo)方法的名字。args
:表示目標(biāo)方法的參數(shù)類型列表。不同的 @Signature 注解可能有不同的參數(shù)類型列表,這取決于具體的方法簽名。
代碼實(shí)戰(zhàn)
實(shí)現(xiàn)一個(gè)類似與PageHelper
的一個(gè)工具類,在本地線程變量中存儲(chǔ)數(shù)據(jù)權(quán)限相關(guān)信息
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); } }
實(shí)現(xiàn) Interceptor
接口對(duì)SQL進(jìn)行增強(qiáng)處理
@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ù)權(quán)限替換后的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é)
以上代碼只是示例,在實(shí)際生產(chǎn)中還需要考慮多表查詢、SQL注入等相關(guān)問題。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
深入理解Java中的volatile關(guān)鍵字(總結(jié)篇)
volatile這個(gè)關(guān)鍵字,不僅僅在Java語言中有,在很多語言中都有的,而且其用法和語義也都是不盡相同的。這篇文章主要介紹了Java中的volatile關(guān)鍵字,需要的朋友可以參考下2018-10-10使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù)的方法
這篇文章主要介紹了使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08Json轉(zhuǎn)化為Java對(duì)象的實(shí)例詳解
這篇文章主要介紹了Json轉(zhuǎn)化為Java對(duì)象的實(shí)例詳解的相關(guān)資料,前后端數(shù)據(jù)交互的情況經(jīng)常會(huì)遇到Json串與java 對(duì)象的相互轉(zhuǎn)換方便操作,需要的朋友可以參考下2017-08-08Java基礎(chǔ)知識(shí)精通循環(huán)結(jié)構(gòu)與break及continue
循環(huán)結(jié)構(gòu)是指在程序中需要反復(fù)執(zhí)行某個(gè)功能而設(shè)置的一種程序結(jié)構(gòu)。它由循環(huán)體中的條件,判斷繼續(xù)執(zhí)行某個(gè)功能還是退出循環(huán),選擇結(jié)構(gòu)用于判斷給定的條件,根據(jù)判斷的結(jié)果判斷某些條件,根據(jù)判斷的結(jié)果來控制程序的流程2022-04-04JDBC連接Mysql的5種方式實(shí)例總結(jié)
JDBC是Java DataBase Connectivity技術(shù)的簡(jiǎn)稱,是一種可用于執(zhí)行 SQL語句的Java API,下面這篇文章主要給大家介紹了關(guān)于JDBC連接Mysql的5種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04