怎样优雅地增删查改(八):按用户关系查询

news2025/2/23 0:57:46

文章目录

    • 原理
    • 实现
      • 正向用户关系
      • 反向用户关系
    • 使用
    • 测试

用户关系(Relation)是描述业务系统中人员与人员之间的关系,如:签约、关注,或者朋友关系。

之前我们在扩展身份管理模块的时候,已经实现了用户关系管理,可以查看本系列博文之前的内容。怎样优雅地增删查改(二):扩展身份管理模块

原理

查询依据

用户之间的关系通过Relation表来存储。模型如下图所示:

在这里插入图片描述

  • 关系类型由Type来定义

  • 关系指向由UserId与RelatedUserId来描述

    人员之间的关系是单项的,也就是说可以A是B的好友,但B不一定是A的好友

    正向关系:User -> RelatedUser

    反向关系:RelatedUser -> User

查询目标业务对象HealthAlarm关联了业务用户HealthClient,因业务用户与鉴权用户IdentityUser共享同一个Id,因此可以通过查询用户关系关联的User,查询到业务对象。

在这里插入图片描述

实现

正向用户关系

定义按正向用户关系查询(IRelationToOrientedFilter)接口

public interface IRelationToOrientedFilter
{
    Guid? RelationToUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

  • EntityUserIdIdiom:语义上的UserId,用于指定业务实体中用于描述“用户Id”字段的名称,若不指定,则默认为“UserId”;
  • RelationToUserId:正向关系用户Id,若为Guid.Empty,则使用当前登录用户的Id;
  • RelationType:关系类型,如:“attach”为签约,“follow”为关注,可自定义。

对于Relation服务,其依赖关系在应用层,查找指定用户的关系用户将在CurdAppServiceBase的子类实现。创建一个抽象方法GetUserIdsByRelatedToAsync

protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType);

创建应用过滤条件方法:ApplyRelationToOrientedFiltered,在此实现拼接LINQ表达式,

ICurrentUser是Abp的一个服务,用于获取当前登录用户的信息

代码如下:

protected virtual async Task<IQueryable<TEntity>> ApplyRelationToOrientedFiltered(IQueryable<TEntity> query, TGetListInput input)
{
    if (input is IRelationToOrientedFilter)
    {
        var filteredInput = input as IRelationToOrientedFilter;
        var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
        if (string.IsNullOrEmpty(entityUserIdIdiom))
        {
            entityUserIdIdiom = "UserId";
        }
        if (HasProperty<TEntity>(entityUserIdIdiom))
        {
            var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
            if (filteredInput != null && filteredInput.RelationToUserId.HasValue && !string.IsNullOrEmpty(filteredInput.RelationType))
            {

                Guid userId = default;
                if (filteredInput.RelationToUserId.Value == Guid.Empty)
                {
                    using (var scope = ServiceProvider.CreateScope())
                    {
                        var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
                        if (currentUser != null)
                        {
                            userId = currentUser.GetId();
                        }
                    }
                }
                else
                {
                    userId = filteredInput.RelationToUserId.Value;
                }

                var ids = await GetUserIdsByRelatedToAsync(userId, filteredInput.RelationType);
                Expression originalExpression = null;
                var parameter = Expression.Parameter(typeof(TEntity), "p");
                foreach (var id in ids)
                {
                    var keyConstantExpression = Expression.Constant(id, typeof(Guid));
                    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                    var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);

                    if (originalExpression == null)
                    {
                        originalExpression = expressionSegment;
                    }
                    else
                    {
                        originalExpression = Expression.Or(originalExpression, expressionSegment);
                    }
                }

                var equalExpression = originalExpression != null ?
                        Expression.Lambda<Func<TEntity, bool>>(originalExpression, parameter)
                        : p => false;

                query = query.Where(equalExpression);

            }

        }
    }
    return query;
}


反向用户关系

定义按反向用户关系查询(IRelationFromOrientedFilter)接口

public interface IRelationFromOrientedFilter
{
    Guid? RelationFromUserId { get; set; }
    
    public string EntityUserIdIdiom { get; }

