Browse Source

Optimize instant messaging module

pull/116/head
cKey 5 years ago
parent
commit
689d126c3e
  1. 9
      aspnet-core/LINGYUN.MicroService.sln
  2. 4
      aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/AbpIMSignalRModule.cs
  3. 82
      aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/Hubs/MessagesHub.cs
  4. 67
      aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/Messages/SignalRMessageSender.cs
  5. 16
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/IFriendStore.cs
  6. 12
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/UserAddFriendResult.cs
  7. 14
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/UserFriendStatus.cs
  8. 8
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Group/IUserGroupStore.cs
  9. 70
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/ChatMessage.cs
  10. 47
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/MessageSenderBase.cs
  11. 22
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/MessageType.cs
  12. 26
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/NullMessageSender.cs
  13. 4
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/AbpNotificationsSignalRModule.cs
  14. 24
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/Hubs/NotificationsHub.cs
  15. 82
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/OnlineClientHubBase.cs
  16. 37
      aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs
  17. 11
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationData.cs
  18. 8
      aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/Class1.cs
  19. 18
      aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN.Abp.RealTime.SignalR.csproj
  20. 11
      aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN/Abp/RealTime/SignalR/AbpRealTimeSignalRModule.cs
  21. 31
      aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN/Abp/RealTime/SignalR/Hubs/OnlineClientHubBase.cs
  22. 4
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application.Contracts/LINGYUN.Abp.MessageService.Application.Contracts.csproj
  23. 63
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application.Contracts/LINGYUN/Abp/MessageService/Chat/IChatAppService.cs
  24. 124
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application/LINGYUN/Abp/MessageService/Chat/ChatAppService.cs
  25. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application/LINGYUN/Abp/MessageService/Chat/MyFriendAppService.cs
  26. 23
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Chat/UserChatFriendEto.cs
  27. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Group/ChatGroupConsts.cs
  28. 87
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/MessageServiceErrorCodes.cs
  29. 1
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN.Abp.MessageService.Domain.csproj
  30. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/AbpMessageServiceDomainModule.cs
  31. 14
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/ChatNotificationNames.cs
  32. 81
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/FriendStore.cs
  33. 1
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IMessageRepository.cs
  34. 10
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IUserChatSettingRepository.cs
  35. 39
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/MessageStore.cs
  36. 32
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatBlack.cs
  37. 25
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatFriend.cs
  38. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatFriendGroup.cs
  39. 27
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserFriendCacheItem.cs
  40. 32
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserSpecialFocus.cs
  41. 84
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserChatFriendEventHandler.cs
  42. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/ChatGroup.cs
  43. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/GroupChatBlack.cs
  44. 3
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/GroupMessage.cs
  45. 4
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IGroupRepository.cs
  46. 19
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IGroupRepositoryExtensions.cs
  47. 6
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IUserChatGroupRepository.cs
  48. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserChatGroup.cs
  49. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserGroupCard.cs
  50. 14
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserGroupStore.cs
  51. 24
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Localization/Resources/en.json
  52. 24
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Localization/Resources/zh-Hans.json
  53. 6
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreMessageRepository.cs
  54. 14
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatFriendRepository.cs
  55. 305
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatGroupRepository.cs
  56. 8
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatSettingRepository.cs
  57. 25
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/EntityFrameworkCore/MessageServiceDbContextModelCreatingExtensions.cs
  58. 4
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Group/EfCoreGroupRepository.cs
  59. 166
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Group/EfCoreUserChatGroupRepository.cs
  60. 42
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.HttpApi/LINGYUN/Abp/MessageService/Chat/ChatController.cs
  61. BIN
      aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db
  62. 2
      aspnet-core/tests/LINGYUN.Abp.Features.LimitValidation.Redis.Tests/LINGYUN.Abp.Features.LimitValidation.Redis.Tests.csproj
  63. 2
      aspnet-core/tests/LINGYUN.Abp.Features.LimitValidation.Tests/LINGYUN.Abp.Features.LimitValidation.Tests.csproj
  64. 81
      vueJs/src/components/InstantMessage/index.vue
  65. 3
      vueJs/src/components/Notification/index.vue

9
aspnet-core/LINGYUN.MicroService.sln

