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

文章目录

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

用户关系(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.hqwc.cn/news/27705.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EC200A-CN移植

Platform: RK3588 OS: Debian11 Kernel: v5.10.160 Module &#xff1a;EC200A-CN 国内版 Linux USB 驱动架构 USB 主机控制器驱动在分层结构的最底层&#xff0c;直接与硬件交互。USB 核心是整个 USB 主机驱动的核心&#xff0c;用于管理 USB 总线、USB 总线设备和 USB 总线…

springboot整合eureka、config搭建注册中心和配置中心

目录 一 、springboot整合eureka实现注册中心 二、springboot整合config实现配置中心 三、从配置中心拉取配置 这篇文章详细介绍怎么通过eureka和config分别搭建一个注册中心和配置中心的服务。 一 、springboot整合eureka实现注册中心 1、创建一个springboot项目&#xff…

vue3 兄弟子组件相互调用方法的实现思路及解决方法

需求背景&#xff1a; vue实际开发过程中&#xff0c;可能需要在某一个子组件调用另一个子组件的方法&#xff0c;从而实现业务需求。 例如以下的一个业务场景。 如上就涉及到到组件B需要调用组件A的form验证方法。 解决思路&#xff1a; 利用共同的父组件C&#xff0c;我…

Spark 4:Spark Core 共享变量

广播变量 # coding:utf8 import timefrom pyspark import SparkConf, SparkContext from pyspark.storagelevel import StorageLevelif __name__ __main__:conf SparkConf().setAppName("test").setMaster("local[*]")sc SparkContext(confconf)stu_inf…

ES6——Promise

promise 含义&#xff1a;异步编程解决方案 特点&#xff1a;1、状态不受外界影响&#xff0c;状态有三种&#xff1a;pending、fulfilled、rejected 2、状态不可逆&#xff0c;只能pending -> fulfilled、pending -> rejected 缺点&#xff1a;无法取消、不设置回调函…

mysql数字开头字符串排序

表结构 CREATE TABLE building (id bigint NOT NULL,name varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT 名称,full_name varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT 全称,PRIMARY KEY (id) USIN…

javascript 将json数据导出excel

<el-button type"primary" plain v-on:click"jsonToExcel();">导出</el-button>jsonToExcel() {const data this.AlarmData;let head "城市,站点,时间,A相电流(A),B相电流(A),C相电流(A),SO2压力(MPa),CO压力(MPa),NOX压力(MPa),A相电压…

总结930

之前本打算每天学12h&#xff0c;践行了一周&#xff0c;一天最多也就学11.5h,在时间利用上感觉已经趋于饱和的了。 这个时候&#xff0c;时间统计法应该能发挥它应有的作用了&#xff0c;但就算详细记录每日时间支出&#xff0c;也不能从根本上解决问题。 一味的进行时间上的…

基于Springboot的宠物店管理系统(源代码+数据库)087

基于Springboot的宠物店管理系统(源代码数据库)087 一、系统介绍 本系统分为管理员、店员两种角色 店员角色包含以下功能&#xff1a; 登录、宠物主人管理、宠物管理、宠物医疗管理、宠物销售管理、宠物寄养管理、宠物用品管理、宠物日常服务管理、宠物常见问题、个人中心、…

Appium Android ——利用 TestNG 并行执行用例

目录 前言&#xff1a; 一、测试类 二、连接两个 Android 设备或启动两个虚拟机 三、项目路径下新建两个 testng.xml 四、开启两个 appium server 五、导出依赖 六、执行测试 七、查看报告 前言&#xff1a; Appium是一个流行的移动应用自动化测试工具&#xff0c;…

RAID6故障导致分区打不开的服务器数据恢复案例

服务器数据恢复环境&#xff1a; 一台infortrend存储&#xff0c;有一组由12块硬盘组建的RAID6&#xff0c;RAID6的所有空间划分给一个LUN并映射到WINDOWS系统上&#xff0c;WINDOWS系统上划分了一个GPT分区。 服务器故障&分析&#xff1a; 存储无法访问&#xff0c;经过检…

【代码随想录13】前 K 个高频元素

题目 给定一个非空的整数数组&#xff0c;返回其中出现频率前 k 高的元素。 示例 1: 输入: nums [1,1,1,2,2,3], k 2输出: [1,2] 示例 2: 输入: nums [1], k 1输出: [1] 提示&#xff1a; 你可以假设给定的 k 总是合理的&#xff0c;且 1 ≤ k ≤ 数组中不相同的元素…