    string RelationType { get; set; }

}

  • EntityUserIdIdiom:语义上的UserId,用于指定业务实体中用于描述“用户Id”字段的名称,若不指定,则默认为“UserId”;
  • RelationFromUserId:反向关系用户Id,若为Guid.Empty,则使用当前登录用户的Id;
  • RelationType:关系类型,如:“attach”为签约,“follow”为关注,可自定义。

对于Relation服务,其依赖关系在应用层,查找指定用户的关系用户将在CurdAppServiceBase的子类实现。创建一个抽象方法GetUserIdsByRelatedFromAsync

protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType);

创建应用过滤条件方法:ApplyRelationFromOrientedFiltered,在此实现拼接LINQ表达式,

ICurrentUser是Abp的一个服务,用于获取当前登录用户的信息

代码如下:

protected virtual async Task<IQueryable<TEntity>> ApplyRelationFromOrientedFiltered(IQueryable<TEntity> query, TGetListInput input)
{
    if (input is IRelationFromOrientedFilter)
    {
        var filteredInput = input as IRelationFromOrientedFilter;
        var entityUserIdIdiom = filteredInput.EntityUserIdIdiom;
        if (string.IsNullOrEmpty(entityUserIdIdiom))
        {
            entityUserIdIdiom = "UserId";
        }
        if (HasProperty<TEntity>(entityUserIdIdiom))
        {
            var property = typeof(TEntity).GetProperty(entityUserIdIdiom);
            if (filteredInput != null && filteredInput.RelationFromUserId.HasValue && !string.IsNullOrEmpty(filteredInput.RelationType))
            {

                Guid userId = default;
                if (filteredInput.RelationFromUserId.Value == Guid.Empty)
                {
                    using (var scope = ServiceProvider.CreateScope())
                    {
                        var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
                        if (currentUser != null)
                        {
                            userId = currentUser.GetId();
                        }
                    }
                }
                else
                {
                    userId = filteredInput.RelationFromUserId.Value;
                }

                var ids = await GetUserIdsByRelatedFromAsync(userId, filteredInput.RelationType);
                Expression originalExpression = null;
                var parameter = Expression.Parameter(typeof(TEntity), "p");
                foreach (var id in ids)
                {
                    var keyConstantExpression = Expression.Constant(id, typeof(Guid));
                    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
                    var expressionSegment = Expression.Equal(propertyAccess, keyConstantExpression);

                    if (originalExpression == null)
                    {
                        originalExpression = expressionSegment;
                    }
                    else
                    {
                        originalExpression = Expression.Or(originalExpression, expressionSegment);
                    }
                }

                var equalExpression = originalExpression != null ?
                        Expression.Lambda<Func<TEntity, bool>>(originalExpression, parameter)
                        : p => false;

                query = query.Where(equalExpression);

            }
        }
    }
    return query;
}

IRelationToOrientedFilter 和 IRelationFromOrientedFilter接口实现上并非互斥。

请注意,可应用过滤的条件为:

  1. input需实现IRelationToOrientedFilter接口;
  2. 实体必须关联用户。

否则将原封不动返回IQueryable对象。

使用

在应用层中,实现GetUserIdsByRelatedToAsync

protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedToUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}

或GetUserIdsByRelatedFromAsync

protected override async Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType)
{
    var ids = await relationAppService.GetRelatedFromUserIdsAsync(new GetRelatedUsersInput()
    {
        UserId = userId,
        Type = relationType
    });
    return ids;

}

在GetAllAlarmInput中实现IRelationToOrientedFilter或GetUserIdsByRelatedFromAsync接口,代码如下:

public class GetAllAlarmInput : PagedAndSortedResultRequestDto, IRelationToOrientedFilter
{ 
    public Guid? RelationToUserId { get ; set ; }
    public string RelationType { get; set; }
    public string EntityUserIdIdiom { get; }

    ...
}

测试

创建一些客户(Client)

在这里插入图片描述

进入客户管理,在右侧客户列表中点击“查看详情”

打开客户详情页面,点击管理 - 设置签约员工

在这里插入图片描述

选择一个用户,此时该客户会签约至该用户账号下,这里我们将客户1和客户3签约至当前账号admin下。