@ -251,7 +251,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.PermissionManag
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Web", "modules\account\LINGYUN.Abp.Account.Web\LINGYUN.Abp.Account.Web.csproj", "{5F43141B-6C63-4547-B9D6-D69EC3D7CA7E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Account.Web", "modules\account\LINGYUN.Abp.Account.Web\LINGYUN.Abp.Account.Web.csproj", "{5F43141B-6C63-4547-B9D6-D69EC3D7CA7E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.AspNetCore.SignalR.JwtToken", "modules\common\LINGYUN.Abp.AspNetCore.SignalR\LINGYUN.Abp.AspNetCore.SignalR.JwtToken.csproj", "{A66D48C9-F141-4111-9169-CEB64AFFF61D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.SignalR.JwtToken", "modules\common\LINGYUN.Abp.AspNetCore.SignalR\LINGYUN.Abp.AspNetCore.SignalR.JwtToken.csproj", "{A66D48C9-F141-4111-9169-CEB64AFFF61D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.RealTime.SignalR", "modules\common\LINGYUN.Abp.RealTime.SignalR\LINGYUN.Abp.RealTime.SignalR.csproj", "{524276E1-053D-4191-ABF7-4CDA01BFFBC3}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -671,6 +673,10 @@ Global
{A66D48C9-F141-4111-9169-CEB64AFFF61D}.Debug|Any CPU.Build.0 = Debug|Any CPU {A66D48C9-F141-4111-9169-CEB64AFFF61D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A66D48C9-F141-4111-9169-CEB64AFFF61D}.Release|Any CPU.ActiveCfg = Release|Any CPU {A66D48C9-F141-4111-9169-CEB64AFFF61D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A66D48C9-F141-4111-9169-CEB64AFFF61D}.Release|Any CPU.Build.0 = Release|Any CPU {A66D48C9-F141-4111-9169-CEB64AFFF61D}.Release|Any CPU.Build.0 = Release|Any CPU
{524276E1-053D-4191-ABF7-4CDA01BFFBC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{524276E1-053D-4191-ABF7-4CDA01BFFBC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{524276E1-053D-4191-ABF7-4CDA01BFFBC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{524276E1-053D-4191-ABF7-4CDA01BFFBC3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -798,6 +804,7 @@ Global
{2D377D3A-70EC-4BB3-9F4C-6C933693DA98} = {52B5D4F7-237B-4E0A-A167-68442164F70A} {2D377D3A-70EC-4BB3-9F4C-6C933693DA98} = {52B5D4F7-237B-4E0A-A167-68442164F70A}
{5F43141B-6C63-4547-B9D6-D69EC3D7CA7E} = {9E72FEB9-A626-4312-892B-CDD043879758} {5F43141B-6C63-4547-B9D6-D69EC3D7CA7E} = {9E72FEB9-A626-4312-892B-CDD043879758}
{A66D48C9-F141-4111-9169-CEB64AFFF61D} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E} {A66D48C9-F141-4111-9169-CEB64AFFF61D} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
{524276E1-053D-4191-ABF7-4CDA01BFFBC3} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}

4
aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/AbpIMSignalRModule.cs

@ -1,12 +1,12 @@
using LINGYUN.Abp.AspNetCore.SignalR.JwtToken; using LINGYUN.Abp.AspNetCore.SignalR.JwtToken;
using LINGYUN.Abp.RealTime; using LINGYUN.Abp.RealTime.SignalR;
using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace LINGYUN.Abp.IM.SignalR namespace LINGYUN.Abp.IM.SignalR
{ {
[DependsOn( [DependsOn(
typeof(AbpRealTimeModule), typeof(AbpRealTimeSignalRModule),
typeof(AbpAspNetCoreSignalRModule), typeof(AbpAspNetCoreSignalRModule),
typeof(AbpAspNetCoreSignalRJwtTokenModule))] typeof(AbpAspNetCoreSignalRJwtTokenModule))]
public class AbpIMSignalRModule : AbpModule public class AbpIMSignalRModule : AbpModule

82
aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/Hubs/MessagesHub.cs

@ -1,11 +1,16 @@
using LINGYUN.Abp.IM.Contract; using LINGYUN.Abp.IM.Contract;
using LINGYUN.Abp.IM.Group;
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.RealTime.Client; using LINGYUN.Abp.RealTime.Client;
using LINGYUN.Abp.RealTime.SignalR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Users; using Volo.Abp.Users;
@ -16,13 +21,72 @@ namespace LINGYUN.Abp.IM.SignalR.Hubs
{ {
protected IFriendStore FriendStore { get; } protected IFriendStore FriendStore { get; }
protected IMessageStore MessageStore { get; } protected IMessageStore MessageStore { get; }
protected IUserGroupStore UserGroupStore { get; }
public MessagesHub( public MessagesHub(
IFriendStore friendStore, IFriendStore friendStore,
IMessageStore messageStore) IMessageStore messageStore,
IUserGroupStore userGroupStore)
{ {
FriendStore = friendStore; FriendStore = friendStore;
MessageStore = messageStore; MessageStore = messageStore;
UserGroupStore = userGroupStore;
}
protected override async Task OnClientConnectedAsync(IOnlineClient client)
{
// 加入通讯组
var userGroups = await UserGroupStore.GetUserGroupsAsync(client.TenantId, client.UserId.Value);
foreach (var group in userGroups)
{
await Groups.AddToGroupAsync(client.ConnectionId, group.Name);
var groupClient = Clients.Group(group.Name);
if (groupClient != null)
{
// 发送用户上线通知
await groupClient.SendAsync("onUserOnlined", client.TenantId, client.UserId.Value);
}
}
// 发送好友上线通知
var userFriends = await FriendStore.GetListAsync(client.TenantId, client.UserId.Value);
if (userFriends.Count > 0)
{
var friendClientIds = userFriends.Select(friend => friend.FriendId.ToString()).ToImmutableArray();
var userClients = Clients.Users(friendClientIds);
if (userClients != null)
{
await userClients.SendAsync("onUserOnlined", client.TenantId, client.UserId.Value);
}
}
}
protected override async Task OnClientDisconnectedAsync(IOnlineClient client)
{
// 从通讯组断开会话
var userGroups = await UserGroupStore.GetUserGroupsAsync(client.TenantId, client.UserId.Value);
foreach (var group in userGroups)
{
await Groups.RemoveFromGroupAsync(client.ConnectionId, group.Name);
var groupClient = Clients.Group(group.Name);
if (groupClient != null)
{
// 发送用户下线指令
await groupClient.SendAsync("onUserOfflined", client.TenantId, client.UserId.Value);
}
}
// 发送好友下线通知
var userFriends = await FriendStore.GetListAsync(client.TenantId, client.UserId.Value);
if (userFriends.Count > 0)
{
var friendClientIds = userFriends.Select(friend => friend.FriendId.ToString()).ToImmutableArray();
var userClients = Clients.Users(friendClientIds);
if (userClients != null)
{
await userClients.SendAsync("onUserOfflined", client.TenantId, client.UserId.Value);
}
}
} }
[HubMethodName("LastContactFriends")] [HubMethodName("LastContactFriends")]
@ -116,8 +180,6 @@ namespace LINGYUN.Abp.IM.SignalR.Hubs
// 持久化 // 持久化
await MessageStore.StoreMessageAsync(chatMessage); await MessageStore.StoreMessageAsync(chatMessage);
try
{
if (!chatMessage.GroupId.IsNullOrWhiteSpace()) if (!chatMessage.GroupId.IsNullOrWhiteSpace())
{ {
await SendMessageToGroupAsync(chatMessage); await SendMessageToGroupAsync(chatMessage);
@ -127,14 +189,6 @@ namespace LINGYUN.Abp.IM.SignalR.Hubs
await SendMessageToUserAsync(chatMessage); 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 virtual async Task SendMessageToGroupAsync(ChatMessage chatMessage) protected virtual async Task SendMessageToGroupAsync(ChatMessage chatMessage)
{ {
@ -153,11 +207,6 @@ namespace LINGYUN.Abp.IM.SignalR.Hubs
var onlineClientContext = new OnlineClientContext(chatMessage.TenantId, chatMessage.ToUserId.GetValueOrDefault()); var onlineClientContext = new OnlineClientContext(chatMessage.TenantId, chatMessage.ToUserId.GetValueOrDefault());
var onlineClients = OnlineClientManager.GetAllByContext(onlineClientContext); var onlineClients = OnlineClientManager.GetAllByContext(onlineClientContext);
// 需要捕捉每一个发送任务的异常吗?
// var onlineClientConnections = onlineClients.Select(c => c.ConnectionId).ToImmutableList();
// var signalRClient = Clients.Clients(onlineClientConnections);
// await signalRClient.SendAsync("getChatMessage", chatMessage);
foreach (var onlineClient in onlineClients) foreach (var onlineClient in onlineClients)
{ {
try try
@ -172,6 +221,7 @@ namespace LINGYUN.Abp.IM.SignalR.Hubs
} }
catch (Exception ex) catch (Exception ex)
{ {
// 发送异常记录就行了,因为消息已经持久化
Logger.LogWarning("Could not send message to user: {0}", chatMessage.ToUserId); Logger.LogWarning("Could not send message to user: {0}", chatMessage.ToUserId);
Logger.LogWarning("Send to user message error: {0}", ex.Message); Logger.LogWarning("Send to user message error: {0}", ex.Message);
} }

67
aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/Messages/SignalRMessageSender.cs

@ -2,66 +2,36 @@
using LINGYUN.Abp.IM.SignalR.Hubs; using LINGYUN.Abp.IM.SignalR.Hubs;
using LINGYUN.Abp.RealTime.Client; using LINGYUN.Abp.RealTime.Client;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System; using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.IM.SignalR.Messages namespace LINGYUN.Abp.IM.SignalR.Messages
{ {
public class SignalRMessageSender : IMessageSender, ITransientDependency [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IMessageSender))]
public class SignalRMessageSender : MessageSenderBase
{ {
public ILogger<SignalRMessageSender> Logger { protected get; set; }
private readonly IOnlineClientManager _onlineClientManager; private readonly IOnlineClientManager _onlineClientManager;
private readonly IHubContext<MessagesHub> _hubContext; private readonly IHubContext<MessagesHub> _hubContext;
private readonly IMessageStore _messageStore;
public SignalRMessageSender( public SignalRMessageSender(
IOnlineClientManager onlineClientManager, IOnlineClientManager onlineClientManager,
IHubContext<MessagesHub> hubContext, IHubContext<MessagesHub> hubContext,
IMessageStore messageStore) IMessageStore messageStore,
ILogger<SignalRMessageSender> logger)
: base(messageStore, logger)
{ {
_hubContext = hubContext; _hubContext = hubContext;
_messageStore = messageStore;
_onlineClientManager = onlineClientManager; _onlineClientManager = onlineClientManager;
Logger = NullLogger<SignalRMessageSender>.Instance;
}
/// <summary>
/// 服务端调用发送消息方法
/// </summary>
/// <param name="chatMessage"></param>
/// <returns></returns>
public async Task SendMessageAsync(ChatMessage chatMessage)
{
// 持久化
await _messageStore.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 virtual async Task SendMessageToGroupAsync(ChatMessage chatMessage) protected override async Task SendMessageToGroupAsync(ChatMessage chatMessage)
{ {
var signalRClient = _hubContext.Clients.Group(chatMessage.GroupId); var signalRClient = _hubContext.Clients.Group(chatMessage.GroupId);
if (signalRClient == null) if (signalRClient == null)
@ -73,22 +43,20 @@ namespace LINGYUN.Abp.IM.SignalR.Messages
await signalRClient.SendAsync("getChatMessage", chatMessage); await signalRClient.SendAsync("getChatMessage", chatMessage);
} }
protected virtual async Task SendMessageToUserAsync(ChatMessage chatMessage) protected override async Task SendMessageToUserAsync(ChatMessage chatMessage)
{ {
var onlineClientContext = new OnlineClientContext(chatMessage.TenantId, chatMessage.ToUserId.Value); var onlineClientContext = new OnlineClientContext(chatMessage.TenantId, chatMessage.ToUserId.Value);
var onlineClients = _onlineClientManager.GetAllByContext(onlineClientContext); var onlineClients = _onlineClientManager.GetAllByContext(onlineClientContext);
foreach (var onlineClient in onlineClients)
{
try try
{ {
var signalRClient = _hubContext.Clients.Client(onlineClient.ConnectionId); var onlineClientConnectionIds = onlineClients.Select(client => client.ConnectionId).ToImmutableArray();
if (signalRClient == null) var signalRClients = _hubContext.Clients.Clients(onlineClientConnectionIds);
if (signalRClients == null)
{ {
Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " with connectionId " + onlineClient.ConnectionId + " from SignalR hub!"); Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " connection from SignalR hub!");
continue; return;
} }
await signalRClients.SendAsync("getChatMessage", chatMessage);
await signalRClient.SendAsync("getChatMessage", chatMessage);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -97,5 +65,4 @@ namespace LINGYUN.Abp.IM.SignalR.Messages
} }
} }
} }
}
} }

16
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/IFriendStore.cs

@ -7,6 +7,18 @@ namespace LINGYUN.Abp.IM.Contract
public interface IFriendStore public interface IFriendStore
{ {
/// <summary> /// <summary>
/// 是否是好友关系
/// </summary>
/// <param name="tenantId"></param>
/// <param name="userId"></param>
/// <param name="friendId"></param>
/// <returns></returns>
Task<bool> IsFriendAsync(
Guid? tenantId,
Guid userId,
Guid friendId
);
/// <summary>
/// 查询好友列表 /// 查询好友列表
/// </summary> /// </summary>
/// <param name="tenantId"></param> /// <param name="tenantId"></param>
@ -42,7 +54,7 @@ namespace LINGYUN.Abp.IM.Contract
/// <param name="skipCount"></param> /// <param name="skipCount"></param>
/// <param name="maxResultCount"></param> /// <param name="maxResultCount"></param>
/// <returns></returns> /// <returns></returns>
Task<List<UserFriend>> GetListAsync( Task<List<UserFriend>> GetPagedListAsync(
Guid? tenantId, Guid? tenantId,
Guid userId, Guid userId,
string filter = "", string filter = "",
@ -82,7 +94,7 @@ namespace LINGYUN.Abp.IM.Contract
/// <param name="friendId"></param> /// <param name="friendId"></param>
/// <param name="remarkName"></param> /// <param name="remarkName"></param>
/// <returns></returns> /// <returns></returns>
Task AddMemberAsync( Task<UserAddFriendResult> AddMemberAsync(
Guid? tenantId, Guid? tenantId,
Guid userId, Guid userId,
Guid friendId, Guid friendId,

12
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/UserAddFriendResult.cs

@ -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;
}
}
}

14
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Contract/UserFriendStatus.cs

@ -0,0 +1,14 @@
namespace LINGYUN.Abp.IM.Contract
{
public enum UserFriendStatus : byte
{
/// <summary>
/// 需要验证
/// </summary>
NeedValidation,
/// <summary>
/// 已添加
/// </summary>
Added
}
}

8
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Group/IUserGroupStore.cs

@ -6,6 +6,14 @@ namespace LINGYUN.Abp.IM.Group
{ {
public interface IUserGroupStore public interface IUserGroupStore
{ {
/// <summary>
/// 成员是否在群组
/// </summary>
/// <param name="tenantId"></param>
/// <param name="groupId"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task<bool> MemberHasInGroupAsync(Guid? tenantId, long groupId, Guid userId);
/// <summary> /// <summary>
/// 获取群组用户身份 /// 获取群组用户身份
/// </summary> /// </summary>

70
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/ChatMessage.cs

@ -1,29 +1,79 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.ObjectExtending;
namespace LINGYUN.Abp.IM.Messages namespace LINGYUN.Abp.IM.Messages
{ {
public class ChatMessage public class ChatMessage : ExtensibleObject
{ {
/// <summary>
/// 租户
/// </summary>
public Guid? TenantId { get; set; } public Guid? TenantId { get; set; }
/// <summary>
/// 群组标识
/// </summary>
public string GroupId { get; set; } public string GroupId { get; set; }
/// <summary>
/// 消息标识
/// </summary>
/// <remarks>
/// 调用者无需关注此字段,将由服务自动生成
/// </remarks>
public string MessageId { get; set; } public string MessageId { get; set; }
/// <summary>
/// 发送者标识
/// </summary>
public Guid FormUserId { get; set; } public Guid FormUserId { get; set; }
/// <summary>
/// 发送者名称
/// </summary>
public string FormUserName { get; set; } public string FormUserName { get; set; }
/// <summary>
/// 接收用户标识
/// </summary>
/// <remarks>
/// 设计为可空是为了兼容群聊消息
/// /remarks>
public Guid? ToUserId { get; set; } public Guid? ToUserId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
[DisableAuditing] [DisableAuditing]
public string Content { get; set; } public string Content { get; set; }
/// <summary>
/// 发送时间
/// </summary>
public DateTime SendTime { get; set; } 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("");
}
}
} }
} }

47
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/MessageSenderBase.cs

@ -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);
}
}

22
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/MessageType.cs

@ -2,9 +2,29 @@
{ {
public enum MessageType public enum MessageType
{ {
/// <summary>
/// 文本消息
/// </summary>
Text = 0, Text = 0,
/// <summary>
/// 图片消息
/// </summary>
Image = 10, Image = 10,
/// <summary>
/// 链接
/// </summary>
Link = 20, Link = 20,
Video = 30 /// <summary>
/// 视频
/// </summary>
Video = 30,
/// <summary>
/// 音频
/// </summary>
Voice = 40,
/// <summary>
/// 文件
/// </summary>
File = 50
} }
} }

26
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/Messages/NullMessageSender.cs

@ -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;
}
}
}

4
aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/AbpNotificationsSignalRModule.cs

@ -1,12 +1,12 @@
using LINGYUN.Abp.AspNetCore.SignalR.JwtToken; using LINGYUN.Abp.AspNetCore.SignalR.JwtToken;
using LINGYUN.Abp.RealTime; using LINGYUN.Abp.RealTime.SignalR;
using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Notifications.SignalR namespace LINGYUN.Abp.Notifications.SignalR
{ {
[DependsOn( [DependsOn(
typeof(AbpRealTimeModule), typeof(AbpRealTimeSignalRModule),
typeof(AbpNotificationModule), typeof(AbpNotificationModule),
typeof(AbpAspNetCoreSignalRModule), typeof(AbpAspNetCoreSignalRModule),
typeof(AbpAspNetCoreSignalRJwtTokenModule))] typeof(AbpAspNetCoreSignalRJwtTokenModule))]

24
aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/Hubs/NotificationsHub.cs

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Authorization; using LINGYUN.Abp.RealTime.Client;
using LINGYUN.Abp.RealTime.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
@ -12,6 +14,24 @@ namespace LINGYUN.Abp.Notifications.SignalR.Hubs
private INotificationStore _notificationStore; private INotificationStore _notificationStore;
protected INotificationStore NotificationStore => LazyGetRequiredService(ref _notificationStore); protected INotificationStore NotificationStore => LazyGetRequiredService(ref _notificationStore);
protected override async Task OnClientConnectedAsync(IOnlineClient client)
{
if (client.TenantId.HasValue)
{
// 以租户为分组,将用户加入租户通讯组
await Groups.AddToGroupAsync(client.ConnectionId, client.TenantId.Value.ToString());
}
}
protected override async Task OnClientDisconnectedAsync(IOnlineClient client)
{
if (client.TenantId.HasValue)
{
// 以租户为分组,将移除租户通讯组
await Groups.RemoveFromGroupAsync(client.ConnectionId, client.TenantId.Value.ToString());
}
}
[HubMethodName("GetNotification")] [HubMethodName("GetNotification")]
public virtual async Task<ListResultDto<NotificationInfo>> GetNotificationAsync( public virtual async Task<ListResultDto<NotificationInfo>> GetNotificationAsync(
NotificationReadState readState = NotificationReadState.UnRead, int maxResultCount = 10) NotificationReadState readState = NotificationReadState.UnRead, int maxResultCount = 10)
@ -26,7 +46,5 @@ namespace LINGYUN.Abp.Notifications.SignalR.Hubs
{ {
await NotificationStore.ChangeUserNotificationReadStateAsync(CurrentTenant.Id, CurrentUser.GetId(), long.Parse(id), readState); await NotificationStore.ChangeUserNotificationReadStateAsync(CurrentTenant.Id, CurrentUser.GetId(), long.Parse(id), readState);
} }
} }
} }

82
aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/OnlineClientHubBase.cs

@ -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;
}
}
}
}

37
aspnet-core/modules/common/LINGYUN.Abp.Notifications.SignalR/LINGYUN/Abp/Notifications/SignalR/SignalRNotificationPublishProvider.cs

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -53,26 +54,44 @@ namespace LINGYUN.Abp.Notifications.SignalR
Logger.LogDebug($"Find online client with user {identifier.UserId} - {identifier.UserName}"); Logger.LogDebug($"Find online client with user {identifier.UserId} - {identifier.UserName}");
var onlineClientContext = new OnlineClientContext(notification.TenantId, identifier.UserId); var onlineClientContext = new OnlineClientContext(notification.TenantId, identifier.UserId);
var onlineClients = _onlineClientManager.GetAllByContext(onlineClientContext); var onlineClients = _onlineClientManager.GetAllByContext(onlineClientContext);
foreach (var onlineClient in onlineClients) var onlineClientConnectionIds = onlineClients.Select(client => client.ConnectionId).ToImmutableArray();
{
try try
{ {
Logger.LogDebug($"Find online client {onlineClient.UserId} - {onlineClient.ConnectionId}"); var signalRClients = _hubContext.Clients.Clients(onlineClientConnectionIds);
var signalRClient = _hubContext.Clients.Client(onlineClient.ConnectionId); if (signalRClients == null)
if (signalRClient == null)
{ {
Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " with connectionId " + onlineClient.ConnectionId + " from SignalR hub!"); Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " connection from SignalR hub!");
continue; return;
} }
Logger.LogDebug($"Found a singalr client, begin senging notifications"); Logger.LogDebug($"Found a singalr client, begin senging notifications");
await signalRClient.SendAsync("getNotification", notification); await signalRClients.SendAsync("getNotification", notification);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning("Could not send notifications to user: {0}", identifier.UserId); Logger.LogWarning("Could not send notifications to user: {0}", identifier.UserId);
Logger.LogWarning("Send to user notifications error: {0}", ex.Message); Logger.LogWarning("Send to user notifications error: {0}", ex.Message);
} }
}
//foreach (var onlineClient in onlineClients)
//{
// try
// {
// Logger.LogDebug($"Find online client {onlineClient.UserId} - {onlineClient.ConnectionId}");
// var signalRClient = _hubContext.Clients.Client(onlineClient.ConnectionId);
// if (signalRClient == null)
// {
// Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " with connectionId " + onlineClient.ConnectionId + " from SignalR hub!");
// continue;
// }
// Logger.LogDebug($"Found a singalr client, begin senging notifications");
// await signalRClient.SendAsync("getNotification", notification);
// }
// catch (Exception ex)
// {
// Logger.LogWarning("Could not send notifications to user: {0}", identifier.UserId);
// Logger.LogWarning("Send to user notifications error: {0}", ex.Message);
// }
//}
} }
} }
} }

