65 changed files with 1210 additions and 844 deletions
@ -0,0 +1,12 @@ |
|||
namespace LINGYUN.Abp.IM.Contract |
|||
{ |
|||
public class UserAddFriendResult |
|||
{ |
|||
public bool Successed => Status == UserFriendStatus.Added; |
|||
public UserFriendStatus Status { get; } |
|||
public UserAddFriendResult(UserFriendStatus status) |
|||
{ |
|||
Status = status; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
namespace LINGYUN.Abp.IM.Contract |
|||
{ |
|||
public enum UserFriendStatus : byte |
|||
{ |
|||
/// <summary>
|
|||
/// 需要验证
|
|||
/// </summary>
|
|||
NeedValidation, |
|||
/// <summary>
|
|||
/// 已添加
|
|||
/// </summary>
|
|||
Added |
|||
} |
|||
} |
|||
@ -1,29 +1,79 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.ObjectExtending; |
|||
|
|||
namespace LINGYUN.Abp.IM.Messages |
|||
{ |
|||
public class ChatMessage |
|||
public class ChatMessage : ExtensibleObject |
|||
{ |
|||
/// <summary>
|
|||
/// 租户
|
|||
/// </summary>
|
|||
public Guid? TenantId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 群组标识
|
|||
/// </summary>
|
|||
public string GroupId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 消息标识
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 调用者无需关注此字段,将由服务自动生成
|
|||
/// </remarks>
|
|||
public string MessageId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 发送者标识
|
|||
/// </summary>
|
|||
public Guid FormUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 发送者名称
|
|||
/// </summary>
|
|||
public string FormUserName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 接收用户标识
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 设计为可空是为了兼容群聊消息
|
|||
/// /remarks>
|
|||
public Guid? ToUserId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 消息内容
|
|||
/// </summary>
|
|||
[DisableAuditing] |
|||
public string Content { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 发送时间
|
|||
/// </summary>
|
|||
public DateTime SendTime { get; set; } |
|||
/// <summary>
|
|||
/// 是否匿名发送
|
|||
/// </summary>
|
|||
public bool IsAnonymous { get; set; } |
|||
/// <summary>
|
|||
/// 消息类型
|
|||
/// </summary>
|
|||
public MessageType MessageType { get; set; } = MessageType.Text; |
|||
|
|||
public bool IsAnonymous { get; set; } = false; |
|||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |
|||
{ |
|||
var results = ExtensibleObjectValidator.GetValidationErrors(this, validationContext); |
|||
|
|||
public MessageType MessageType { get; set; } = MessageType.Text; |
|||
foreach (var result in ValidateReceiver(validationContext)) |
|||
{ |
|||
results.Add(result); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
protected virtual IEnumerable<ValidationResult> ValidateReceiver(ValidationContext validationContext) |
|||
{ |
|||
if (GroupId.IsNullOrWhiteSpace() && !ToUserId.HasValue) |
|||
{ |
|||
yield return new ValidationResult(""); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,47 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.IM.Messages |
|||
{ |
|||
public abstract class MessageSenderBase : IMessageSender |
|||
{ |
|||
protected IMessageStore Store { get; } |
|||
protected ILogger Logger { get; } |
|||
protected MessageSenderBase( |
|||
IMessageStore store, |
|||
ILogger logger) |
|||
{ |
|||
Store = store; |
|||
Logger = logger; |
|||
} |
|||
|
|||
public virtual async Task SendMessageAsync(ChatMessage chatMessage) |
|||
{ |
|||
// 持久化
|
|||
await Store.StoreMessageAsync(chatMessage); |
|||
|
|||
try |
|||
{ |
|||
if (!chatMessage.GroupId.IsNullOrWhiteSpace()) |
|||
{ |
|||
await SendMessageToGroupAsync(chatMessage); |
|||
} |
|||
else |
|||
{ |
|||
await SendMessageToUserAsync(chatMessage); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogWarning("Could not send message, group: {0}, formUser: {1}, toUser: {2}", |
|||
chatMessage.GroupId, chatMessage.FormUserName, |
|||
chatMessage.ToUserId.HasValue ? chatMessage.ToUserId.ToString() : "None"); |
|||
Logger.LogWarning("Send group message error: {0}", ex.Message); |
|||
} |
|||
} |
|||
|
|||
protected abstract Task SendMessageToGroupAsync(ChatMessage chatMessage); |
|||
protected abstract Task SendMessageToUserAsync(ChatMessage chatMessage); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.IM.Messages |
|||
{ |
|||
public class NullMessageSender : MessageSenderBase, ITransientDependency |
|||
{ |
|||
public NullMessageSender(IMessageStore store, ILogger<NullMessageSender> logger) |
|||
: base(store, logger) |
|||
{ |
|||
} |
|||
|
|||
protected override Task SendMessageToGroupAsync(ChatMessage chatMessage) |
|||
{ |
|||
Logger.LogWarning("No IMessageSender Interface implementation!"); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
protected override Task SendMessageToUserAsync(ChatMessage chatMessage) |
|||
{ |
|||
Logger.LogWarning("No IMessageSender Interface implementation!"); |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
using LINGYUN.Abp.RealTime.Client; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.SignalR; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace LINGYUN.Abp.Notifications.SignalR |
|||
{ |
|||
public abstract class OnlineClientHubBase : AbpHub |
|||
{ |
|||
private ICurrentPrincipalAccessor _currentPrincipalAccessor; |
|||
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyGetRequiredService(ref _currentPrincipalAccessor); |
|||
|
|||
private IOnlineClientManager _onlineClientManager; |
|||
protected IOnlineClientManager OnlineClientManager => LazyGetRequiredService(ref _onlineClientManager); |
|||
|
|||
private IHttpContextAccessor _httpContextAccessor; |
|||
protected IHttpContextAccessor HttpContextAccessor => LazyGetRequiredService(ref _httpContextAccessor); |
|||
|
|||
public override async Task OnConnectedAsync() |
|||
{ |
|||
await base.OnConnectedAsync(); |
|||
IOnlineClient onlineClient = CreateClientForCurrentConnection(); |
|||
Logger.LogDebug("A client is connected: " + onlineClient.ToString()); |
|||
OnlineClientManager.Add(onlineClient); |
|||
if (onlineClient.TenantId.HasValue) |
|||
{ |
|||
// 以租户为分组,将用户加入租户通讯组
|
|||
await Groups.AddToGroupAsync(onlineClient.ConnectionId, onlineClient.TenantId.Value.ToString()); |
|||
} |
|||
} |
|||
|
|||
public override async Task OnDisconnectedAsync(Exception exception) |
|||
{ |
|||
await base.OnDisconnectedAsync(exception); |
|||
Logger.LogDebug("A client is disconnected: " + Context.ConnectionId); |
|||
try |
|||
{ |
|||
// 从通讯组移除
|
|||
var onlineClient = OnlineClientManager.GetByConnectionIdOrNull(Context.ConnectionId); |
|||
if(onlineClient != null) |
|||
{ |
|||
// 移除在线客户端
|
|||
OnlineClientManager.Remove(Context.ConnectionId); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogWarning(ex.ToString(), ex); |
|||
} |
|||
} |
|||
|
|||
protected virtual IOnlineClient CreateClientForCurrentConnection() |
|||
{ |
|||
// abp框架没有处理,需要切换一下用户身份令牌.否则无法获取用户信息
|
|||
using (CurrentPrincipalAccessor.Change(Context.User)) |
|||
{ |
|||
return new OnlineClient(Context.ConnectionId, GetClientIpAddress(), |
|||
CurrentTenant.Id, CurrentUser.Id) |
|||
{ |
|||
ConnectTime = Clock.Now, |
|||
UserName = CurrentUser.UserName |
|||
}; |
|||
} |
|||
} |
|||
|
|||
protected virtual string GetClientIpAddress() |
|||
{ |
|||
try |
|||
{ |
|||
return HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogException(ex, LogLevel.Warning); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.RealTime.SignalR |
|||
{ |
|||
public class Class1 |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="3.2.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.RealTime\LINGYUN.Abp.RealTime.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.AspNetCore.SignalR; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.RealTime.SignalR |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreSignalRModule))] |
|||
public class AbpRealTimeSignalRModule : AbpModule |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using LINGYUN.Abp.IM.Contract; |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
public class UserChatFriendEto : IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
/// <summary>
|
|||
/// 用户标识
|
|||
/// </summary>
|
|||
public Guid UserId { get; set; } |
|||
/// <summary>
|
|||
/// 好友标识
|
|||
/// </summary>
|
|||
public Guid FrientId { get; set; } |
|||
/// <summary>
|
|||
/// 状态
|
|||
/// </summary>
|
|||
public UserFriendStatus Status { get; set; } |
|||
} |
|||
} |
|||
@ -1,4 +1,4 @@ |
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
namespace LINGYUN.Abp.MessageService.Group |
|||
{ |
|||
public class ChatGroupConsts |
|||
{ |
|||
@ -1,34 +1,99 @@ |
|||
namespace LINGYUN.Abp.MessageService |
|||
{ |
|||
/// <summary>
|
|||
/// 消息系统错误码设计
|
|||
/// 状态码分为两部分 前2位领域 后3位状态
|
|||
///
|
|||
/// <list type="table">
|
|||
/// 领域部分:
|
|||
/// 01 输入
|
|||
/// 02 群组
|
|||
/// 03 用户
|
|||
/// 04 应用
|
|||
/// 05 内部
|
|||
/// 10 输出
|
|||
/// </list>
|
|||
///
|
|||
/// <list type="table">
|
|||
/// 状态部分:
|
|||
/// 200-299 成功
|
|||
/// 300-399 成功但有后续操作
|
|||
/// 400-499 业务异常
|
|||
/// 500-599 内部异常
|
|||
/// 900-999 输入输出异常
|
|||
/// </list>
|
|||
///
|
|||
/// </summary>
|
|||
public class MessageServiceErrorCodes |
|||
{ |
|||
public const string Namespace = "LINGYUN.Abp.Message"; |
|||
/// <summary>
|
|||
/// 管理员已开启全员禁言
|
|||
/// 消息不完整
|
|||
/// </summary>
|
|||
public const string GroupNotAllowedToSpeak = "Messages.Group:1001"; |
|||
public const string MessageIncomplete = Namespace + ":01400"; |
|||
/// <summary>
|
|||
/// 管理员不允许匿名发言
|
|||
/// 您还未加入群组,不能进行操作
|
|||
/// </summary>
|
|||
public const string YouHaveNotJoinedGroup = Namespace + ":01401"; |
|||
/// <summary>
|
|||
/// 已发送群组申请,等待管理员同意
|
|||
/// </summary>
|
|||
public const string GroupNotAllowedToSpeakAnonymously = "Messages.Group:1002"; |
|||
public const string YouHaveAddingToGroup = Namespace + ":02301"; |
|||
/// <summary>
|
|||
/// 你需要验证问题才能加入群聊
|
|||
/// </summary>
|
|||
public const string YouNeedValidationQuestingByAddGroup = Namespace + ":02302"; |
|||
/// <summary>
|
|||
/// 管理员已开启全员禁言
|
|||
/// </summary>
|
|||
public const string GroupNotAllowedToSpeak = Namespace + ":02400"; |
|||
/// <summary>
|
|||
/// 管理员已禁止用户发言
|
|||
/// </summary>
|
|||
public const string GroupUserHasBlack = "Messages.Group:1003"; |
|||
public const string GroupUserHasBlack = Namespace + ":02403"; |
|||
/// <summary>
|
|||
/// 用户已将发信人拉黑
|
|||
/// 管理员不允许匿名发言
|
|||
/// </summary>
|
|||
public const string GroupNotAllowedToSpeakAnonymously = Namespace + ":02401"; |
|||
/// <summary>
|
|||
/// 群组不存在或已解散
|
|||
/// </summary>
|
|||
public const string UserHasBlack = "Messages.User:1003"; |
|||
public const string GroupNotFount = Namespace + ":02404"; |
|||
/// <summary>
|
|||
/// 用户已拒接所有消息
|
|||
/// </summary>
|
|||
public const string UserHasRejectAllMessage = "Messages.User:1001"; |
|||
public const string UserHasRejectAllMessage = Namespace + ":03400"; |
|||
/// <summary>
|
|||
/// 用户已将发信人拉黑
|
|||
/// </summary>
|
|||
public const string UserHasBlack = Namespace + ":03401"; |
|||
/// <summary>
|
|||
/// 用户不允许匿名发言
|
|||
/// </summary>
|
|||
public const string UserNotAllowedToSpeakAnonymously = "Messages.User:1002"; |
|||
public const string UserNotAllowedToSpeakAnonymously = Namespace + ":03402"; |
|||
/// <summary>
|
|||
/// 用户不接收非好友发言
|
|||
/// </summary>
|
|||
public const string UserHasRejectNotFriendMessage = Namespace + ":03403"; |
|||
/// <summary>
|
|||
/// 接收消息用户不存在或已注销
|
|||
/// </summary>
|
|||
public const string UseNotFount = Namespace + ":03404"; |
|||
/// <summary>
|
|||
/// 用户拒绝添加好友
|
|||
/// </summary>
|
|||
public const string UseRefuseToAddFriend = Namespace + ":03410"; |
|||
/// <summary>
|
|||
/// 对方已是您的好友或已发送验证请求,不能重复操作
|
|||
/// </summary>
|
|||
public const string UseHasBeenAddedTheFriendOrSendAuthorization = Namespace + ":03411"; |
|||
/// <summary>
|
|||
/// 已发送好友申请,等待对方同意
|
|||
/// </summary>
|
|||
public const string YouHaveAddingTheUserToFriend = Namespace + ":03301"; |
|||
/// <summary>
|
|||
/// 已经添加对方为好友
|
|||
/// 你需要验证问题才能添加好友
|
|||
/// </summary>
|
|||
public const string YouHaveAddedTheUserToFriend = "Messages.UserFriend:1001"; |
|||
public const string YouNeedValidationQuestingByAddFriend = Namespace + ":03302"; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,14 @@ |
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
public static class ChatNotificationNames |
|||
{ |
|||
public const string GroupName = "LINGYUN.Abp.IM.Chat"; |
|||
|
|||
public static class UserFriend |
|||
{ |
|||
public const string Default = GroupName + ".UserFriend"; |
|||
|
|||
public const string NeedValidation = Default + ".NeedValidation"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
/// <summary>
|
|||
/// 用户黑名单
|
|||
/// </summary>
|
|||
public class UserChatBlack : CreationAuditedEntity<long>, IMultiTenant |
|||
{ |
|||
/// <summary>
|
|||
/// 租户
|
|||
/// </summary>
|
|||
public virtual Guid? TenantId { get; protected set; } |
|||
/// <summary>
|
|||
/// 用户标识
|
|||
/// </summary>
|
|||
public virtual Guid UserId { get; protected set; } |
|||
/// <summary>
|
|||
/// 拉黑的用户
|
|||
/// </summary>
|
|||
public virtual Guid ShieldUserId { get; protected set; } |
|||
protected UserChatBlack() { } |
|||
public UserChatBlack(Guid userId, Guid shieldUserId, Guid? tenantId) |
|||
{ |
|||
UserId = userId; |
|||
ShieldUserId = shieldUserId; |
|||
TenantId = tenantId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using LINGYUN.Abp.IM.Contract; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
[Serializable] |
|||
public class UserFriendCacheItem |
|||
{ |
|||
public List<UserFriend> Friends { get; set; } |
|||
|
|||
public UserFriendCacheItem() |
|||
{ |
|||
Friends = new List<UserFriend>(); |
|||
} |
|||
|
|||
public UserFriendCacheItem(List<UserFriend> friends) |
|||
{ |
|||
Friends = friends; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(string userId) |
|||
{ |
|||
return "uid:" + userId; |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
/// <summary>
|
|||
/// 用户特别关注
|
|||
/// </summary>
|
|||
public class UserSpecialFocus : CreationAuditedEntity<long>, IMultiTenant |
|||
{ |
|||
/// <summary>
|
|||
/// 租户
|
|||
/// </summary>
|
|||
public virtual Guid? TenantId { get; protected set; } |
|||
/// <summary>
|
|||
/// 用户标识
|
|||
/// </summary>
|
|||
public virtual Guid UserId { get; protected set; } |
|||
/// <summary>
|
|||
/// 关注的用户
|
|||
/// </summary>
|
|||
public virtual Guid FocusUserId { get; protected set; } |
|||
protected UserSpecialFocus() { } |
|||
public UserSpecialFocus(Guid userId, Guid focusUserId, Guid? tenantId) |
|||
{ |
|||
UserId = userId; |
|||
FocusUserId = focusUserId; |
|||
TenantId = tenantId; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
using LINGYUN.Abp.IM.Messages; |
|||
using LINGYUN.Abp.MessageService.Chat; |
|||
using LINGYUN.Abp.Notifications; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.EventBus.Local |
|||
{ |
|||
public class UserChatFriendEventHandler : |
|||
ILocalEventHandler<EntityCreatedEventData<UserChatFriend>>, |
|||
ILocalEventHandler<EntityDeletedEventData<UserChatFriend>>, |
|||
ILocalEventHandler<EntityUpdatedEventData<UserChatFriend>>, |
|||
ILocalEventHandler<UserChatFriendEto>, |
|||
ITransientDependency |
|||
{ |
|||
private ILogger _logger; |
|||
private IMessageSender _messageSender; |
|||
private INotificationDispatcher _dispatcher; |
|||
private IDistributedCache<UserFriendCacheItem> _cache; |
|||
|
|||
public UserChatFriendEventHandler( |
|||
IMessageSender messageSender, |
|||
INotificationDispatcher dispatcher, |
|||
ILogger<UserChatFriendEventHandler> logger) |
|||
{ |
|||
_logger = logger; |
|||
_dispatcher = dispatcher; |
|||
_messageSender = messageSender; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityCreatedEventData<UserChatFriend> eventData) |
|||
{ |
|||
switch (eventData.Entity.Status) |
|||
{ |
|||
case IM.Contract.UserFriendStatus.Added: |
|||
await SendFriendAddedMessageAsync(eventData.Entity.UserId, eventData.Entity.FrientId, eventData.Entity.TenantId); |
|||
break; |
|||
case IM.Contract.UserFriendStatus.NeedValidation: |
|||
await SendFriendValidationNotiferAsync(eventData.Entity.UserId, eventData.Entity.FrientId, eventData.Entity.TenantId); |
|||
break; |
|||
} |
|||
await RemoveUserFriendCacheItemAsync(eventData.Entity.UserId); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityDeletedEventData<UserChatFriend> eventData) |
|||
{ |
|||
await RemoveUserFriendCacheItemAsync(eventData.Entity.UserId); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityUpdatedEventData<UserChatFriend> eventData) |
|||
{ |
|||
await RemoveUserFriendCacheItemAsync(eventData.Entity.UserId); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(UserChatFriendEto eventData) |
|||
{ |
|||
if (eventData.Status == IM.Contract.UserFriendStatus.Added) |
|||
{ |
|||
await SendFriendAddedMessageAsync(eventData.UserId, eventData.FrientId, eventData.TenantId); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task SendFriendAddedMessageAsync(Guid userId, Guid friendId, Guid? tenantId = null) |
|||
{ |
|||
// 发送添加好友的第一条消息
|
|||
} |
|||
|
|||
protected virtual async Task SendFriendValidationNotiferAsync(Guid userId, Guid friendId, Guid? tenantId = null) |
|||
{ |
|||
// 发送好友验证通知
|
|||
} |
|||
|
|||
protected virtual async Task RemoveUserFriendCacheItemAsync(Guid userId) |
|||
{ |
|||
// 移除好友缓存
|
|||
await _cache.RemoveAsync(UserFriendCacheItem.CalculateCacheKey(userId.ToString())); |
|||
} |
|||
} |
|||
} |
|||
@ -1,7 +1,8 @@ |
|||
using LINGYUN.Abp.IM.Messages; |
|||
using LINGYUN.Abp.MessageService.Chat; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
namespace LINGYUN.Abp.MessageService.Group |
|||
{ |
|||
public class GroupMessage : Message |
|||
{ |
|||
@ -0,0 +1,19 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Group |
|||
{ |
|||
public static class IGroupRepositoryExtensions |
|||
{ |
|||
public static async Task<ChatGroup> GetByIdAsync( |
|||
this IGroupRepository repository, |
|||
long id, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var group = await repository.FindByIdAsync(id, cancellationToken); |
|||
|
|||
return group ?? throw new BusinessException(MessageServiceErrorCodes.GroupNotFount); |
|||
} |
|||
} |
|||
} |
|||
@ -1,305 +0,0 @@ |
|||
using LINGYUN.Abp.IM.Group; |
|||
using LINGYUN.Abp.MessageService.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Internal; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Chat |
|||
{ |
|||
public class EfCoreUserChatGroupRepository : EfCoreRepository<IMessageServiceDbContext, UserChatGroup, long>, |
|||
IUserChatGroupRepository, ITransientDependency |
|||
{ |
|||
public EfCoreUserChatGroupRepository( |
|||
IDbContextProvider<IMessageServiceDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public virtual async Task<GroupUserCard> GetMemberAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId && ugc.UserId == userId |
|||
select new GroupUserCard |
|||
{ |
|||
TenantId = uc.TenantId, |
|||
UserId = uc.UserId, |
|||
UserName = uc.UserName, |
|||
Age = uc.Age, |
|||
AvatarUrl = uc.AvatarUrl, |
|||
IsAdmin = ugc.IsAdmin, |
|||
IsSuperAdmin = gp.AdminUserId == uc.UserId, |
|||
GroupId = gp.GroupId, |
|||
Birthday = uc.Birthday, |
|||
Description = uc.Description, |
|||
NickName = ugc.NickName ?? uc.NickName, |
|||
Sex = uc.Sex, |
|||
Sign = uc.Sign |
|||
}; |
|||
|
|||
return await cardQuery |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<List<GroupUserCard>> GetMembersAsync( |
|||
long groupId, |
|||
string sorting = nameof(UserChatCard.UserId), |
|||
bool reverse = false, |
|||
int skipCount = 0, |
|||
int maxResultCount = 10, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
sorting ??= nameof(UserChatCard.UserId); |
|||
sorting = reverse ? sorting + " desc" : sorting; |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId |
|||
select new GroupUserCard |
|||
{ |
|||
TenantId = uc.TenantId, |
|||
UserId = uc.UserId, |
|||
UserName = uc.UserName, |
|||
Age = uc.Age, |
|||
AvatarUrl = uc.AvatarUrl, |
|||
IsAdmin = ugc.IsAdmin, |
|||
IsSuperAdmin = gp.AdminUserId == uc.UserId, |
|||
GroupId = gp.GroupId, |
|||
Birthday = uc.Birthday, |
|||
Description = uc.Description, |
|||
NickName = ugc.NickName ?? uc.NickName, |
|||
Sex = uc.Sex, |
|||
Sign = uc.Sign |
|||
}; |
|||
|
|||
return await cardQuery |
|||
.OrderBy(sorting ?? nameof(UserChatCard.UserId)) |
|||
.PageBy(skipCount, maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<int> GetMembersCountAsync( |
|||
long groupId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId |
|||
select ucg; |
|||
|
|||
return await cardQuery |
|||
.CountAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<bool> MemberHasInGroupAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await DbContext.Set<UserChatGroup>() |
|||
.AnyAsync(ucg => ucg.GroupId == groupId && ucg.UserId == userId, |
|||
GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<List<Group>> GetMemberGroupsAsync( |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var groupQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
where ucg.UserId.Equals(userId) |
|||
group ucg by new |
|||
{ |
|||
gp.AllowAnonymous, |
|||
gp.AllowSendMessage, |
|||
gp.MaxUserCount, |
|||
gp.Name |
|||
} |
|||
into cg |
|||
select new Group |
|||
{ |
|||
AllowAnonymous = cg.Key.AllowAnonymous, |
|||
AllowSendMessage = cg.Key.AllowSendMessage, |
|||
MaxUserLength = cg.Key.MaxUserCount, |
|||
Name = cg.Key.Name, |
|||
GroupUserCount = cg.Count() |
|||
}; |
|||
|
|||
return await groupQuery |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task RemoveMemberFormGroupAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
await DeleteAsync(ucg => ucg.GroupId == groupId && ucg.UserId == userId); |
|||
} |
|||
|
|||
//public virtual async Task<List<GroupUserCard>> GetGroupUsersAsync(
|
|||
// long groupId,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// // TODO: 急需单元测试,对这段代码不是太自信...
|
|||
// var groupUsers = await (from cg in DbContext.Set<ChatGroup>()
|
|||
// join ucg in DbContext.Set<UserChatGroup>()
|
|||
// on cg.GroupId equals ucg.GroupId
|
|||
// join ugc in DbContext.Set<UserGroupCard>()
|
|||
// on ucg.UserId equals ugc.UserId
|
|||
// where cg.GroupId.Equals(groupId)
|
|||
// select new GroupUserCard
|
|||
// {
|
|||
// GroupId = ucg.GroupId,
|
|||
// IsSuperAdmin = ugc.UserId == cg.AdminUserId,
|
|||
// IsAdmin = ugc.IsAdmin,
|
|||
// TenantId = ucg.TenantId,
|
|||
// UserId = ucg.UserId
|
|||
// })
|
|||
// .Distinct()
|
|||
// .AsNoTracking()
|
|||
// .ToListAsync(GetCancellationToken(cancellationToken));
|
|||
// return groupUsers;
|
|||
//}
|
|||
|
|||
//public virtual async Task<GroupUserCard> GetMemberAsync(
|
|||
// long groupId,
|
|||
// Guid userId,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// var groupUserCard = await (from cg in DbContext.Set<ChatGroup>()
|
|||
// join ucg in DbContext.Set<UserChatGroup>().DefaultIfEmpty()
|
|||
// on cg.GroupId equals ucg.GroupId
|
|||
// join ucc in DbContext.Set<UserChatCard>().DefaultIfEmpty()
|
|||
// on ucg.UserId equals ucc.UserId
|
|||
// join cga in DbContext.Set<ChatGroupAdmin>().DefaultIfEmpty()
|
|||
// on cg.GroupId equals cga.GroupId
|
|||
// where ucg.GroupId.Equals(groupId) && cga.UserId.Equals(userId)
|
|||
// select new GroupUserCard
|
|||
// {
|
|||
// IsSuperAdmin = cga != null && cga.IsSuperAdmin,
|
|||
// IsAdmin = cga != null,//能查到数据就是管理员
|
|||
// GroupId = ucg.GroupId,
|
|||
// UserId = ucg.UserId,
|
|||
// TenantId = ucg.TenantId,
|
|||
// Permissions = new Dictionary<string, bool>
|
|||
// {
|
|||
// { nameof(ChatGroupAdmin.AllowAddPeople), cga != null && cga.AllowAddPeople },
|
|||
// { nameof(ChatGroupAdmin.AllowDissolveGroup), cga != null && cga.AllowDissolveGroup },
|
|||
// { nameof(ChatGroupAdmin.AllowKickPeople), cga != null && cga.AllowKickPeople },
|
|||
// { nameof(ChatGroupAdmin.AllowSendNotice), cga != null && cga.AllowSendNotice },
|
|||
// { nameof(ChatGroupAdmin.AllowSilence), cga != null && cga.AllowSilence },
|
|||
// { nameof(ChatGroupAdmin.IsSuperAdmin), cga != null && cga.IsSuperAdmin }
|
|||
// }
|
|||
// })
|
|||
// .AsNoTracking()
|
|||
// .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
|
|||
|
|||
// return groupUserCard;
|
|||
//}
|
|||
|
|||
//public virtual Task<List<UserGroup>> GetUsersAsync(
|
|||
// long groupId,
|
|||
// string filter = "",
|
|||
// string sorting = nameof(UserGroup.UserId),
|
|||
// bool reverse = false,
|
|||
// int skipCount = 0,
|
|||
// int maxResultCount = 10,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// sorting = reverse ? sorting + " desc" : sorting;
|
|||
// // TODO: 复杂的实现,暂时无关紧要,后期再说 :)
|
|||
// throw new NotImplementedException();
|
|||
//}
|
|||
|
|||
//public virtual Task<int> GetMembersCountAsync(
|
|||
// long groupId,
|
|||
// string filter = "",
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// var ss = (from ucg in DbContext.Set<UserChatGroup>()
|
|||
// join cg in DbContext.Set<ChatGroup>() on ucg.GroupId equals cg.GroupId
|
|||
// select cg)
|
|||
// .WhereIf(!filter.IsNullOrWhiteSpace(),)
|
|||
|
|||
// // TODO: 复杂的实现,暂时无关紧要,后期再说 :)
|
|||
// //throw new NotImplementedException();
|
|||
//}
|
|||
|
|||
//public virtual async Task<UserChatGroup> GetUserGroupAsync(
|
|||
// long groupId,
|
|||
// Guid userId,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// return await DbSet.Where(x => x.GroupId.Equals(groupId) && x.UserId.Equals(userId))
|
|||
// .AsNoTracking()
|
|||
// .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
|
|||
//}
|
|||
|
|||
//public virtual async Task<List<Group>> GetUserGroupsAsync(
|
|||
// Guid userId,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// // TODO: 急需单元测试,对这段代码不是太自信...
|
|||
// var userGroups = await (from ucg in DbSet
|
|||
// join cg in DbContext.Set<ChatGroup>()
|
|||
// on ucg.GroupId equals cg.GroupId
|
|||
// group cg by new
|
|||
// {
|
|||
// cg.GroupId,
|
|||
// cg.Name,
|
|||
// cg.AllowAnonymous,
|
|||
// cg.AllowSendMessage,
|
|||
// cg.MaxUserCount
|
|||
// }
|
|||
// into ug
|
|||
// orderby ug.Key.GroupId descending
|
|||
// select new Group
|
|||
// {
|
|||
// AllowAnonymous = ug.Key.AllowAnonymous,
|
|||
// AllowSendMessage = ug.Key.AllowSendMessage,
|
|||
// GroupUserCount = ug.Count(),
|
|||
// MaxUserLength = ug.Key.MaxUserCount,
|
|||
// Name = ug.Key.Name
|
|||
// })
|
|||
// .Distinct()
|
|||
// .ToListAsync(GetCancellationToken(cancellationToken));
|
|||
|
|||
// return userGroups;
|
|||
//}
|
|||
|
|||
//public virtual async Task<bool> MemberHasInGroupAsync(
|
|||
// long groupId,
|
|||
// Guid userId,
|
|||
// CancellationToken cancellationToken = default)
|
|||
//{
|
|||
// return await DbSet
|
|||
// .AnyAsync(x => x.GroupId.Equals(groupId) && x.UserId.Equals(userId), GetCancellationToken(cancellationToken));
|
|||
//}
|
|||
} |
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
using LINGYUN.Abp.IM.Group; |
|||
using LINGYUN.Abp.MessageService.Chat; |
|||
using LINGYUN.Abp.MessageService.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Internal; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Group |
|||
{ |
|||
public class EfCoreUserChatGroupRepository : EfCoreRepository<IMessageServiceDbContext, UserChatGroup, long>, |
|||
IUserChatGroupRepository, ITransientDependency |
|||
{ |
|||
public EfCoreUserChatGroupRepository( |
|||
IDbContextProvider<IMessageServiceDbContext> dbContextProvider) : base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public virtual async Task<GroupUserCard> GetMemberAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId && ugc.UserId == userId |
|||
select new GroupUserCard |
|||
{ |
|||
TenantId = uc.TenantId, |
|||
UserId = uc.UserId, |
|||
UserName = uc.UserName, |
|||
Age = uc.Age, |
|||
AvatarUrl = uc.AvatarUrl, |
|||
IsAdmin = ugc.IsAdmin, |
|||
IsSuperAdmin = gp.AdminUserId == uc.UserId, |
|||
GroupId = gp.GroupId, |
|||
Birthday = uc.Birthday, |
|||
Description = uc.Description, |
|||
NickName = ugc.NickName ?? uc.NickName, |
|||
Sex = uc.Sex, |
|||
Sign = uc.Sign |
|||
}; |
|||
|
|||
return await cardQuery |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<List<GroupUserCard>> GetMembersAsync( |
|||
long groupId, |
|||
string sorting = nameof(UserChatCard.UserId), |
|||
bool reverse = false, |
|||
int skipCount = 0, |
|||
int maxResultCount = 10, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
sorting ??= nameof(UserChatCard.UserId); |
|||
sorting = reverse ? sorting + " desc" : sorting; |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId |
|||
select new GroupUserCard |
|||
{ |
|||
TenantId = uc.TenantId, |
|||
UserId = uc.UserId, |
|||
UserName = uc.UserName, |
|||
Age = uc.Age, |
|||
AvatarUrl = uc.AvatarUrl, |
|||
IsAdmin = ugc.IsAdmin, |
|||
IsSuperAdmin = gp.AdminUserId == uc.UserId, |
|||
GroupId = gp.GroupId, |
|||
Birthday = uc.Birthday, |
|||
Description = uc.Description, |
|||
NickName = ugc.NickName ?? uc.NickName, |
|||
Sex = uc.Sex, |
|||
Sign = uc.Sign |
|||
}; |
|||
|
|||
return await cardQuery |
|||
.OrderBy(sorting ?? nameof(UserChatCard.UserId)) |
|||
.PageBy(skipCount, maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<int> GetMembersCountAsync( |
|||
long groupId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var cardQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
join ugc in DbContext.Set<UserGroupCard>() |
|||
on ucg.UserId equals ugc.UserId |
|||
join uc in DbContext.Set<UserChatCard>() |
|||
on ugc.UserId equals uc.UserId |
|||
where gp.GroupId == groupId |
|||
select ucg; |
|||
|
|||
return await cardQuery |
|||
.CountAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<bool> MemberHasInGroupAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await DbContext.Set<UserChatGroup>() |
|||
.AnyAsync(ucg => ucg.GroupId == groupId && ucg.UserId == userId, |
|||
GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<List<IM.Group.Group>> GetMemberGroupsAsync( |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var groupQuery = from gp in DbContext.Set<ChatGroup>() |
|||
join ucg in DbContext.Set<UserChatGroup>() |
|||
on gp.GroupId equals ucg.GroupId |
|||
where ucg.UserId.Equals(userId) |
|||
group ucg by new |
|||
{ |
|||
gp.AllowAnonymous, |
|||
gp.AllowSendMessage, |
|||
gp.MaxUserCount, |
|||
gp.Name |
|||
} |
|||
into cg |
|||
select new IM.Group.Group |
|||
{ |
|||
AllowAnonymous = cg.Key.AllowAnonymous, |
|||
AllowSendMessage = cg.Key.AllowSendMessage, |
|||
MaxUserLength = cg.Key.MaxUserCount, |
|||
Name = cg.Key.Name, |
|||
GroupUserCount = cg.Count() |
|||
}; |
|||
|
|||
return await groupQuery |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task RemoveMemberFormGroupAsync( |
|||
long groupId, |
|||
Guid userId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
await DeleteAsync(ucg => ucg.GroupId == groupId && ucg.UserId == userId); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
Loading…
Reference in new issue