在这里插入图片描述

登录签约用户(admin)的账号,点击“我的” - 客户 - 签约客户

在客户列表中可见,客户1和客户3已签约至当前账号下。

在这里插入图片描述

组合查询的报文Payload如下图:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/766704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark(30):Spark性能调优之常规性能调优

目录 0. 相关文章链接 1. 最优资源配置 2. RDD优化 2.1. RDD复用 2.2. RDD持久化 2.3. RDD尽可能早的 filter 操作 3. 并行度调节 4. 广播大变量 5. Kryo序列化 6. 调节本地化等待时长 0. 相关文章链接 Spark文章汇总 1. 最优资源配置 Spark 性能调优的第一步&…

9.Ceph部署

文章目录 Ceph部署前期环境准备实验部署软件安装部署Ceph集群部署mon节点部署OSD存储节点部署mgr节点开启监控模块管理pool Ceph部署 前期环境准备 主机名public网络cluster网络角色admin192.168.242.69admin(管理节点)node01192.168.242.66192.168.242.100.11mon、mgr、osdn…

【Elemnt-UI——el-popover点击出现多个弹框】

效果图 解决 :append-to-body"false"添加这个属性就可以了 <el-popoverv-model"item.contextmenuVisible"placement"bottom-end":append-to-body"false"trigger"click":visible-arrow"false"hide"item.…

[PCIE体系结构导读]PCIE总结(一)

什么是PCIE PCIe Peripheral Component Interconnect express 快速外部组件互联 高速串行计算机扩展总线标准 处理器系统的局部总线 连接外部设备 高速、低时延支持热插拔可靠扩展性好复杂度高点对点串行连接 附一个博主写的总结文章&#xff0c;非常好 《PCI EXPRESS体系结…

如何在服务器下载coco数据集

如果是需要在服务器上进行跑实验&#xff0c;可以直接通过wget下载链接 那么如何确定下载地址呢&#xff1f; 例如&#xff1a;先找到coco的数据集地址http://cocodataset.org 然后可以看到 可以下载一个 2017 Train/Val annotations[241MB]&#xff0c;在下载内容中可以看到…

跨域是怎么个事?

跨域是怎么个事&#xff1f; 【一】到底什么是跨域&#xff1f;【二】什么是浏览器的同源策略&#xff1f;【三】跨域问题有哪些解决方案&#xff1f;【四】详细说说Nginx解决跨域问题的实现过程&#xff1f;&#xff08;1&#xff09;项目准备 【一】到底什么是跨域&#xff1…

宋浩线性代数笔记(一)行列式的计算

本帖更新b站宋浩老师的线代网课笔记&#xff0c;内容较为细致详细&#xff0c;参考书用的是科学出版社的第三版&#xff0c;之后会附加同济出版社第六版的教材内容。 &#xff08;字不好看大家将就看吧QAQ&#xff09;

【论文阅读】一些多轮对话文章的体会 ACL 2023

前言 本文是对昨天看到的ACL 2023三篇多轮对话文章的分享这三个工作都是根据一些额外属性控制输出的工作&#xff0c;且评估的方面比较相似&#xff0c;可以借鉴 方法 这几篇文章都不是做general任务的&#xff0c;倾向于通过一些额外信息&#xff0c;来做specific任务 【1】…

当低代码和无代码平台可以加速应用程序现代化时

你的组织很可能正在寻求将遗留应用程序现代化、将单片应用架构拆分为服务&#xff0c;并迁移到公共或私有云基础架构。在此过程中&#xff0c;您可能还希望改善用户体验&#xff0c;创建CI/CD流水线&#xff0c;添加测试自动化&#xff0c;并实施一系列其他DevOps最佳实践。 这…

水声功率放大器的作用是什么

水声功率放大器是一种专门用于水声设备的高功率电子设备&#xff0c;主要用于提升水下信号的传输距离和保证语音清晰度。它的作用在水下通信、水下测量、海洋科学等领域都非常重要。 其主要作用有以下几个方面&#xff1a; 增强信号传输距离 水声信号在水中传播会受到各种因素的…