11
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationData.cs

@ -3,6 +3,13 @@ using System.Collections.Generic;
namespace LINGYUN.Abp.Notifications namespace LINGYUN.Abp.Notifications
{ {
/// <summary>
/// 通知数据
/// </summary>
/// <remarks>
/// TODO: 2020-10-29 针对不同语言的用户,如果在发布时期就本地化语言是错误的设计
/// 把通知的标题和内容设计为 <see cref="Volo.Abp.Validation.StringValues.LocalizableStringInfo"/> 让客户端自行本地化
/// </remarks>
public class NotificationData public class NotificationData
{ {
public const string NotificationKey = "N:G"; public const string NotificationKey = "N:G";
@ -153,14 +160,14 @@ namespace LINGYUN.Abp.Notifications
public bool HasUserNotification(out Guid userId, out string userName) public bool HasUserNotification(out Guid userId, out string userName)
{ {
userName = "";
if (Properties.TryGetValue(UserIdNotificationKey, out object userKey)) if (Properties.TryGetValue(UserIdNotificationKey, out object userKey))
{ {
userId = (Guid)userKey; userId = (Guid)userKey;
var name = TryGetData(UserNameNotificationKey); var name = TryGetData(UserNameNotificationKey);
userName = name != null ? name.ToString() : ""; userName = name?.ToString() ?? userName;
return true; return true;
} }
userName = "";
return false; return false;
} }

8
aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/Class1.cs

@ -0,0 +1,8 @@
using System;
namespace LINGYUN.Abp.RealTime.SignalR
{
public class Class1
{
}
}

18
aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN.Abp.RealTime.SignalR.csproj

@ -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>

11
aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN/Abp/RealTime/SignalR/AbpRealTimeSignalRModule.cs

@ -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
{
}
}

31
aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/OnlineClientHubBase.cs → aspnet-core/modules/common/LINGYUN.Abp.RealTime.SignalR/LINGYUN/Abp/RealTime/SignalR/Hubs/OnlineClientHubBase.cs

@ -1,18 +1,14 @@
using LINGYUN.Abp.IM.Group; using LINGYUN.Abp.RealTime.Client;
using LINGYUN.Abp.RealTime.Client;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.AspNetCore.SignalR;
namespace LINGYUN.Abp.IM.SignalR namespace LINGYUN.Abp.RealTime.SignalR
{ {
public abstract class OnlineClientHubBase : AbpHub public abstract class OnlineClientHubBase : AbpHub
{ {
private IUserGroupStore _userGroupStore;
protected IUserGroupStore UserGroupStore => LazyGetRequiredService(ref _userGroupStore);
private IOnlineClientManager _onlineClientManager; private IOnlineClientManager _onlineClientManager;
protected IOnlineClientManager OnlineClientManager => LazyGetRequiredService(ref _onlineClientManager); protected IOnlineClientManager OnlineClientManager => LazyGetRequiredService(ref _onlineClientManager);
@ -25,12 +21,7 @@ namespace LINGYUN.Abp.IM.SignalR
IOnlineClient onlineClient = CreateClientForCurrentConnection(); IOnlineClient onlineClient = CreateClientForCurrentConnection();
Logger.LogDebug("A client is connected: " + onlineClient.ToString()); Logger.LogDebug("A client is connected: " + onlineClient.ToString());
OnlineClientManager.Add(onlineClient); OnlineClientManager.Add(onlineClient);
// 加入通讯组 await OnClientConnectedAsync(onlineClient);
var userGroups = await UserGroupStore.GetUserGroupsAsync(onlineClient.TenantId, onlineClient.UserId.Value);
foreach(var group in userGroups)
{
await Groups.AddToGroupAsync(onlineClient.ConnectionId, group.Name);
}
} }
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
@ -43,13 +34,9 @@ namespace LINGYUN.Abp.IM.SignalR
var onlineClient = OnlineClientManager.GetByConnectionIdOrNull(Context.ConnectionId); var onlineClient = OnlineClientManager.GetByConnectionIdOrNull(Context.ConnectionId);
if(onlineClient != null) if(onlineClient != null)
{ {
var userGroups = await UserGroupStore.GetUserGroupsAsync(onlineClient.TenantId, onlineClient.UserId.Value);
foreach (var group in userGroups)
{
await Groups.RemoveFromGroupAsync(onlineClient.ConnectionId, group.Name);
}
// 移除在线客户端 // 移除在线客户端
OnlineClientManager.Remove(Context.ConnectionId); OnlineClientManager.Remove(Context.ConnectionId);
await OnClientDisconnectedAsync(onlineClient);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -79,5 +66,15 @@ namespace LINGYUN.Abp.IM.SignalR
return null; return null;
} }
} }
protected virtual Task OnClientConnectedAsync(IOnlineClient client)
{
return Task.CompletedTask;
}
protected virtual Task OnClientDisconnectedAsync(IOnlineClient client)
{
return Task.CompletedTask;
}
} }
} }

4
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application.Contracts/LINGYUN.Abp.MessageService.Application.Contracts.csproj

@ -28,4 +28,8 @@
<ProjectReference Include="..\..\message\LINGYUN.Abp.MessageService.Domain.Shared\LINGYUN.Abp.MessageService.Domain.Shared.csproj" /> <ProjectReference Include="..\..\message\LINGYUN.Abp.MessageService.Domain.Shared\LINGYUN.Abp.MessageService.Domain.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="LINGYUN\Abp\MessageService\Group\" />
</ItemGroup>
</Project> </Project>

