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

