拦截器
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版权协议,转载请附上原文出处链接和本声明。