63
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application.Contracts/LINGYUN/Abp/MessageService/Chat/IChatAppService.cs

@ -1,5 +1,4 @@
using LINGYUN.Abp.IM.Group; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.IM.Messages;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
@ -14,41 +13,41 @@ namespace LINGYUN.Abp.MessageService.Chat
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input); Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input);
/// <summary> ///// <summary>
/// 申请加入群组 ///// 申请加入群组
/// </summary> ///// </summary>
/// <param name="input"></param> ///// <param name="input"></param>
/// <returns></returns> ///// <returns></returns>
Task ApplyJoinGroupAsync(UserJoinGroupDto input); //Task ApplyJoinGroupAsync(UserJoinGroupDto input);
/// <summary> ///// <summary>
/// 获取我的群组 ///// 获取我的群组
/// </summary> ///// </summary>
/// <returns></returns> ///// <returns></returns>
Task<ListResultDto<Group>> GetMyGroupsAsync(); //Task<ListResultDto<Group>> GetMyGroupsAsync();
/// <summary> ///// <summary>
/// 获取群组用户 ///// 获取群组用户
/// </summary> ///// </summary>
/// <param name="input"></param> ///// <param name="input"></param>
/// <returns></returns> ///// <returns></returns>
Task<PagedResultDto<GroupUserCard>> GetGroupUsersAsync(GroupUserGetByPagedDto input); //Task<PagedResultDto<GroupUserCard>> GetGroupUsersAsync(GroupUserGetByPagedDto input);
/// <summary> ///// <summary>
/// 处理用户群组申请 ///// 处理用户群组申请
/// </summary> ///// </summary>
/// <param name="input"></param> ///// <param name="input"></param>
/// <returns></returns> ///// <returns></returns>
Task GroupAcceptUserAsync(GroupAcceptUserDto input); //Task GroupAcceptUserAsync(GroupAcceptUserDto input);
/// <summary> ///// <summary>
/// 群组移除用户 ///// 群组移除用户
/// </summary> ///// </summary>
/// <param name="input"></param> ///// <param name="input"></param>
/// <returns></returns> ///// <returns></returns>
Task GroupRemoveUserAsync(GroupRemoveUserDto input); //Task GroupRemoveUserAsync(GroupRemoveUserDto input);
/// <summary> /// <summary>
/// 获取群组消息 /// 获取群组消息
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
Task<PagedResultDto<ChatMessage>> GetGroupMessageAsync(GroupMessageGetByPagedDto input); Task<PagedResultDto<ChatMessage>> GetMyGroupMessageAsync(GroupMessageGetByPagedDto input);
/// <summary> /// <summary>
/// 获取我的消息 /// 获取我的消息
/// </summary> /// </summary>

124
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application/LINGYUN/Abp/MessageService/Chat/ChatAppService.cs

@ -1,7 +1,6 @@
using LINGYUN.Abp.IM.Group; using LINGYUN.Abp.IM.Group;
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Messages;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
@ -50,9 +49,12 @@ namespace LINGYUN.Abp.MessageService.Chat
return new ListResultDto<LastChatMessage>(chatMessages); return new ListResultDto<LastChatMessage>(chatMessages);
} }
public virtual async Task<PagedResultDto<ChatMessage>> GetGroupMessageAsync(GroupMessageGetByPagedDto input) public virtual async Task<PagedResultDto<ChatMessage>> GetMyGroupMessageAsync(GroupMessageGetByPagedDto input)
{ {
// TODO: 增加验证,用户不在群组却来查询这个群组消息,是非法客户端操作 if (! await _userGroupStore.MemberHasInGroupAsync(CurrentTenant.Id, input.GroupId, CurrentUser.GetId()))
{
throw new BusinessException(MessageServiceErrorCodes.YouHaveNotJoinedGroup);
}
var groupMessageCount = await _messageStore var groupMessageCount = await _messageStore
.GetGroupMessageCountAsync(CurrentTenant.Id, input.GroupId, .GetGroupMessageCountAsync(CurrentTenant.Id, input.GroupId,
@ -66,61 +68,61 @@ namespace LINGYUN.Abp.MessageService.Chat
return new PagedResultDto<ChatMessage>(groupMessageCount, groupMessages); return new PagedResultDto<ChatMessage>(groupMessageCount, groupMessages);
} }
public virtual async Task<PagedResultDto<GroupUserCard>> GetGroupUsersAsync(GroupUserGetByPagedDto input) //public virtual async Task<PagedResultDto<GroupUserCard>> GetGroupUsersAsync(GroupUserGetByPagedDto input)
{ //{
var groupUserCardCount = await _userGroupStore // var groupUserCardCount = await _userGroupStore
.GetMembersCountAsync(CurrentTenant.Id, input.GroupId); // .GetMembersCountAsync(CurrentTenant.Id, input.GroupId);
var groupUserCards = await _userGroupStore.GetMembersAsync(CurrentTenant.Id, // var groupUserCards = await _userGroupStore.GetMembersAsync(CurrentTenant.Id,
input.GroupId, input.Sorting, input.Reverse, // input.GroupId, input.Sorting, input.Reverse,
input.SkipCount, input.MaxResultCount); // input.SkipCount, input.MaxResultCount);
return new PagedResultDto<GroupUserCard>(groupUserCardCount, groupUserCards); // return new PagedResultDto<GroupUserCard>(groupUserCardCount, groupUserCards);
} //}
[Authorize] //[Authorize]
public virtual async Task<ListResultDto<Group>> GetMyGroupsAsync() //public virtual async Task<ListResultDto<Group>> GetMyGroupsAsync()
{ //{
var myGroups = await _userGroupStore.GetUserGroupsAsync(CurrentTenant.Id, CurrentUser.GetId()); // var myGroups = await _userGroupStore.GetUserGroupsAsync(CurrentTenant.Id, CurrentUser.GetId());
return new ListResultDto<Group>(myGroups.ToImmutableList()); // return new ListResultDto<Group>(myGroups.ToImmutableList());
} //}
public virtual async Task GroupAcceptUserAsync(GroupAcceptUserDto input) //public virtual async Task GroupAcceptUserAsync(GroupAcceptUserDto input)
{ //{
var myGroupCard = await _userGroupStore // var myGroupCard = await _userGroupStore
.GetUserGroupCardAsync(CurrentTenant.Id, input.GroupId, CurrentUser.GetId()); // .GetUserGroupCardAsync(CurrentTenant.Id, input.GroupId, CurrentUser.GetId());
if (myGroupCard == null) // if (myGroupCard == null)
{ // {
// 当前登录用户不再用户组 // // 当前登录用户不再用户组
throw new UserFriendlyException(""); // throw new UserFriendlyException("");
} // }
if (!myGroupCard.IsAdmin) // if (!myGroupCard.IsAdmin)
{ // {
// 当前登录用户没有加人权限 // // 当前登录用户没有加人权限
throw new UserFriendlyException(""); // throw new UserFriendlyException("");
} // }
await _userGroupStore // await _userGroupStore
.AddUserToGroupAsync(CurrentTenant.Id, input.UserId, input.GroupId, CurrentUser.GetId()); // .AddUserToGroupAsync(CurrentTenant.Id, input.UserId, input.GroupId, CurrentUser.GetId());
} //}
public virtual async Task GroupRemoveUserAsync(GroupRemoveUserDto input) //public virtual async Task GroupRemoveUserAsync(GroupRemoveUserDto input)
{ //{
var myGroupCard = await _userGroupStore // var myGroupCard = await _userGroupStore
.GetUserGroupCardAsync(CurrentTenant.Id, input.GroupId, CurrentUser.GetId()); // .GetUserGroupCardAsync(CurrentTenant.Id, input.GroupId, CurrentUser.GetId());
if (myGroupCard == null) // if (myGroupCard == null)
{ // {
// 当前登录用户不再用户组 // // 当前登录用户不再用户组
throw new UserFriendlyException(""); // throw new UserFriendlyException("");
} // }
if (!myGroupCard.IsAdmin) // if (!myGroupCard.IsAdmin)
{ // {
// 当前登录用户没有踢人权限 // // 当前登录用户没有踢人权限
throw new UserFriendlyException(""); // throw new UserFriendlyException("");
} // }
await _userGroupStore // await _userGroupStore
.RemoveUserFormGroupAsync(CurrentTenant.Id, input.UserId, input.GroupId); // .RemoveUserFormGroupAsync(CurrentTenant.Id, input.UserId, input.GroupId);
} //}
public virtual async Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input) public virtual async Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input)
{ {
@ -131,11 +133,5 @@ namespace LINGYUN.Abp.MessageService.Chat
return new ChatMessageSendResultDto(input.MessageId); return new ChatMessageSendResultDto(input.MessageId);
} }
public virtual Task ApplyJoinGroupAsync(UserJoinGroupDto input)
{
// TOTO 发送通知?
return Task.CompletedTask;
}
} }
} }

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Application/LINGYUN/Abp/MessageService/Chat/MyFriendAppService.cs

@ -41,7 +41,7 @@ namespace LINGYUN.Abp.MessageService.Chat
var myFrientCount = await FriendStore.GetCountAsync(CurrentTenant.Id, CurrentUser.GetId()); var myFrientCount = await FriendStore.GetCountAsync(CurrentTenant.Id, CurrentUser.GetId());
var myFriends = await FriendStore var myFriends = await FriendStore
.GetListAsync(CurrentTenant.Id, CurrentUser.GetId(), .GetPagedListAsync(CurrentTenant.Id, CurrentUser.GetId(),
input.Filter, input.Sorting, input.Reverse, input.Filter, input.Sorting, input.Reverse,
input.SkipCount, input.MaxResultCount); input.SkipCount, input.MaxResultCount);

23
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Chat/UserChatFriendEto.cs

@ -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; }
}
}

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Chat/ChatGroupConsts.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Group/ChatGroupConsts.cs

