用户关系(Relation)是描述业务系统中人员与人员之间的关系,如:签约、关注,或者朋友关系。
之前我们在扩展身份管理模块的时候,已经实现了用户关系管理,可以查看本系列博文之前的内容。怎样优雅地增删查改(二):扩展身份管理模块
原理 查询依据
用户之间的关系通过Relation表来存储。模型如下图所示:
查询目标业务对象HealthAlarm关联了业务用户HealthClient,因业务用户与鉴权用户IdentityUser共享同一个Id,因此可以通过查询用户关系关联的User,查询到业务对象。
实现 正向用户关系 定义按正向用户关系查询(IRelationToOrientedFilter)接口
1 2 3 4 5 6 7 8 9 10 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
1 protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedToAsync(Guid userId, string relationType);
创建应用过滤条件方法:ApplyRelationToOrientedFiltered,在此实现拼接LINQ表达式,
ICurrentUser是Abp的一个服务,用于获取当前登录用户的信息
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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)接口
1 2 3 4 5 6 7 8 9 10 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
1 protected abstruct Task<IEnumerable<Guid>> GetUserIdsByRelatedFromAsync(Guid userId, string relationType);
创建应用过滤条件方法:ApplyRelationFromOrientedFiltered,在此实现拼接LINQ表达式,
ICurrentUser是Abp的一个服务,用于获取当前登录用户的信息
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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接口实现上并非互斥。
请注意,可应用过滤的条件为:
input需实现IRelationToOrientedFilter接口;
实体必须关联用户。
否则将原封不动返回IQueryable对象。
使用 在应用层中,实现GetUserIdsByRelatedToAsync
1 2 3 4 5 6 7 8 9 10 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
1 2 3 4 5 6 7 8 9 10 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接口,代码如下:
1 2 3 4 5 6 7 8 9 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如下图: