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

2023-09-15 09:40:27


使用方法:

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

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

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

sqlSegment:

<where>
    ${ew.sqlSegment}
</where>

customSqlSegment:

${ew.customSqlSegment} 则省略 <where></where>

eg: select * from a<where> ${ew.sqlSegment} </where>

select * from a ${ew.customSqlSegment}

注:在对应的dao方法中 一样要指定 @Param(Constants.WRAPPER) Wrapper<User> 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, "");
        }
    }
}
-----------------------------------

在这里插入图片描述

使用场景

二、ew.customSqlSegment

${ew.customSqlSegment}

在这里插入图片描述

build:

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

get

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

需求

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

解决

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

在这里插入图片描述

在我们的Mapper.xml文件中:

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

<select id="selectCustExtraInfoPage">
        SELECT * FROM cust_info ${ew.customSqlSegment}
</select>

分析

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

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

附上Query Wrapper的关系图

在这里插入图片描述
我在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 但是注意:我的类里面并没有这个属性

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

实体类:
在这里插入图片描述

xml文件

在这里插入图片描述
调用:居然真的查询成功了!!!!!!!!!!!!!

在这里插入图片描述

end 查看源码中:

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

在这里插入图片描述
到这里大家应该就明白了,

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

  private void addSetMethods(Method[] methods) {
    Map<String, List<Method>> 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<String, List<Method>> conflictingMethods, String name, Method method) {
    if (isValidPropertyName(name)) {
      List<Method> list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>());
      list.add(method);
    }
  }

  private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {
      String propName = entry.getKey();
      List<Method> 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));
  }

在这里插入图片描述

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

在这里插入图片描述

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

更多推荐

Windows网络服务综测刷题

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。座右铭:低头赶路,敬事如仪个人主页:网络豆的主页​​​​​前言本章将会讲解Windows网络服务的刷题一.刷题在WindowsServer2016系统中,通过添加Web服务器(IIS)角色并进行正确配置,可以提供()应用服务。(选择两项)AB使用wind

Java版企业电子招标采购系统源码Spring Cloud + Spring Boot +二次开发+ MybatisPlus + Redis

项目说明随着公司的快速发展,企业人员和经营规模不断壮大,公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境,最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范,以及审计监督要求;通过电子化平台提高招投标工作的公开性和透明性;通过电子化招投标,使得招标采购的质量更高、速度

效率工具3-计算机网络工具

查看各个状态的tcp连接情况netstat-n|awk‘/^tcp/{++S[$NF]}END{for(ainS)printa,S[a]}’/^tcp/正则表达式匹配netstat命令输出的匹配部分,即以"TCP"开始的行{++S[$NF]}对于符合条件的每一行,awk命令将使用数组S来计算每种连接状态的连接数$NF代

Javascript小案例--树形菜单(菜单数据为对象)

理论上菜单层级可以无限多,因为是递归渲染。gif效果图:代码<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>

看盘也要学?伦敦金看盘技巧

看盘是伦敦金投资的基础,没有好的看看技巧,不能盯住市场价格的变化,我们就没办法在市场出现机会的时候就入场,也就是说,我们会错过好的交易机会,最终导致自己的交易不能盈利,甚至发生亏损。下面我们就来讨论一下如何在伦敦市场中学习看弹的技巧。当市场出现大量买入,出现大阳线的时候,很可能是有市场的资金出场。我们会观察到,市场会出

SpringTask任务调度和声明式事务配置

是哪段话激励着你不断向前摆脱逆境?一,SpringTaskxml写法SpringTask已经纳入到spring核心技术里面了,直接导入spring的上下文坐标就好了<dependencies><dependency><groupId>org.springframework</groupId><artifactId>sp

生产发版前准备【经验分享给萌新程序员】

经验分享自从当了码农,已经不知道有多少个日日夜夜熬夜到凌晨三四点了。不知道大家有没有想过,生产上线发布新版本到凌晨三、四点都有可能是哪些原因呢?下面我将分享下自己以前跟进生产版本发布的经验,经验丰富的老前辈们肯定都比我清楚(可忽略此篇文章~哈哈~)。这篇文章可能更适合萌新程序员体质。争取不熬夜工作(只能熬夜玩乐,不能熬

kafka ack确认机制

Kafka使用ACK(Acknowledgment)确认机制来确保消息在生产者和消费者之间的可靠传递。这个机制确保消息在被认为已成功发送或处理之前不会被丢失。Kafka的ACK确认机制有三个级别:acks=0:这是最快速的确认级别,也是最不可靠的。生产者发送消息后不会等待任何确认,直接将消息添加到分区的副本中,并认为消

汽车电子系统总线LIN通信协议精要

汽车电子系统总线LIN通信协议精要文章目录汽车电子系统总线LIN通信协议精要简介LIN信号的物理层LIN总线收发器LIN通信协议LIN的主机和从机LIN报文帧结构同步间隔段(Breakfield)同步段(Syncfield)受保护的帧ID(ProtectedIdentifierfield)应答数据段(Datafield

从零开始:使用 Kubernetes 部署 Nginx 应用

[root@icv-k8s-node-1~]#kubectlcreatedeploymentjavaedge-nginx--image=nginx:1.24.0deployment.apps/javaedge-nginxcreated[root@icv-k8s-node-1~]#kubectlgetallNAMEREA

内网穿透的应用-NAS私有云存储 - 搭建Nextcloud私有云盘并公网远程访问

文章目录摘要1.环境搭建2.测试局域网访问3.内网穿透3.1ubuntu本地安装cpolar3.2创建隧道3.3测试公网访问4配置固定http公网地址4.1保留一个二级子域名4.1配置固定二级子域名4.3测试访问公网固定二级子域名摘要Nextcloud,它是ownCloud的一个分支,是一个文件共享服务器,允许您将个人

热文推荐