MyBatis-Plus结合Spring Boot实现数据权限

一、场景介绍

在开发过程中很多时候我们需要根据某些条件去做数据权限,比如:A组织只能看见A组织及其下属组织的数据,B部门只能看见自己的数据、等等,此时如果每次都去自己写SQL进行校验就会显得代码非常臃肿,因为就产生了自己去定义一套全局公用的数据权限过滤方式。

二、实现思路

借助于Spring的拦截器或过滤器,当请求进入到Controller时,将该用户的数据权限信息存入数据权限上下文中,在MyBatis执行SQL之前将其动态拼接上去,MyBatis-Plus给我们提供了一个DataPermissionHandler接口用于做数据权限控制,其核心调用逻辑位于DataPermissionInterceptor中,因此我们自己需要定义一个DataPermissionHandler实现类将其注入到MybatisPlusInterceptor中

三、实现步骤

  1. 定义数据权限配置对象

    @Data
    public class DataAuthConfig {
    
     private Map 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
      * @author Guo Shuai
      * @date 2022/7/25
      * @since 1.0
      **/
     public DataAuthConfig withParam(String key, Object value) {
         //设置参数
         params.put(key, value);
         //添加参数
         return this;
     }
    }
  2. 定义数据权限上下文

    public class DataAuthContextHolder {
     private static final ThreadLocal CONTEXT = new ThreadLocal<>();
    
     /**
      * 获取配置
      *
      * @return java.util.Map
      * @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();
     }
    }
  3. 实现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 conditions) {
         //定义条件
         AtomicReference 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();
     }
    }
  4. 将实现的拦截器注入到MybatisPlusInterceptor中

    @Configuration
    public class MybatisPlusConfig {
    
     /**
      * 分页插件和数据权限插件
      */
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
         //数据权限
         interceptor.addInnerInterceptor(new DataPermissionInterceptor(new GlobalDataAuthHandler()));
    
         return interceptor;
     }
    }
  5. 定义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构造来说应该可以实现大部分的数据权限过滤,这里数据类型只区分了集合和其他类型,若自己的业务有其他要求的话可以对其进行改造拓展。

你可能感兴趣的:(MyBatis-Plus结合Spring Boot实现数据权限)