拦截器
import cn.hutool.core.util.ReflectUtil;
import com.manager.enums.PositionStatusEnum;
import com.manager.util.LoginUserUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
/**
* mybatis 拦截顺序Executor -> StatementHandler->ParameterHandler->ResultSetHandler
* 要在分页插件之前完成sql语句的修改 应拦截Executor
* @author
*/
@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 DataAuthInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
//只拦截加注解的方法
DataAuth dataAuth = this.getDataScope(ms);
if (Objects.isNull(dataAuth)) {
return invocation.proceed();
}
//只针对投资经理岗位做数据权限,如果多岗位则不控制
List<Integer> positionStatus = LoginUserUtil.getPositionStatus();
if (CollectionUtils.isEmpty(positionStatus) || !positionStatus.contains(PositionStatusEnum.PRODUCT_MANAGER.getCodeValue())
|| (positionStatus.contains(PositionStatusEnum.PRODUCT_MANAGER.getCodeValue()) && positionStatus.size() > 1)){
return invocation.proceed();
}
if (StringUtils.isBlank(LoginUserUtil.getProductIds())){
return invocation.proceed();
}
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if(args.length == 4){
//4 个参数时
//---------------这里就是拷贝的CachingExecutor或者BaseExecutor的代码
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
//-------------本来一般情况下是执行invocation.proceed(),继续执行拦截方法,但这里直接执行这个方法,相
//-------------当于替换了CachingExecutor或者BaseExecutor原来的实现。
String originalSql = boundSql.getSql();
originalSql = this.modifyOrgSql(originalSql);
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), originalSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
//解决mybatis分页foreach 参数失效问题:There is no getter for property named ‘__frch_ _0’ in 'class
//得知新版需要用到additionalParameters参数,也就是说不论哪个版本,value都是从metaParameters获得,
//在老版本中使用metaParameters新版中使用了additionalParameters判断是否有值。而分页插件没有注入该参数,用ref加入该参数(注释的代码),问题解决
/* if (ReflectUtil.getFieldValue(boundSql,"additionalParameters") != null){
Object metaParameters = ReflectUtil.getFieldValue(boundSql, "additionalParameters");
ReflectUtil.setFieldValue(newBoundSql,"additionalParameters",metaParameters);
}*/
// todo ReflectUtil方法在有物理分页的情况下foreach #{item}可以正常取值,但是在没有物理分页的情况下foreach #{item}取值为null,故修改为以下方式
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
} return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, newBoundSql);
}
/**
* 通过反射获取mapper方法是否加了数据拦截注解
*/
private DataAuth getDataScope(MappedStatement mappedStatement) throws ClassNotFoundException {
DataAuth dataAuth = null;
String id = mappedStatement.getId();
String className = id.substring(0, id.lastIndexOf("."));
String methodName = id.substring(id.lastIndexOf(".") + 1);
final Class<?> cls = Class.forName(className);
final Method[] methods = cls.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(DataAuth.class)) {
dataAuth = method.getAnnotation(DataAuth.class);
break;
}
}
return dataAuth;
}
/**
* 根据权限点拼装对应sql
* @return 拼装后的sql
*/
private String modifyOrgSql(String originalSql){
return "select * from (" + originalSql + ") temp_data_scope where temp_data_scope.productId in (".concat(LoginUserUtil.getProductIds()).concat(")");
}}
LoginUserUtil是一个线程对象,本次采用的方案是将所有的权限数据在验证token的拦截器处理好,并放入线程对象中的属性productIds,在该自定义拦截器中只需要去来直接用即可。具体的权限逻辑根据自己业务实现
自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 产品权限注解
* @author
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataAuth {
}配置自定义拦截器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.manager.productrole.DataAuthInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.manager.dao","com.manager.module.**.dao"})
public class MybatisPlusConfig {
/**
* 分页
**/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return configuration -> {
configuration.addInterceptor(new com.github.pagehelper.PageInterceptor());
configuration.setMapUnderscoreToCamelCase(false);
};
}
//由于拦截器加载顺序1>2>3执行顺序就会变成3>2>1,所以自定义拦截器应当放在分页拦截器之后加载
@Bean
public DataAuthInterceptor dataAuthInterceptor(){
return new DataAuthInterceptor();
}自定义注解的使用
自定义注解使用在mapper的方法上
public interface ProductDocumentMapper extends BaseMapper<ProductDocument> {
@DataAuth
List<ProductDocumentListRsp> getList(ProductDocumentListReq productDocumentListReq);}
总结:期间遇到这个报错:There is no getter for property named ‘__frch_ _0’ in 'class
解决方案:这个报错的问题是由于mybatis foreach标签使用报错
#{item}这个写法本身没问题,由于分页插件的问题
可以改成:${item} //有sql注入的风险,不推荐
#{list[${index}]}
最终解决方案:
修改代码费时费力,还容易出错,最终解决方案
if (ReflectUtil.getFieldValue(boundSql,"additionalParameters") != null){
Object metaParameters = ReflectUtil.getFieldValue(boundSql, "additionalParameters");
ReflectUtil.setFieldValue(newBoundSql,"additionalParameters",metaParameters);
}参考:
如何在PageHelper之前拦截sql_a361117441的博客-CSDN博客_sql拦截
There is no getter for property named ‘__frch_item_0‘ in ‘class_bbq烤鸡的博客-CSDN博客
MyBatis 物理分页foreach 参数失效(list值传不进<foreach>标签为null)_jbgtwang的博客-CSDN博客_foreach mybatis null
版权声明:本文为qq_38377774原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。