维安股份冲刺A股上市:计划募资约15亿元,上海科学院为实控人

7月17日&#xff0c;上海证券交易所对上海维安电子股份有限公司&#xff08;下称“维安股份”&#xff09;发出问询函。据贝多财经了解&#xff0c;维安股份于2023年6月20日递交招股书&#xff0c;准备在上海证券交易所主板上市。 本次冲刺上市&#xff0c;维安股份计划募资15.…

解决一云多芯全球命题,浪潮云海给出了解题思路

云计算&#xff0c;生而为用户简化资源的使用&#xff0c;让用户不必关注复杂的底层硬件架构&#xff0c;而是通过“服务”的方式调用资源&#xff0c;专注于自己的业务创新。 因此&#xff0c;用户要上云&#xff0c;就天然地要求云平台必须能够屏蔽底层的硬件架构&#xff0…

轻量级应用服务器开放端口

关于使用浏览器连接自己所写的TCP进程时&#xff0c;由于没有开放端口&#xff0c;而且搜索到对应的操作来进行开放端口&#xff0c;所以在完成开放端口后特意做个笔记&#xff0c;防止忘记。 登录自己所使用的服务器的网站找到控制台 找到轻量级应用服务器 找到所需要开放端口…

建造者模式-复杂对象的组装与创建

生产一辆车&#xff0c;主要有以下步骤&#xff1a;安装骨架、安装发动机及安装轮胎。这些步骤有指定的执行顺序&#xff0c;步骤缺一不可。 图 传统方案 传统方案存在的问题&#xff1a; 传参不便&#xff0c;虽可在构造函数那传参&#xff0c;但是传参时需要注意参数顺序等…

After Effects CPU 和 RAM 使用率高,如何修复?

如果您发现 Adob​​e After Effects 的 CPU 和 RAM 使用率较高&#xff0c;可以按照以下方法解决该问题。 1]确保您的系统满足最低系统要求 要在您的 PC 上运行 Adob​​e After Effects&#xff0c;您的 PC 需要满足最低系统要求。只有这样&#xff0c;该程序才会停止消耗更…

WEB:Confusion1

背景知识 SSTI漏洞 题目 根据网站图片和题目描述的提示&#xff0c;大象是php&#xff0c;蟒蛇是python&#xff0c;说明了这个网站是用python写的 在python中&#xff0c;比较常规的漏洞就是SSTI模板注入 没有思路&#xff0c;先点login和register页面看看 查看源代码 之前…

如何防止DDoS?基于分布式云的 DDoS 解决方案一览

在日益开放和互联的世界中&#xff0c;DDOS&#xff08;分布式拒绝服务&#xff09;攻击和安全漏洞日益频发&#xff0c;企业都应将有效地保护其业务、声誉和数据中心免受不断加剧的DDoS攻击放在战略性位置。如何防止DDoS&#xff1f;看了F5提供的分布式云DDoS解决方案&#xf…

list最常用的遍历五种方式以及使用场景

目录 遍历方式的适用场景对比 迭代器遍历 列表迭代器 增强for遍历 Lambda表达式 lambda表达式简介 普通for遍历 集合中通用的并且常用的六种方法 遍历方式的适用场景对比 迭代器遍历 &#xff1a;在遍历过程中需要删除元素&#xff0c;请使用迭代器 列表迭代器&#xff1…

Java如何实现定时读取json文件里的内容

Java实现定时读取json文件里的内容 项目背景代码实现读取json配置文件定时任务 测试 项目背景 有时候我们会需要定时来读取JSON配置文件里的内容&#xff0c;来执行一些业务逻辑上的操作。 比如&#xff1a;开发一个物流运输系统&#xff0c;系统需要定期读取一个包含货物信息…

6个真正免费的平面设计素材网站

新手设计师没有自己的素材库&#xff0c;找素材费时又费力&#xff0c;找到了还不一定能免费下载或商用&#xff0c;别急&#xff01;我带着我收藏多年的素材库来了&#xff0c;平面、UI、免扣、高清背景图、设计元素、字体等素材全部都有&#xff0c;重点是免费下载&#xff0…