一、场景介绍
在开发过程中很多时候我们需要根据某些条件去做数据权限,比如:A组织只能看见A组织及其下属组织的数据,B部门只能看见自己的数据、等等,此时如果每次都去自己写SQL进行校验就会显得代码非常臃肿,因为就产生了自己去定义一套全局公用的数据权限过滤方式。
二、实现思路
借助于Spring的拦截器或过滤器,当请求进入到Controller时,将该用户的数据权限信息存入数据权限上下文中,在MyBatis执行SQL之前将其动态拼接上去,MyBatis-Plus给我们提供了一个DataPermissionHandler接口用于做数据权限控制,其核心调用逻辑位于DataPermissionInterceptor中,因此我们自己需要定义一个DataPermissionHandler实现类将其注入到MybatisPlusInterceptor中
三、实现步骤
定义数据权限配置对象
@Data public class DataAuthConfig { private Map<String, Object> params; private DataAuthConfig() { } /** * 构建实例 * * @return com.minportal.platform.common.mybatis.core.DataPermissionContextHolder * @author Guo Shuai * @date 2022/7/25 * @since 1.0 **/ public static DataAuthConfig create() { DataAuthConfig config = new DataAuthConfig(); config.setParams(new ConcurrentHashMap<>(4)); return config; } /** * 添加参数 * * @param key sql字段名称 * @param value 值 * @return java.util.Map<java.lang.String, java.lang.Object> * @author Guo Shuai * @date 2022/7/25 * @since 1.0 **/ public DataAuthConfig withParam(String key, Object value) { //设置参数 params.put(key, value); //添加参数 return this; } }
定义数据权限上下文
public class DataAuthContextHolder { private static final ThreadLocal<DataAuthConfig> CONTEXT = new ThreadLocal<>(); /** * 获取配置 * * @return java.util.Map<java.lang.String, java.lang.Object> * @author Guo Shuai * @date 2022/7/25 * @since 1.0 **/ public static DataAuthConfig getContext() { if (Objects.isNull(CONTEXT.get())) { synchronized (DataAuthContextHolder.class) { //判断是否为空 if (Objects.isNull(CONTEXT.get())) { CONTEXT.set(DataAuthConfig.create()); } } } //返回 return CONTEXT.get(); } /** * 清空数据 * * @author Guo Shuai * @date 2022/7/25 * @since 1.0 **/ public static void clean() { CONTEXT.remove(); } }
实现DataPermissionHandler接口
/** * 全局数据权限处理器 * * @author Guo Shuai * @version 1.0 * @date 2022/7/25 */ public class GlobalDataAuthHandler implements DataPermissionHandler { @Override public Expression getSqlSegment(Expression where, String mappedStatementId) { //从数据权限上下文获取数据权限参数列表 DataAuthConfig config = DataAuthContextHolder.getContext(); //判断是否为空 if (config == null || CollUtil.isEmpty(config.getParams())) { return where; } else { return dataScopeFilter(where, config.getParams()); } } /** * 构建过滤条件 * * @param where 条件对象 * @param conditions 条件列表 * @return net.sf.jsqlparser.expression.Expression * @author Guo Shuai * @date 2022/7/25 * @since 1.0 **/ public static Expression dataScopeFilter(Expression where, Map<String, Object> conditions) { //定义条件 AtomicReference<Expression> whereAtomic = new AtomicReference<>(where); //循环构造条件 conditions.forEach((key, value) -> { //判断value的类型(集合特殊处理) if (value instanceof Collection) { Collection<?> collection = (Collection<?>) value; InExpression expression = new InExpression(); expression.setLeftExpression(new Column(key)); //获取条件 ItemsList itemsList = new ExpressionList(collection.stream().map(String::valueOf).map(StringValue::new).collect(Collectors.toList())); expression.setRightItemsList(itemsList); //拼接条件 whereAtomic.set(new AndExpression(whereAtomic.get(), expression)); } else { whereAtomic.set(new AndExpression(whereAtomic.get(), new EqualsTo(new Column(key), new StringValue(String.valueOf(value))))); } }); return whereAtomic.get(); } }
将实现的拦截器注入到MybatisPlusInterceptor中
@Configuration public class MybatisPlusConfig { /** * 分页插件和数据权限插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //数据权限 interceptor.addInnerInterceptor(new DataPermissionInterceptor(new GlobalDataAuthHandler())); return interceptor; } }
定义Spring拦截器处理数据权限上下文参数
@Component public class DataAuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //设置数据权限信息 //例A部门只能看自己的数据及其子级部门A1的数据 DataAuthContextHolder.getContext().withParam("organId", Lists.newArrayList("A", "A1")); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { DataAuthContextHolder.clean(); } }
四、总结
对于不是特别复杂的SQL构造来说应该可以实现大部分的数据权限过滤,这里数据类型只区分了集合和其他类型,若自己的业务有其他要求的话可以对其进行改造拓展。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。