有一个@AuthData,用它能够在MyBatis查询的时候增加一些权限
mybatis可以被拦截的类型有四种:
- Excutor(拦截执行器的方法)
- ParameterHandler(拦截参数的处理),基本
- StatementHandler(拦截sql语句构建的处理)
- ResultHandler(拦截结果集的处理)
详情看下面的类的具体实现
// (1) 数据权限拦截器的实现类
AuthDataIntercepter
// (2) 配合该注解实现,用在controller层的方法上
@AuthData
// (3) 在写SQL语句时可以获取到注入的参数数据
<if test="auth != null and auth.authType == 2">
WHERE org_id IN
<foreach collection="auth.departIds" item="did" open="(" separator="," close=")">
#{did}
</foreach>
</if>
// (4) 在启动类种注册拦截器
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ModuleFinanceServiceApplication.class, args);
SpringUtils.setApplicationContext(context);
//这种方式添加mybatis拦截器保证在pageHelper前执行
context.getBean(SqlSessionFactory.class).getConfiguration().addInterceptor(new AuthDataIntercepter());
context.getBean(SqlSessionFactory.class).getConfiguration().addInterceptor(new SignComAuthDataIntercept());
}
业务开发只需要在写SQL时从指定的变量中获取权限的数据,并将其拼接到SQL语句中即可。
拦截器实现代码
前面的注解@Intercepts的两个参数,好像也没人介绍为什么要这样写。
基本的意思就是,执行具体的update或者query的SQL的时候会触发这些逻辑。
第一个接口AuthDataIntercepter
@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}),})
public class AuthDataIntercepter implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(AuthDataIntercepter.class);
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Object intercept(Invocation invocation) throws Throwable {
AuthDataBo authDataBo = AuthDataUtils.getAuthData();
Object[] args = invocation.getArgs();
Object paramObject = args[1];
if (Objects.isNull(authDataBo)) {
if (paramObject instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap paramObject1 = (MapperMethod.ParamMap) paramObject;
paramObject1.put("auth", authDataBo);
args[1] = paramObject1;
}
return invocation.proceed();
}
if (paramObject instanceof Map) {
if (MapUtils.isNotEmpty((Map<String, Object>) paramObject)) {
((Map<Object, Object>) paramObject).put("auth", authDataBo);
}
} else if (paramObject == null) {
// 如果mapper参数列表为空而又需要auth
HashMap<String, Object> params = new HashMap<>();
params.put("auth", authDataBo);
args[1] = params;
} else {
Class<?> clazz = paramObject.getClass();
if (logger.isDebugEnabled()) {
logger.debug("mybatis参数类型不为Map:{}", clazz);
}
Map<String, Object> beamMap = getMethod(args);
beamMap.put("auth", authDataBo);
args[1] = beamMap;
}
if (logger.isDebugEnabled()) {
logger.info("mybatis添加数据权限后的参数:{}", JsonUtils.toJson(args[1]));
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//do Nothing
}
public Map<String, Object> getMethod(Object[] args) throws Throwable {
MappedStatement arg = (MappedStatement) args[0];
String string = arg.getId();
Object paramObject = args[1];
String method = string.substring(string.lastIndexOf(".") + 1);
String clazz = string.substring(0, string.lastIndexOf("."));
Class<?> aClass = Class.forName(clazz);
Method[] methods = aClass.getMethods();
Map<String, Object> beamMap = ObjectUtil.objectToMap(paramObject);
for (Method method1 : methods) {
if (!method1.getName().equals(method)) {
continue;
}
//if (method1.isAnnotationPresent(Param.class)) continue;
Parameter[] ps = method1.getParameters();
for (Parameter p : ps) {
String name = p.getName();
beamMap.put(name, paramObject);
}
args[1] = beamMap;
}
return beamMap;
}
}
另一个拦截器的代码是这样的
另一个接口SignComAuthDataIntercept
/**
* 签约主体划分数据权限Mybatis拦截器
*
*/
@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}),})
public class SignComAuthDataIntercept implements Interceptor {
/**
* 需要注入参数的key值
*/
private static final String SIGN_COM_KEY = "signComAuth";
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 从线程本地变量中获取数据权限对象
SignComVo signComAuthVo = SignComAuthDataAspect.getSignComAuthVo();
Object[] args = invocation.getArgs(); // 这里获取的是@Signature中args
Object paramObject = args[1]; // 数据结构为:MapperMethod.ParamMap
if (Objects.isNull(signComAuthVo)) {
if (MapperMethod.ParamMap.class.isInstance(paramObject)) {
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) paramObject;
// 对map追加一个元素然后放回 args[1]
paramMap.put(SIGN_COM_KEY, null);
args[1] = paramMap;
}
return invocation.proceed();
}
if (paramObject instanceof Map) {
if (MapUtils.isNotEmpty((Map<String, Object>) paramObject)) {
((Map<String, Object>) paramObject).put(SIGN_COM_KEY, signComAuthVo);
}
} else if (paramObject == null) {
// 如果mapper参数列表为空而又需要auth
HashMap<String, Object> params = new HashMap<>();
params.put(SIGN_COM_KEY, signComAuthVo);
args[1] = params;
} else {
Map<String, Object> beamMap = ObjectUtil.objectToMap(paramObject);
beamMap.put(SIGN_COM_KEY, signComAuthVo);
args[1] = beamMap;
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// do nothing
}
}
总结
以前设计权限系统的时候,所有的权限都是在代码中实现的,这样其实是会增加代码量。
这是一个很不错的思路