若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析

目录

  • 一、DataScopeAspect
  • 使用场景
  • 二、ew.customSqlSegment
      • ${ew.customSqlSegment}
        • build:
        • this.normal : queryWrapper where 条件不为空的时候,才有normal
        • get
        • 第二次 进来add(), 已经拼接完 ew.customSqlSegment 了, 因为@DataPermission 注解进来的 动态拼接
  • Mybatis-plus 在自定义的sql语句中 调用QueryWrapper 的查询条件
    • 需求
    • 解决
    • 分析
      • 注意这里,如果没有where 条件,不拼接 customSqlSegment
    • 扩展
    • end 查看源码中:
        • 是根据所有的get方法,经过处理
        • 保存到:getMethods中

使用方法:

ew是mapper方法里的@Param(Constants.WRAPPER) Wrapper queryWrapper对象

首先判断ew.emptyOfWhere是否存在where条件,有的话再拼接上去,ew.customSqlSegment是WHERE + sql语句
没有where的时候加上 == false

使用${ew.sqlSegment} 如果是连表查询且查询条件是连表的字段则需在service层拼接查询条件时字段前指定别名

sqlSegment:


    ${ew.sqlSegment}


customSqlSegment:

${ew.customSqlSegment} 则省略 

eg: select * from a ${ew.sqlSegment} 

select * from a ${ew.customSqlSegment}

注:在对应的dao方法中 一样要指定 @Param(Constants.WRAPPER) Wrapper ew,其中Constants.WRAPPER="ew"源码中可以看到
————————————————


一、DataScopeAspect

从这里可以看出:表sys_role_dept 的用途,之前一起不清楚 role_dept关联表的含义。
自定义数据权限时(DATA_SCOPE_CUSTOM),通过角色 可以管理哪几个部门,来实现的。

@Aspect
@Component
public class DataScopeAspect
{
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
    {
        // 获取当前的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNotNull(loginUser))
        {
            SysUser currentUser = loginUser.getUser();
            // 如果是超级管理员,则不过滤数据
            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
            {
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     *
     * @param joinPoint 切点
     * @param user 用户
     * @param userAlias 别名
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
    {
        StringBuilder sqlString = new StringBuilder();

        for (SysRole role : user.getRoles())
        {
            String dataScope = role.getDataScope();
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                // 自定义数据权限,通过 role_dept, roleid可以查看哪些部门deptid的数据
                // 关于 OR  关键字,大胆猜测是考虑 有多个角色时(因为是for),用OR 连接。当然第一个OR 肯定得去掉,(看后面验证) 
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getRoleId()));
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            }
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StringUtils.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(" OR 1=0 ");
                }
            }
        }

        if (StringUtils.isNotBlank(sqlString.toString()))
        {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
                BaseEntity baseEntity = (BaseEntity) params;
                // 验证了前面的猜测:一个user多个role时,会拼接多个OR 部门的数据, 猜测第一个OR ,按sql 逻辑,理应去掉。 
                // 这里substring(4) 去掉第一个" OR "
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }
    上面拼接后: and dept_id in (),  放到 baseEntity的 params里 。

    /**
     * 拼接权限sql前先清空params.dataScope参数防止注入
     */
    private void clearDataScope(final JoinPoint joinPoint)
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }
}
-----------------------------------

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第1张图片

使用场景

二、ew.customSqlSegment

${ew.customSqlSegment}

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第2张图片

build:

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第3张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第4张图片

this.normal : queryWrapper where 条件不为空的时候,才有normal

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第5张图片

get

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第6张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第7张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第8张图片

第二次 进来add(), 已经拼接完 ew.customSqlSegment 了, 因为@DataPermission 注解进来的 动态拼接

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第9张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第10张图片
若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第11张图片
若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第12张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第13张图片

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第14张图片
若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第15张图片

Mybatis-plus 在自定义的sql语句中 调用QueryWrapper 的查询条件

需求

项目中需要实现自定义的查询,但是仍然想用QueryWrapper对象里面的那些查询,

