背景
现在的项目负责人去年年底离职,导致前期规划的数据过滤功能一直没有去实现。现在项目马上进入试运行时期了,需要根据用户数据权限的配置对数据进行过滤处理。如果一个个手动去Mapper.xml文件中修改SQL工作量太大了,后面我考虑通过Mybatis对查询的SQL进行处理。
基础知识
Mybatis 拦截器介绍
Interceptor接口源码解析
java
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
方法 这个方法是核心,当拦截到调用时会执行。Invocation 对象包含了被拦截方法的所有信息,包括方法本身、参数、目标对象等。在这个方法中,你可以做任何预处理或后处理逻辑,然后通过调用 invocation.proceed() 来继续执行原方法,或者直接返回自定义的结果。plugin
方法 这个方法用于决定是否对某个对象应用拦截器。如果返回 target,则表示不进行拦截;如果返回一个新的对象,则表示将使用这个新对象替代原有的对象,通常是在这里返回一个代理对象。setProperties
方法 用于设置拦截器的属性,这些属性可以在 MyBatis 的配置文件中定义。
Signature 注解源码解析
java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
type
:表示目标对象的类型,method
:表示要拦截的目标方法的名字。args
:表示目标方法的参数类型列表。不同的 @Signature 注解可能有不同的参数类型列表,这取决于具体的方法签名。
代码实战
实现一个类似与PageHelper
的一个工具类,在本地线程变量中存储数据权限相关信息
java
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);
}
}
实现 Interceptor
接口对SQL进行增强处理
java
@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:{}, 数据权限替换后的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;
}
}
}
总结
以上代码只是示例,在实际生产中还需要考虑多表查询、SQL注入等相关问题。如果文章存在问题欢迎各位大佬指正。