@ -1,4 +1,4 @@
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public class ChatGroupConsts public class ChatGroupConsts
{ {

87
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/MessageServiceErrorCodes.cs

@ -1,34 +1,99 @@
namespace LINGYUN.Abp.MessageService 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 class MessageServiceErrorCodes
{ {
public const string Namespace = "LINGYUN.Abp.Message";
/// <summary> /// <summary>
/// 管理员已开启全员禁言 /// 消息不完整
/// </summary> /// </summary>
public const string GroupNotAllowedToSpeak = "Messages.Group:1001"; public const string MessageIncomplete = Namespace + ":01400";
/// <summary> /// <summary>
/// 管理员不允许匿名发言 /// 您还未加入群组,不能进行操作
/// </summary>
public const string YouHaveNotJoinedGroup = Namespace + ":01401";
/// <summary>
/// 已发送群组申请,等待管理员同意
/// </summary>
public const string YouHaveAddingToGroup = Namespace + ":02301";
/// <summary>
/// 你需要验证问题才能加入群聊
/// </summary>
public const string YouNeedValidationQuestingByAddGroup = Namespace + ":02302";
/// <summary>
/// 管理员已开启全员禁言
/// </summary> /// </summary>
public const string GroupNotAllowedToSpeakAnonymously = "Messages.Group:1002"; public const string GroupNotAllowedToSpeak = Namespace + ":02400";
/// <summary> /// <summary>
/// 管理员已禁止用户发言 /// 管理员已禁止用户发言
/// </summary> /// </summary>
public const string GroupUserHasBlack = "Messages.Group:1003"; public const string GroupUserHasBlack = Namespace + ":02403";
/// <summary> /// <summary>
/// 用户已将发信人拉黑 /// 管理员不允许匿名发言
/// </summary>
public const string GroupNotAllowedToSpeakAnonymously = Namespace + ":02401";
/// <summary>
/// 群组不存在或已解散
/// </summary> /// </summary>
public const string UserHasBlack = "Messages.User:1003"; public const string GroupNotFount = Namespace + ":02404";
/// <summary> /// <summary>
/// 用户已拒接所有消息 /// 用户已拒接所有消息
/// </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>
/// 用户不允许匿名发言 /// 用户不允许匿名发言
/// </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>
/// 已经添加对方为好友 /// 你需要验证问题才能添加好友
/// </summary> /// </summary>
public const string YouHaveAddedTheUserToFriend = "Messages.UserFriend:1001"; public const string YouNeedValidationQuestingByAddFriend = Namespace + ":03302";
} }
} }

1
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN.Abp.MessageService.Domain.csproj

@ -18,6 +18,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Volo.Abp.Caching" Version="3.2.0" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="3.2.0" /> <PackageReference Include="Volo.Abp.Ddd.Domain" Version="3.2.0" />
<PackageReference Include="Volo.Abp.AutoMapper" Version="3.2.0" /> <PackageReference Include="Volo.Abp.AutoMapper" Version="3.2.0" />
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="3.2.0" /> <PackageReference Include="Volo.Abp.BackgroundJobs" Version="3.2.0" />

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/AbpMessageServiceDomainModule.cs

@ -1,6 +1,7 @@
using LINGYUN.Abp.MessageService.Localization; using LINGYUN.Abp.MessageService.Localization;
using LINGYUN.Abp.MessageService.Mapper; using LINGYUN.Abp.MessageService.Mapper;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -10,6 +11,7 @@ namespace LINGYUN.Abp.MessageService
{ {
[DependsOn( [DependsOn(
typeof(AbpAutoMapperModule), typeof(AbpAutoMapperModule),
typeof(AbpCachingModule),
typeof(AbpMessageServiceDomainSharedModule))] typeof(AbpMessageServiceDomainSharedModule))]
public class AbpMessageServiceDomainModule : AbpModule public class AbpMessageServiceDomainModule : AbpModule
{ {

14
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/ChatNotificationNames.cs

@ -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";
}
}
}

81
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/FriendStore.cs

@ -1,8 +1,10 @@
using LINGYUN.Abp.IM.Contract; using LINGYUN.Abp.IM.Contract;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@ -10,31 +12,65 @@ namespace LINGYUN.Abp.MessageService.Chat
{ {
public class FriendStore : DomainService, IFriendStore public class FriendStore : DomainService, IFriendStore
{ {
protected IDistributedCache<UserFriendCacheItem> Cache { get; }
protected IUserChatFriendRepository UserChatFriendRepository { get; } protected IUserChatFriendRepository UserChatFriendRepository { get; }
protected IUserChatSettingRepository UserChatSettingRepository { get; }
public FriendStore( public FriendStore(
IUserChatFriendRepository userChatFriendRepository) IDistributedCache<UserFriendCacheItem> cache,
IUserChatFriendRepository userChatFriendRepository,
IUserChatSettingRepository userChatSettingRepository)
{ {
Cache = cache;
UserChatFriendRepository = userChatFriendRepository; UserChatFriendRepository = userChatFriendRepository;
UserChatSettingRepository = userChatSettingRepository;
}
public virtual async Task<bool> IsFriendAsync(
Guid? tenantId,
Guid userId,
Guid friendId
)
{
using (CurrentTenant.Change(tenantId))
{
return await UserChatFriendRepository.IsAddedAsync(userId, friendId);
}
} }
[UnitOfWork] [UnitOfWork]
public virtual async Task AddMemberAsync(Guid? tenantId, Guid userId, Guid friendId, string remarkName = "") public virtual async Task<UserAddFriendResult> AddMemberAsync(Guid? tenantId, Guid userId, Guid friendId, string remarkName = "")
{ {
using (CurrentTenant.Change(tenantId)) using (CurrentTenant.Change(tenantId))
{ {
if (await UserChatFriendRepository.IsAddedAsync(userId, friendId)) if (await UserChatFriendRepository.IsAddedAsync(userId, friendId))
{ {
throw new BusinessException(MessageServiceErrorCodes.YouHaveAddedTheUserToFriend); throw new BusinessException(MessageServiceErrorCodes.UseHasBeenAddedTheFriendOrSendAuthorization);
}
var status = UserFriendStatus.NeedValidation;
var userChatSetting = await UserChatSettingRepository.FindByUserIdAsync(friendId);
if (userChatSetting != null)
{
if (!userChatSetting.AllowAddFriend)
{
throw new BusinessException(MessageServiceErrorCodes.UseRefuseToAddFriend);
} }
var userChatFriend = new UserChatFriend(userId, friendId, remarkName, tenantId)
status = userChatSetting.RequireAddFriendValition
? UserFriendStatus.NeedValidation
: UserFriendStatus.Added;
}
var userChatFriend = new UserChatFriend(userId, friendId, remarkName, status, tenantId)
{ {
CreationTime = Clock.Now, CreationTime = Clock.Now,
CreatorId = userId CreatorId = userId
}; };
await UserChatFriendRepository.InsertAsync(userChatFriend); await UserChatFriendRepository.InsertAsync(userChatFriend);
return new UserAddFriendResult(status);
} }
} }
@ -51,11 +87,7 @@ namespace LINGYUN.Abp.MessageService.Chat
bool reverse = false bool reverse = false
) )
{ {
using (CurrentTenant.Change(tenantId)) return await GetAllFriendByCacheItemAsync(tenantId, userId, sorting, reverse);
{
return await UserChatFriendRepository
.GetAllMembersAsync(userId, sorting, reverse);
}
} }
public virtual async Task<int> GetCountAsync(Guid? tenantId, Guid userId, string filter = "") public virtual async Task<int> GetCountAsync(Guid? tenantId, Guid userId, string filter = "")
@ -67,7 +99,7 @@ namespace LINGYUN.Abp.MessageService.Chat
} }
} }
public virtual async Task<List<UserFriend>> GetListAsync(Guid? tenantId, Guid userId, string filter = "", string sorting = nameof(UserFriend.UserId), bool reverse = false, int skipCount = 0, int maxResultCount = 10) public virtual async Task<List<UserFriend>> GetPagedListAsync(Guid? tenantId, Guid userId, string filter = "", string sorting = nameof(UserFriend.UserId), bool reverse = false, int skipCount = 0, int maxResultCount = 10)
{ {
using (CurrentTenant.Change(tenantId)) using (CurrentTenant.Change(tenantId))
{ {
@ -132,5 +164,34 @@ namespace LINGYUN.Abp.MessageService.Chat
} }
} }
} }
protected virtual async Task<List<UserFriend>> GetAllFriendByCacheItemAsync(
Guid? tenantId,
Guid userId,
string sorting = nameof(UserFriend.UserId),
bool reverse = false
)
{
var cacheKey = UserFriendCacheItem.CalculateCacheKey(userId.ToString());
Logger.LogDebug($"FriendStore.GetCacheItemAsync: {cacheKey}");
var cacheItem = await Cache.GetAsync(cacheKey);
if (cacheItem != null)
{
Logger.LogDebug($"Found in the cache: {cacheKey}");
return cacheItem.Friends;
}
Logger.LogDebug($"Not found in the cache: {cacheKey}");
using (CurrentTenant.Change(tenantId))
{
var friends = await UserChatFriendRepository
.GetAllMembersAsync(userId, sorting, reverse);
cacheItem = new UserFriendCacheItem(friends);
Logger.LogDebug($"Set item in the cache: {cacheKey}");
await Cache.SetAsync(cacheKey, cacheItem);
return friends;
}
}
} }
} }

1
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IMessageRepository.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.MessageService.Group;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;

10
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IUserChatSettingRepository.cs

@ -7,14 +7,6 @@ namespace LINGYUN.Abp.MessageService.Chat
public interface IUserChatSettingRepository : IBasicRepository<UserChatSetting, long> public interface IUserChatSettingRepository : IBasicRepository<UserChatSetting, long>
{ {
Task<bool> UserHasOpendImAsync(Guid userId); Task<bool> UserHasOpendImAsync(Guid userId);
/// <summary> Task<UserChatSetting> FindByUserIdAsync(Guid userId);
/// 用户是否已被拉黑
/// </summary>
/// <param name="formUserId"></param>
/// <param name="toUserId"></param>
/// <returns></returns>
Task<bool> UserHasBlackedAsync(Guid formUserId, Guid toUserId);
Task<UserChatSetting> GetByUserIdAsync(Guid userId);
} }
} }

39
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/MessageStore.cs