解决

在接口中方法中:@Param(Constants.WRAPPER) QueryWrapper queryWrapper; 里面的Constants.WRAPPER其实就是:“ew”

在这里插入图片描述

在我们的Mapper.xml文件中:

直接使用:${ew.customSqlSegment} 就可以调用QueryWrapper对象的条件sql


分析

就上面就可以直接用了,下面来分析怎么调用的

首先${ew.customSqlSegment} 这里,我们首先猜测Query Wrapper里面是不是有这个属性,因为我们用过mybaits都知道, 传入一个对象之后,直接对象.属性就可以获取值
————————————————

附上Query Wrapper的关系图

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第16张图片
我在Wrapper类中找到了getCustomSqlSegment()方法,所以我猜测肯定是调用了这个get方法,

但是奇怪的是这个类里面并没有customSqlSegment属性字段

    public String getCustomSqlSegment() {
        MergeSegments expression = this.getExpression();
        if (Objects.nonNull(expression)) {
            NormalSegmentList normal = expression.getNormal();
            String sqlSegment = this.getSqlSegment();
            if (StringUtils.isNotBlank(sqlSegment)) {
            // 注意这里,如果没有where 条件,不拼接 customSqlSegment
                if (normal.isEmpty()) {
                    return sqlSegment;
                }

                return "WHERE " + sqlSegment;
            }
        }

        return "";
    }

注意这里,如果没有where 条件,不拼接 customSqlSegment

所以猜测是直接调用的这个get方法,根据我们传入的属性名称,拼接成get+属性名称,这样来调用的吗?

扩展

所以我就写了一个demo

1、没有属性值

2、但是有一个getUserForId()的方法,

3、在mapper.xml文件中 :对象.userForId 但是注意:我的类里面并没有这个属性

试一下是否可以成功查询出数据!

实体类:
若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第17张图片

xml文件

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第18张图片
调用:居然真的查询成功了!!!!!!!!!!!!!

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第19张图片

end 查看源码中:

Reflector类中:
构造方法里面,根据该类名获取所有的get方法

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第20张图片
到这里大家应该就明白了,

是根据所有的get方法,经过处理


  private void addSetMethods(Method[] methods) {
    Map> conflictingSetters = new HashMap<>();
    Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
      .forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
    resolveSetterConflicts(conflictingSetters);
  }

  private void addMethodConflict(Map> conflictingMethods, String name, Method method) {
    if (isValidPropertyName(name)) {
      List list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>());
      list.add(method);
    }
  }

  private void resolveSetterConflicts(Map> conflictingSetters) {
    for (Entry> entry : conflictingSetters.entrySet()) {
      String propName = entry.getKey();
      List setters = entry.getValue();
      Class getterType = getTypes.get(propName);
      boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
      boolean isSetterAmbiguous = false;
      Method match = null;
      for (Method setter : setters) {
        if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
          // should be the best match
          match = setter;
          break;
        }
        if (!isSetterAmbiguous) {
          match = pickBetterSetter(match, setter, propName);
          isSetterAmbiguous = match == null;
        }
      }
      if (match != null) {
        addSetMethod(propName, match);
      }
    }
  }

保存到:getMethods中


  private void addGetMethod(String name, Method method, boolean isAmbiguous) {
    MethodInvoker invoker = isAmbiguous
        ? new AmbiguousMethodInvoker(method, MessageFormat.format(
            "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
            name, method.getDeclaringClass().getName()))
        : new MethodInvoker(method);
    getMethods.put(name, invoker);
    Type returnType = TypeParameterResolver.resolveReturnType(method, type);
    getTypes.put(name, typeToClass(returnType));
  }

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第21张图片

调用的时候:根据xml里面的属性名称,在我们上面初始化的getMethods的map中找到对应的方法,执行方法的调用

若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析_第22张图片

总结
所以这就是为啥 ${ew.customSqlSegment} 可以调用的原因了

你可能感兴趣的:(鉴权,前端,java,mybatis)