@ -1,4 +1,6 @@
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Contract;
using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.MessageService.Group;
using LINGYUN.Abp.MessageService.Utils; using LINGYUN.Abp.MessageService.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -12,6 +14,9 @@ namespace LINGYUN.Abp.MessageService.Chat
{ {
public class MessageStore : DomainService, IMessageStore public class MessageStore : DomainService, IMessageStore
{ {
private IFriendStore _friendStore;
protected IFriendStore FriendStore => LazyGetRequiredService(ref _friendStore);
private IObjectMapper _objectMapper; private IObjectMapper _objectMapper;
protected IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper); protected IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper);
@ -137,14 +142,15 @@ namespace LINGYUN.Abp.MessageService.Chat
protected virtual async Task StoreUserMessageAsync(ChatMessage chatMessage) protected virtual async Task StoreUserMessageAsync(ChatMessage chatMessage)
{ {
var userHasBlacked = await UserChatSettingRepository // 检查接收用户
.UserHasBlackedAsync(chatMessage.ToUserId.Value, chatMessage.FormUserId); if (!chatMessage.ToUserId.HasValue)
if (userHasBlacked)
{ {
// 当前发送的用户已被拉黑 throw new BusinessException(MessageServiceErrorCodes.UseNotFount);
throw new BusinessException(MessageServiceErrorCodes.UserHasBlack);
} }
var userChatSetting = await UserChatSettingRepository.GetByUserIdAsync(chatMessage.ToUserId.Value);
var myFriend = await FriendStore.GetMemberAsync(chatMessage.TenantId, chatMessage.ToUserId.Value, chatMessage.FormUserId);
var userChatSetting = await UserChatSettingRepository.FindByUserIdAsync(chatMessage.ToUserId.Value);
if (userChatSetting != null) if (userChatSetting != null)
{ {
if (!userChatSetting.AllowReceiveMessage) if (!userChatSetting.AllowReceiveMessage)
@ -152,12 +158,29 @@ namespace LINGYUN.Abp.MessageService.Chat
// 当前发送的用户不接收消息 // 当前发送的用户不接收消息
throw new BusinessException(MessageServiceErrorCodes.UserHasRejectAllMessage); throw new BusinessException(MessageServiceErrorCodes.UserHasRejectAllMessage);
} }
if (myFriend == null && !chatMessage.IsAnonymous)
{
throw new BusinessException(MessageServiceErrorCodes.UserHasRejectNotFriendMessage);
}
if (chatMessage.IsAnonymous && !userChatSetting.AllowAnonymous) if (chatMessage.IsAnonymous && !userChatSetting.AllowAnonymous)
{ {
// 当前用户不允许匿名发言 // 当前用户不允许匿名发言
throw new BusinessException(MessageServiceErrorCodes.UserNotAllowedToSpeakAnonymously); throw new BusinessException(MessageServiceErrorCodes.UserNotAllowedToSpeakAnonymously);
} }
} }
else
{
if (myFriend == null)
{
throw new BusinessException(MessageServiceErrorCodes.UserHasRejectNotFriendMessage);
}
}
if (myFriend?.Black == true)
{
throw new BusinessException(MessageServiceErrorCodes.UserHasBlack);
}
var messageId = SnowflakeIdGenerator.Create(); var messageId = SnowflakeIdGenerator.Create();
var message = new UserMessage(messageId, chatMessage.FormUserId, chatMessage.FormUserName, chatMessage.Content, chatMessage.MessageType); var message = new UserMessage(messageId, chatMessage.FormUserId, chatMessage.FormUserName, chatMessage.Content, chatMessage.MessageType);
message.SendToUser(chatMessage.ToUserId.Value); message.SendToUser(chatMessage.ToUserId.Value);
@ -188,7 +211,7 @@ namespace LINGYUN.Abp.MessageService.Chat
} }
var messageId = SnowflakeIdGenerator.Create(); var messageId = SnowflakeIdGenerator.Create();
var message = new GroupMessage(messageId, chatMessage.FormUserId, chatMessage.FormUserName, chatMessage.Content, chatMessage.MessageType); var message = new GroupMessage(messageId, chatMessage.FormUserId, chatMessage.FormUserName, chatMessage.Content, chatMessage.MessageType);
// TODO: 需要压测 高并发场景下的装箱性能影响
message.SendToGroup(groupId); message.SendToGroup(groupId);
await MessageRepository.InsertGroupMessageAsync(message); await MessageRepository.InsertGroupMessageAsync(message);

32
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatBlack.cs

@ -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;
}
}
}

25
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatFriend.cs

@ -1,10 +1,11 @@
using System; using LINGYUN.Abp.IM.Contract;
using System;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Chat
{ {
public class UserChatFriend : CreationAuditedEntity<long>, IMultiTenant public class UserChatFriend : CreationAuditedAggregateRoot<long>, IMultiTenant
{ {
/// <summary> /// <summary>
/// 租户 /// 租户
@ -35,6 +36,8 @@ namespace LINGYUN.Abp.MessageService.Chat
/// </summary> /// </summary>
public virtual string RemarkName { get; set; } public virtual string RemarkName { get; set; }
public virtual UserFriendStatus Status { get; protected set; }
protected UserChatFriend() protected UserChatFriend()
{ {
} }
@ -43,12 +46,30 @@ namespace LINGYUN.Abp.MessageService.Chat
Guid userId, Guid userId,
Guid friendId, Guid friendId,
string remarkName = "", string remarkName = "",
UserFriendStatus status = UserFriendStatus.NeedValidation,
Guid? tenantId = null) Guid? tenantId = null)
{ {
UserId = userId; UserId = userId;
FrientId = friendId; FrientId = friendId;
RemarkName = remarkName; RemarkName = remarkName;
Status = status;
TenantId = tenantId; TenantId = tenantId;
} }
public void SetStatus(UserFriendStatus status = UserFriendStatus.NeedValidation)
{
if (Status == UserFriendStatus.NeedValidation && status == UserFriendStatus.NeedValidation)
{
// 如果是后续验证通过的需要单独的事件
AddLocalEvent(new UserChatFriendEto
{
TenantId = TenantId,
UserId = UserId,
FrientId = FrientId,
Status = UserFriendStatus.Added
});
}
Status = status;
}
} }
} }

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatFriendGroup.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;

27
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserFriendCacheItem.cs

@ -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;
}
}
}

32
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserSpecialFocus.cs

@ -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;
}
}
}

84
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/EventBus/Local/UserChatFriendEventHandler.cs

@ -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()));
}
}
}

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/ChatGroup.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/ChatGroup.cs

@ -2,7 +2,7 @@
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
/// <summary> /// <summary>
/// 聊天群组 /// 聊天群组

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/GroupChatBlack.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/GroupChatBlack.cs

@ -2,7 +2,7 @@
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
/// <summary> /// <summary>
/// 用户黑名单 /// 用户黑名单

3
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/GroupMessage.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/GroupMessage.cs

@ -1,7 +1,8 @@
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.MessageService.Chat;
using System; using System;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public class GroupMessage : Message public class GroupMessage : Message
{ {

4
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IGroupRepository.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IGroupRepository.cs

@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public interface IGroupRepository : IBasicRepository<ChatGroup, long> public interface IGroupRepository : IBasicRepository<ChatGroup, long>
{ {
@ -19,7 +19,7 @@ namespace LINGYUN.Abp.MessageService.Chat
Guid formUserId, Guid formUserId,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
Task<ChatGroup> GetByIdAsync( Task<ChatGroup> FindByIdAsync(
long id, long id,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);

19
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IGroupRepositoryExtensions.cs

@ -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);
}
}
}

6
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/IUserChatGroupRepository.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/IUserChatGroupRepository.cs

@ -5,7 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public interface IUserChatGroupRepository : IBasicRepository<UserChatGroup, long> public interface IUserChatGroupRepository : IBasicRepository<UserChatGroup, long>
{ {
@ -51,7 +51,7 @@ namespace LINGYUN.Abp.MessageService.Chat
/// <returns></returns> /// <returns></returns>
Task<List<GroupUserCard>> GetMembersAsync( Task<List<GroupUserCard>> GetMembersAsync(
long groupId, long groupId,
string sorting = nameof(UserChatCard.UserId), string sorting = nameof(GroupUserCard.UserId),
bool reverse = false, bool reverse = false,
int skipCount = 0, int skipCount = 0,
int maxResultCount = 10, int maxResultCount = 10,
@ -62,7 +62,7 @@ namespace LINGYUN.Abp.MessageService.Chat
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<List<Group>> GetMemberGroupsAsync( Task<List<LINGYUN.Abp.IM.Group.Group>> GetMemberGroupsAsync(
Guid userId, Guid userId,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserChatGroup.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserChatGroup.cs

@ -2,7 +2,7 @@
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
/// <summary> /// <summary>
/// 用户群组 /// 用户群组

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserGroupCard.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserGroupCard.cs

@ -3,7 +3,7 @@ using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing; using Volo.Abp.Timing;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
/// <summary> /// <summary>
/// 用户群组卡片 /// 用户群组卡片

14
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Chat/UserGroupStore.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Group/UserGroupStore.cs

@ -6,7 +6,7 @@ using Volo.Abp.Domain.Services;
using Volo.Abp.ObjectMapping; using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow; using Volo.Abp.Uow;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public class UserGroupStore : DomainService, IUserGroupStore public class UserGroupStore : DomainService, IUserGroupStore
{ {
@ -24,8 +24,16 @@ namespace LINGYUN.Abp.MessageService.Chat
UserChatGroupRepository = userChatGroupRepository; UserChatGroupRepository = userChatGroupRepository;
} }
public virtual async Task<bool> MemberHasInGroupAsync(Guid? tenantId, long groupId, Guid userId)
{
using (CurrentTenant.Change(tenantId))
{
return await UserChatGroupRepository.MemberHasInGroupAsync(groupId, userId);
}
}
[UnitOfWork] [UnitOfWork]
public async Task AddUserToGroupAsync(Guid? tenantId, Guid userId, long groupId, Guid acceptUserId) public virtual async Task AddUserToGroupAsync(Guid? tenantId, Guid userId, long groupId, Guid acceptUserId)
{ {
using (var unitOfWork = UnitOfWorkManager.Begin()) using (var unitOfWork = UnitOfWorkManager.Begin())
{ {
@ -62,7 +70,7 @@ namespace LINGYUN.Abp.MessageService.Chat
} }
} }
public async Task<IEnumerable<Group>> GetUserGroupsAsync(Guid? tenantId, Guid userId) public async Task<IEnumerable<LINGYUN.Abp.IM.Group.Group>> GetUserGroupsAsync(Guid? tenantId, Guid userId)
{ {
using (CurrentTenant.Change(tenantId)) using (CurrentTenant.Change(tenantId))
{ {

24
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Localization/Resources/en.json

@ -1,13 +1,23 @@
{ {
"culture": "en", "culture": "en",
"texts": { "texts": {
"Messages.Group:1001": "The current group is not allowed to speak", "LINGYUN.Abp.Message:01400": "Sending the message failed: The message is incomplete!",
"Messages.Group:1002": "The current group is not allowed to speak anonymously", "LINGYUN.Abp.Message:01401": "You have not joined the group and cannot operate!",
"Messages.Group:1003": "The administrator has banned you from speaking!", "LINGYUN.Abp.Message:02301": "Group application has been sent, waiting for administrator's approval",
"Messages.User:1001": "Users do not receive anonymous comments!", "LINGYUN.Abp.Message:02302": "You need to validate the questions to join the group chat",
"Messages.User:1002": "The user has rejected all messages!", "LINGYUN.Abp.Message:02400": "The administrator has turned on silence mode!",
"Messages.User:1003": "The user rejects the message you sent!", "LINGYUN.Abp.Message:02403": "The administrator has banned you from speaking!",
"Messages.UserFriend:1001": "You have added each other as friends, can not be repeated!", "LINGYUN.Abp.Message:02401": "The administrator does not allow anonymous speaking!",
"LINGYUN.Abp.Message:02404": "Sending the message failed: the group does not exist or is disbanded!",
"LINGYUN.Abp.Message:03301": "Friend request has been sent, waiting for the other party's approval",
"LINGYUN.Abp.Message:03302": "You need to verify the problem to add friends",
"LINGYUN.Abp.Message:03400": "The user has rejected all messages!",
"LINGYUN.Abp.Message:03401": "The user rejects the message you sent!",
"LINGYUN.Abp.Message:03402": "Users do not receive anonymous comments!",
"LINGYUN.Abp.Message:03403": "Sending the message failed: the person needs to agree to add a friend!",
"LINGYUN.Abp.Message:03404": "Sending the message failed: the user does not exist or is deactivated!",
"LINGYUN.Abp.Message:03410": "Users refuse to add friends",
"LINGYUN.Abp.Message:03411": "The other party is already your friend or has sent an authentication request. The operation cannot be repeated!",
"WelcomeToApplicationNotification": "User Welcome Notice", "WelcomeToApplicationNotification": "User Welcome Notice",
"NewTenantRegisterdNotification": "Tenants create notification", "NewTenantRegisterdNotification": "Tenants create notification",
"WelcomeToApplicationFormUser": "User :{0} welcome to join us!" "WelcomeToApplicationFormUser": "User :{0} welcome to join us!"

24
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Localization/Resources/zh-Hans.json

@ -1,13 +1,23 @@
{ {
"culture": "zh-Hans", "culture": "zh-Hans",
"texts": { "texts": {
"Messages.Group:1001": "管理员已开启全员禁言!", "LINGYUN.Abp.Message:01400": "发送消息失败: 消息不完整!",
"Messages.Group:1002": "管理员不允许匿名发言!", "LINGYUN.Abp.Message:01401": "您还未加入群组,不能进行操作!",
"Messages.Group:1003": "管理员已禁止您发言!", "LINGYUN.Abp.Message:02301": "已发送群组申请,等待管理员同意",
"Messages.User:1001": "用户不接收匿名发言!", "LINGYUN.Abp.Message:02302": "你需要验证问题才能加入群聊",
"Messages.User:1002": "用户已拒接所有消息!", "LINGYUN.Abp.Message:02400": "管理员已开启全员禁言!",
"Messages.User:1003": "用户拒绝您发送的消息!", "LINGYUN.Abp.Message:02403": "管理员已禁止您发言!",
"Messages.UserFriend:1001": "您已经添加对方为好友,不能重复添加!", "LINGYUN.Abp.Message:02401": "管理员不允许匿名发言!",
"LINGYUN.Abp.Message:02404": "发送消息失败: 群组不存在或已解散!",
"LINGYUN.Abp.Message:03301": "已发送好友申请,等待对方同意",
"LINGYUN.Abp.Message:03302": "你需要验证问题才能添加好友",
"LINGYUN.Abp.Message:03400": "用户已拒接所有消息!",
"LINGYUN.Abp.Message:03401": "用户拒绝您发送的消息!",
"LINGYUN.Abp.Message:03402": "用户不接收匿名发言!",
"LINGYUN.Abp.Message:03403": "需要对方同意添加好友才能发送消息!",
"LINGYUN.Abp.Message:03404": "发送消息失败: 用户不存在或已注销账号!",
"LINGYUN.Abp.Message:03410": "用户拒绝添加好友",
"LINGYUN.Abp.Message:03411": "对方已是您的好友或已发送验证请求,不能重复操作!",
"WelcomeToApplicationNotification": "用户欢迎通知", "WelcomeToApplicationNotification": "用户欢迎通知",
"NewTenantRegisterdNotification": "租户创建通知", "NewTenantRegisterdNotification": "租户创建通知",
"WelcomeToApplicationFormUser": "用户:{0} 欢迎您的加入!" "WelcomeToApplicationFormUser": "用户:{0} 欢迎您的加入!"

6
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreMessageRepository.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.IM.Messages; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.MessageService.EntityFrameworkCore; using LINGYUN.Abp.MessageService.EntityFrameworkCore;
using LINGYUN.Abp.MessageService.Group;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -161,10 +162,11 @@ namespace LINGYUN.Abp.MessageService.Chat
var groupMsgQuery = DbContext.Set<UserMessage>() var groupMsgQuery = DbContext.Set<UserMessage>()
.Where(msg => msg.ReceiveUserId == userId || msg.CreatorId == userId) .Where(msg => msg.ReceiveUserId == userId || msg.CreatorId == userId)
.GroupBy(msg => msg.CreatorId) .GroupBy(msg => new { msg.CreatorId, msg.ReceiveUserId })
.Select(msg => new .Select(msg => new
{ {
msg.Key, msg.Key.CreatorId,
msg.Key.ReceiveUserId,
MessageId = msg.Max(x => x.MessageId) MessageId = msg.Max(x => x.MessageId)
}); });

14
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatFriendRepository.cs

@ -23,7 +23,7 @@ namespace LINGYUN.Abp.MessageService.Chat
public virtual async Task<UserChatFriend> FindByUserFriendIdAsync(Guid userId, Guid friendId, CancellationToken cancellationToken = default) public virtual async Task<UserChatFriend> FindByUserFriendIdAsync(Guid userId, Guid friendId, CancellationToken cancellationToken = default)
{ {
return await DbSet return await DbSet
.Where(ucf => ucf.UserId == userId && ucf.FrientId == friendId) .Where(ucf => ucf.UserId == userId && ucf.FrientId == friendId && ucf.Status == UserFriendStatus.Added)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
} }
@ -34,10 +34,12 @@ namespace LINGYUN.Abp.MessageService.Chat
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
sorting = reverse ? sorting + " DESC" : sorting; sorting = reverse ? sorting + " DESC" : sorting;
var userFriendQuery = from ucf in DbContext.Set<UserChatFriend>() var userFriendQuery = from ucf in DbContext.Set<UserChatFriend>()
join ucc in DbContext.Set<UserChatCard>() join ucc in DbContext.Set<UserChatCard>()
on ucf.FrientId equals ucc.UserId // on ucf.FrientId equals ucc.UserId // 查询双向好友的
where ucf.UserId == userId on ucf.UserId equals ucc.UserId
where ucf.UserId == userId && ucf.Status == UserFriendStatus.Added
select new UserFriend select new UserFriend
{ {
Age = ucc.Age, Age = ucc.Age,
@ -67,7 +69,7 @@ namespace LINGYUN.Abp.MessageService.Chat
var userFriendQuery = from ucf in DbContext.Set<UserChatFriend>() var userFriendQuery = from ucf in DbContext.Set<UserChatFriend>()
join ucc in DbContext.Set<UserChatCard>() join ucc in DbContext.Set<UserChatCard>()
on ucf.FrientId equals ucc.UserId on ucf.FrientId equals ucc.UserId
where ucf.UserId == userId && ucf.FrientId == friendId where ucf.UserId == userId && ucf.FrientId == friendId && ucf.Status == UserFriendStatus.Added
select new UserFriend select new UserFriend
{ {
Age = ucc.Age, Age = ucc.Age,
@ -100,6 +102,7 @@ namespace LINGYUN.Abp.MessageService.Chat
// 过滤好友资料 // 过滤好友资料
var userChatFriendQuery = DbContext.Set<UserChatFriend>() var userChatFriendQuery = DbContext.Set<UserChatFriend>()
.Where(ucf => ucf.Status == UserFriendStatus.Added)
.WhereIf(!filter.IsNullOrWhiteSpace(), ucf => ucf.RemarkName.Contains(filter)); .WhereIf(!filter.IsNullOrWhiteSpace(), ucf => ucf.RemarkName.Contains(filter));
// 组合查询 // 组合查询
@ -146,7 +149,7 @@ namespace LINGYUN.Abp.MessageService.Chat
on ucf.FrientId equals ucc.UserId on ucf.FrientId equals ucc.UserId
join um in userReceiveMsgQuery join um in userReceiveMsgQuery
on ucc.UserId equals um.CreatorId on ucc.UserId equals um.CreatorId
where ucf.UserId == userId where ucf.UserId == userId && ucf.Status == UserFriendStatus.Added
orderby um.CreationTime descending // 消息创建时间倒序 orderby um.CreationTime descending // 消息创建时间倒序
select new UserFriend select new UserFriend
{ {
@ -178,6 +181,7 @@ namespace LINGYUN.Abp.MessageService.Chat
.WhereIf(!filter.IsNullOrWhiteSpace(), ucc => ucc.UserName.Contains(filter) || ucc.NickName.Contains(filter)); .WhereIf(!filter.IsNullOrWhiteSpace(), ucc => ucc.UserName.Contains(filter) || ucc.NickName.Contains(filter));
var userChatFriendQuery = DbContext.Set<UserChatFriend>() var userChatFriendQuery = DbContext.Set<UserChatFriend>()
.Where(ucf => ucf.Status == UserFriendStatus.Added)
.WhereIf(!filter.IsNullOrWhiteSpace(), ucf => ucf.RemarkName.Contains(filter)); .WhereIf(!filter.IsNullOrWhiteSpace(), ucf => ucf.RemarkName.Contains(filter));
var userFriendQuery = from ucf in userChatFriendQuery var userFriendQuery = from ucf in userChatFriendQuery

305
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatGroupRepository.cs

@ -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));
//}
}
}

8
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreUserChatSettingRepository.cs

@ -18,19 +18,13 @@ namespace LINGYUN.Abp.MessageService.Chat
{ {
} }
public async Task<UserChatSetting> GetByUserIdAsync(Guid userId) public async Task<UserChatSetting> FindByUserIdAsync(Guid userId)
{ {
return await DbSet.Where(x => x.UserId.Equals(userId)) return await DbSet.Where(x => x.UserId.Equals(userId))
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
} }
public async Task<bool> UserHasBlackedAsync(Guid formUserId, Guid toUserId)
{
return await DbContext.Set<UserChatBlack>()
.AnyAsync(x => x.UserId.Equals(toUserId) && x.ShieldUserId.Equals(formUserId));
}
public async Task<bool> UserHasOpendImAsync(Guid userId) public async Task<bool> UserHasOpendImAsync(Guid userId)
{ {
return await DbSet.AnyAsync(x => x.UserId.Equals(userId)); return await DbSet.AnyAsync(x => x.UserId.Equals(userId));

25
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/EntityFrameworkCore/MessageServiceDbContextModelCreatingExtensions.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.MessageService.Chat; using LINGYUN.Abp.MessageService.Chat;
using LINGYUN.Abp.MessageService.Group;
using LINGYUN.Abp.MessageService.Notifications; using LINGYUN.Abp.MessageService.Notifications;
using LINGYUN.Abp.MessageService.Subscriptions; using LINGYUN.Abp.MessageService.Subscriptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -137,23 +138,23 @@ namespace LINGYUN.Abp.MessageService.EntityFrameworkCore
b.HasIndex(p => new { p.TenantId, p.UserId }); b.HasIndex(p => new { p.TenantId, p.UserId });
}); });
builder.Entity<UserSpecialFocus>(b => //builder.Entity<UserSpecialFocus>(b =>
{ //{
b.ToTable(options.TablePrefix + "UserSpecialFocuss", options.Schema); // b.ToTable(options.TablePrefix + "UserSpecialFocuss", options.Schema);
b.ConfigureMultiTenant(); // b.ConfigureMultiTenant();
b.HasIndex(p => new { p.TenantId, p.UserId }); // b.HasIndex(p => new { p.TenantId, p.UserId });
}); //});
builder.Entity<UserChatBlack>(b => //builder.Entity<UserChatBlack>(b =>
{ //{
b.ToTable(options.TablePrefix + "UserChatBlacks", options.Schema); // b.ToTable(options.TablePrefix + "UserChatBlacks", options.Schema);
b.ConfigureMultiTenant(); // b.ConfigureMultiTenant();
b.HasIndex(p => new { p.TenantId, p.UserId }); // b.HasIndex(p => new { p.TenantId, p.UserId });
}); //});
builder.Entity<GroupChatBlack>(b => builder.Entity<GroupChatBlack>(b =>
{ {

4
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Chat/EfCoreGroupRepository.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Group/EfCoreGroupRepository.cs

@ -9,7 +9,7 @@ using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
namespace LINGYUN.Abp.MessageService.Chat namespace LINGYUN.Abp.MessageService.Group
{ {
public class EfCoreGroupRepository : EfCoreRepository<IMessageServiceDbContext, ChatGroup, long>, public class EfCoreGroupRepository : EfCoreRepository<IMessageServiceDbContext, ChatGroup, long>,
IGroupRepository, ITransientDependency IGroupRepository, ITransientDependency
@ -20,7 +20,7 @@ namespace LINGYUN.Abp.MessageService.Chat
{ {
} }
public virtual async Task<ChatGroup> GetByIdAsync( public virtual async Task<ChatGroup> FindByIdAsync(
long id, long id,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {

166
aspnet-core/modules/message/LINGYUN.Abp.MessageService.EntityFrameworkCore/LINGYUN/Abp/MessageService/Group/EfCoreUserChatGroupRepository.cs

@ -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);
}
}
}

42
aspnet-core/modules/message/LINGYUN.Abp.MessageService.HttpApi/LINGYUN/Abp/MessageService/Chat/ChatController.cs

@ -1,5 +1,4 @@
using LINGYUN.Abp.IM.Group; using LINGYUN.Abp.IM.Messages;
using LINGYUN.Abp.IM.Messages;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
@ -19,25 +18,11 @@ namespace LINGYUN.Abp.MessageService.Chat
_chatAppService = chatAppService; _chatAppService = chatAppService;
} }
[HttpPost]
[Route("groups/join")]
public virtual async Task ApplyJoinGroupAsync(UserJoinGroupDto input)
{
await _chatAppService.ApplyJoinGroupAsync(input);
}
[HttpGet] [HttpGet]
[Route("group/messages")] [Route("group/messages")]
public virtual async Task<PagedResultDto<ChatMessage>> GetGroupMessageAsync(GroupMessageGetByPagedDto input) public virtual async Task<PagedResultDto<ChatMessage>> GetMyGroupMessageAsync(GroupMessageGetByPagedDto input)
{ {
return await _chatAppService.GetGroupMessageAsync(input); return await _chatAppService.GetMyGroupMessageAsync(input);
}
[HttpGet]
[Route("groups/users")]
public virtual async Task<PagedResultDto<GroupUserCard>> GetGroupUsersAsync(GroupUserGetByPagedDto input)
{
return await _chatAppService.GetGroupUsersAsync(input);
} }
[HttpGet] [HttpGet]
@ -54,27 +39,6 @@ namespace LINGYUN.Abp.MessageService.Chat
return await _chatAppService.GetMyLastChatMessageAsync(input); return await _chatAppService.GetMyLastChatMessageAsync(input);
} }
[HttpGet]
[Route("groups/me")]
public virtual async Task<ListResultDto<Group>> GetMyGroupsAsync()
{
return await _chatAppService.GetMyGroupsAsync();
}
[HttpPost]
[Route("groups/users/accept")]
public virtual async Task GroupAcceptUserAsync(GroupAcceptUserDto input)
{
await _chatAppService.GroupAcceptUserAsync(input);
}
[HttpDelete]
[Route("groups/users/remove")]
public virtual async Task GroupRemoveUserAsync(GroupRemoveUserDto input)
{
await _chatAppService.GroupRemoveUserAsync(input);
}
[HttpGet] [HttpGet]
[Route("send-message")] [Route("send-message")]
public virtual async Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input) public virtual async Task<ChatMessageSendResultDto> SendMessageAsync(ChatMessage input)

BIN
aspnet-core/services/apigateway/LINGYUN.ApiGateway.Host/event-bus-cap.db

Binary file not shown.

2
aspnet-core/tests/LINGYUN.Abp.Features.LimitValidation.Redis.Tests/LINGYUN.Abp.Features.LimitValidation.Redis.Tests.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace /> <RootNamespace />
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>

2
aspnet-core/tests/LINGYUN.Abp.Features.LimitValidation.Tests/LINGYUN.Abp.Features.LimitValidation.Tests.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace /> <RootNamespace />
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

81
vueJs/src/components/InstantMessage/index.vue

@ -39,7 +39,7 @@ import { User } from '@/api/users'
class MyContract { class MyContract {
id = '' id = ''
displayName = '' displayName = ''
avatar = '' avatar = 'http://upload.qqbodys.com/allimg/1710/1035512943-0.jpg'
type = '' type = ''
index = 'A' index = 'A'
unread = 0 unread = 0
@ -92,10 +92,20 @@ class Message {
this.content = chatMessage.content this.content = chatMessage.content
this.fromUser.id = chatMessage.formUserId this.fromUser.id = chatMessage.formUserId
this.fromUser.displayName = chatMessage.formUserName this.fromUser.displayName = chatMessage.formUserName
this.fromUser.avatar = 'http://upload.qqbodys.com/allimg/1710/1035512943-0.jpg'
this.toContactId = chatMessage.toUserId this.toContactId = chatMessage.toUserId
this.type = ChatMessage.getType(chatMessage.messageType) this.type = ChatMessage.getType(chatMessage.messageType)
this.sendTime = new Date(chatMessage.sendTime).getTime() this.sendTime = new Date(chatMessage.sendTime).getTime()
} }
public toChatMessage() {
const chatMessage = new ChatMessage()
chatMessage.formUserId = this.fromUser.id
chatMessage.formUserName = this.fromUser.displayName
chatMessage.toUserId = this.toContactId
chatMessage.content = this.content
return chatMessage
}
} }
@Component({ @Component({
@ -129,6 +139,7 @@ export default class InstantMessage extends mixins(EventBusMiXin) {
this.unSubscribeAll() this.unSubscribeAll()
this.subscribe('onShowImDialog', this.onShowImDialog) this.subscribe('onShowImDialog', this.onShowImDialog)
this.subscribe('onUserFriendAdded', this.onUserFriendAdded) this.subscribe('onUserFriendAdded', this.onUserFriendAdded)
this.subscribe('onReceivedChatMessage', this.onReceivedChatMessage)
this.handleInitDefaultMenus() this.handleInitDefaultMenus()
this.handleStartConnection() this.handleStartConnection()
} }
@ -270,8 +281,7 @@ export default class InstantMessage extends mixins(EventBusMiXin) {
next(messages, isEnd) next(messages, isEnd)
}) })
.catch(() => { .catch(() => {
next(new Array<Message>(), true) next([], true)
imui.messageViewToBottom()
}) })
} }
@ -279,38 +289,41 @@ export default class InstantMessage extends mixins(EventBusMiXin) {
console.log('Event:menu-avatar-click') console.log('Event:menu-avatar-click')
} }
private handleMessageClick(e: any, key: any, message: any) { private handleMessageClick(e: any, key: any, message: Message) {
console.log(e) console.log(e)
console.log(key) console.log(key)
console.log(message) console.log(message)
}
private handleReceiveMessage(chatMessage: ChatMessage) {
const message = new Message()
message.fromChatMessage(chatMessage)
const imui = this.$refs.IMUI as any const imui = this.$refs.IMUI as any
imui.appendMessage(message, chatMessage.formUserId) if (key === 'status') {
const currentContact = imui.currentContact imui.updateMessage(message.id, message.toContactId, {
if (currentContact && currentContact.id === chatMessage.formUserId) { status: 'going'
currentContact.lastContent = chatMessage.content })
currentContact.lastSendTime = new Date(chatMessage.sendTime).getTime() const chatMessage = message.toChatMessage()
} else { this.connection
imui.updateContact(chatMessage.formUserId, { .invoke('SendMessage', chatMessage)
unread: '+1', .then(() => {
lastSendTime: new Date(chatMessage.sendTime).getTime(), imui
lastContent: chatMessage.content .updateMessage(message.id, message.toContactId, {
status: 'succeed'
})
})
.catch(() => {
imui
.updateMessage(message.id, message.toContactId, {
status: 'failed'
})
}) })
} }
} }
private handleReceiveMessage(chatMessage: ChatMessage) {
this.trigger('onReceivedChatMessage', chatMessage)
}
private handleSendMessage(message: Message, next: any, file: any) { private handleSendMessage(message: Message, next: any, file: any) {
console.log(message, next, file) console.log(message, next, file)
const imui = this.$refs.IMUI as any const imui = this.$refs.IMUI as any
const chatMessage = new ChatMessage() const chatMessage = message.toChatMessage()
chatMessage.formUserId = message.fromUser.id
chatMessage.formUserName = message.fromUser.displayName
chatMessage.toUserId = message.toContactId
chatMessage.content = message.content
this.connection this.connection
.invoke('SendMessage', chatMessage) .invoke('SendMessage', chatMessage)
.then(() => { .then(() => {
@ -326,12 +339,32 @@ export default class InstantMessage extends mixins(EventBusMiXin) {
}) })
} }
private onReceivedChatMessage(chatMessage: ChatMessage) {
const message = new Message()
message.fromChatMessage(chatMessage)
const imui = this.$refs.IMUI as any
imui.appendMessage(message, chatMessage.formUserId)
const currentContact = imui.currentContact
if (currentContact && currentContact.id === chatMessage.formUserId) {
currentContact.lastContent = chatMessage.content
currentContact.lastSendTime = new Date(chatMessage.sendTime).getTime()
} else {
imui.updateContact(chatMessage.formUserId, {
unread: '+1',
lastSendTime: new Date(chatMessage.sendTime).getTime(),
lastContent: chatMessage.content
})
}
}
private onChangeContract(contract: any) { private onChangeContract(contract: any) {
const imui = this.$refs.IMUI as any const imui = this.$refs.IMUI as any
imui.updateContact(contract.id, { imui.updateContact(contract.id, {
unread: 0 unread: 0
}) })
imui.closeDrawer() imui.closeDrawer()
imui.forceUpdateMessage()
console.log(imui.contacts)
} }
private onUserFriendAdded(user: User) { private onUserFriendAdded(user: User) {

3
vueJs/src/components/Notification/index.vue

@ -53,6 +53,9 @@ export default class extends mixins(EventBusMiXin) {
this.subscribe('onNotificationReadChanged', () => { this.subscribe('onNotificationReadChanged', () => {
this.notificationCount -= 1 this.notificationCount -= 1
}) })
this.subscribe('onReceivedChatMessage', () => {
this.notificationCount += 1
})
} }
} }
</script> </script>

Loading…
Cancel
Save