前端:
1、完善创建群聊逻辑 后端: 1、完善群聊相关接口
This commit is contained in:
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MassTransit;
|
||||
using IM_API.Configs;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace IM_API.Tests.Services
|
||||
{
|
||||
@@ -61,7 +62,9 @@ namespace IM_API.Tests.Services
|
||||
});
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object);
|
||||
var userService = new Mock<UserService>();
|
||||
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object,userService.Object);
|
||||
|
||||
// Act (执行测试: 第1页,每页2条,倒序)
|
||||
var result = await service.GetGroupListAsync(userId, page: 1, limit: 2, desc: true);
|
||||
@@ -76,8 +79,10 @@ namespace IM_API.Tests.Services
|
||||
[Fact]
|
||||
public async Task GetGroupList_ShouldReturnEmpty_WhenUserHasNoGroups()
|
||||
{
|
||||
|
||||
var userSerivce = new Mock<UserService>();
|
||||
// Arrange
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object);
|
||||
var service = new GroupService(_context, _mapper, _loggerMock.Object, _publishMock.Object, userSerivce.Object);
|
||||
|
||||
// Act
|
||||
var result = await service.GetGroupListAsync(userId: 999, page: 1, limit: 10, desc: false);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+10f79fb537b71581876f17031e09898ddf99e367")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+dc6ecf224df4e8714171e8b5d23afaa90b3a1f81")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
6a1eca496991f1712085223e3079932051f23c0e5f7568bc971c393fde95395b
|
||||
1b9e709aa84e0b4f6260cd10cf25bfc3a30c60e75a3966fc7d4cdf489eae898b
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+24
@@ -0,0 +1,24 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Interface.Services;
|
||||
using MassTransit;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupInviteActionUpdateHandler
|
||||
{
|
||||
public class RequestDbHandler : IConsumer<GroupInviteActionUpdateEvent>
|
||||
{
|
||||
private readonly IGroupService _groupService;
|
||||
public RequestDbHandler(IGroupService groupService)
|
||||
{
|
||||
_groupService = groupService;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupInviteActionUpdateEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
if(@event.Action == Models.GroupInviteState.Passed)
|
||||
{
|
||||
await _groupService.MakeGroupRequestAsync(@event.UserId, @event.InviteUserId,@event.GroupId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.VOs.Group;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupInviteActionUpdateHandler
|
||||
{
|
||||
public class SignalRHandler : IConsumer<GroupInviteActionUpdateEvent>
|
||||
{
|
||||
private IHubContext<ChatHub> _hub;
|
||||
public SignalRHandler(IHubContext<ChatHub> hub)
|
||||
{
|
||||
_hub = hub;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupInviteActionUpdateEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
var msg = new HubResponse<GroupInviteActionUpdateVo>("Event", new GroupInviteActionUpdateVo
|
||||
{
|
||||
Action = @event.Action,
|
||||
GroupId = @event.GroupId,
|
||||
InvitedUserId = @event.UserId,
|
||||
InviteUserId = @event.InviteUserId,
|
||||
InviteId = @event.InviteId
|
||||
});
|
||||
|
||||
await _hub.Clients.Users([@event.UserId.ToString(), @event.InviteUserId.ToString()])
|
||||
.SendAsync("ReceiveMessage",msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.VOs.Group;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupInviteHandler
|
||||
{
|
||||
public class GroupInviteSignalRHandler : IConsumer<GroupInviteEvent>
|
||||
{
|
||||
private readonly IHubContext<ChatHub> _hub;
|
||||
public GroupInviteSignalRHandler(IHubContext<ChatHub> hub)
|
||||
{
|
||||
_hub = hub;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupInviteEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
var list = @event.Ids.Select(id => id.ToString()).ToArray();
|
||||
var msg = new HubResponse<GroupInviteVo>("Event", new GroupInviteVo { GroupId = @event.GroupId, UserId = @event.UserId });
|
||||
await _hub.Clients.Users(list).SendAsync("ReceiveMessage", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Interface.Services;
|
||||
using MassTransit;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupJoinHandler
|
||||
{
|
||||
public class GroupJoinConversationHandler : IConsumer<GroupJoinEvent>
|
||||
{
|
||||
private IConversationService _conversationService;
|
||||
public GroupJoinConversationHandler(IConversationService conversationService)
|
||||
{
|
||||
_conversationService = conversationService;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupJoinEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
await _conversationService.MakeConversationAsync(@event.UserId, @event.GroupId, Models.ChatType.GROUP);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Interface.Services;
|
||||
using MassTransit;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupJoinHandler
|
||||
{
|
||||
public class GroupJoinDbHandler : IConsumer<GroupJoinEvent>
|
||||
{
|
||||
private readonly IGroupService _groupService;
|
||||
public GroupJoinDbHandler(IGroupService groupService)
|
||||
{
|
||||
_groupService = groupService;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupJoinEvent> context)
|
||||
{
|
||||
await _groupService.MakeGroupMemberAsync(context.Message.UserId,
|
||||
context.Message.GroupId, context.Message.IsCreated ?
|
||||
Models.GroupMemberRole.Master : Models.GroupMemberRole.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Group;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupJoinHandler
|
||||
{
|
||||
public class GroupJoinSignalrHandler : IConsumer<GroupJoinEvent>
|
||||
{
|
||||
private readonly IHubContext<ChatHub> _hub;
|
||||
private readonly IDatabase _redis;
|
||||
public GroupJoinSignalrHandler(IHubContext<ChatHub> hub, IConnectionMultiplexer connectionMultiplexer)
|
||||
{
|
||||
_hub = hub;
|
||||
_redis = connectionMultiplexer.GetDatabase();
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupJoinEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
string stramKey = StreamKeyBuilder.Group(@event.GroupId);
|
||||
//将用户加入群组通知
|
||||
var list = await _redis.SetMembersAsync(RedisKeys.GetConnectionIdKey(@event.UserId.ToString()));
|
||||
if(list != null && list.Length > 0)
|
||||
{
|
||||
var tasks = list.Select(connectionId =>
|
||||
_hub.Groups.AddToGroupAsync(connectionId!, stramKey)
|
||||
).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
//发送通知给群成员
|
||||
var msg = new GroupJoinVo
|
||||
{
|
||||
GroupId = @event.GroupId,
|
||||
UserId = @event.UserId
|
||||
};
|
||||
await _hub.Clients.Group(stramKey).SendAsync("ReceiveMessage",new HubResponse<GroupJoinVo>("Event",msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -1,13 +1,17 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Hubs;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupRequestHandler
|
||||
{
|
||||
public class GroupRequestSignalRHandler : IConsumer<GroupRequestEvent>
|
||||
public class GroupRequestSignalRHandler(IHubContext<ChatHub> hubContext) : IConsumer<GroupRequestEvent>
|
||||
{
|
||||
Task IConsumer<GroupRequestEvent>.Consume(ConsumeContext<GroupRequestEvent> context)
|
||||
private readonly IHubContext<ChatHub> _hub = hubContext;
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupRequestEvent> context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using IM_API.Domain.Events;
|
||||
using MassTransit;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupRequestHandler
|
||||
{
|
||||
public class NextEventHandler : IConsumer<GroupRequestEvent>
|
||||
{
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
public NextEventHandler(IPublishEndpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupRequestEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
if(@event.Action == Models.GroupRequestState.Passed)
|
||||
{
|
||||
await _endpoint.Publish(new GroupJoinEvent
|
||||
{
|
||||
AggregateId = @event.AggregateId,
|
||||
OccurredAt = @event.OccurredAt,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = @event.GroupId,
|
||||
OperatorId = @event.OperatorId,
|
||||
UserId = @event.UserId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using IM_API.Domain.Events;
|
||||
using MassTransit;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupRequestUpdateHandler
|
||||
{
|
||||
public class NextEventHandler : IConsumer<GroupRequestUpdateEvent>
|
||||
{
|
||||
private readonly IPublishEndpoint _endpoint;
|
||||
public NextEventHandler(IPublishEndpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupRequestUpdateEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
if(@event.Action == Models.GroupRequestState.Passed)
|
||||
{
|
||||
await _endpoint.Publish(new GroupJoinEvent
|
||||
{
|
||||
AggregateId = @event.AggregateId,
|
||||
OccurredAt = @event.OccurredAt,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = @event.GroupId,
|
||||
OperatorId = @event.OperatorId,
|
||||
UserId = @event.UserId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using IM_API.Domain.Events;
|
||||
using IM_API.Dtos;
|
||||
using IM_API.Hubs;
|
||||
using IM_API.VOs.Group;
|
||||
using MassTransit;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace IM_API.Application.EventHandlers.GroupRequestUpdateHandler
|
||||
{
|
||||
public class RequestUpdateSignalrHandler : IConsumer<GroupRequestUpdateEvent>
|
||||
{
|
||||
private readonly IHubContext<ChatHub> _hub;
|
||||
public RequestUpdateSignalrHandler(IHubContext<ChatHub> hub)
|
||||
{
|
||||
_hub = hub;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<GroupRequestUpdateEvent> context)
|
||||
{
|
||||
var msg = new HubResponse<GroupRequestUpdateVo>("Event", new GroupRequestUpdateVo
|
||||
{
|
||||
GroupId = context.Message.GroupId,
|
||||
RequestId = context.Message.RequestId,
|
||||
UserId = context.Message.UserId
|
||||
});
|
||||
await _hub.Clients.User(context.Message.UserId.ToString()).SendAsync("ReceiveMessage", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
using AutoMapper;
|
||||
using IM_API.Application.EventHandlers.FriendAddHandler;
|
||||
using IM_API.Application.EventHandlers.GroupInviteActionUpdateHandler;
|
||||
using IM_API.Application.EventHandlers.GroupInviteHandler;
|
||||
using IM_API.Application.EventHandlers.GroupJoinHandler;
|
||||
using IM_API.Application.EventHandlers.GroupRequestHandler;
|
||||
using IM_API.Application.EventHandlers.GroupRequestUpdateHandler;
|
||||
using IM_API.Application.EventHandlers.MessageCreatedHandler;
|
||||
using IM_API.Application.EventHandlers.RequestFriendHandler;
|
||||
using IM_API.Configs.Options;
|
||||
using IM_API.Domain.Events;
|
||||
using MassTransit;
|
||||
using MySqlConnector;
|
||||
|
||||
@@ -21,6 +27,16 @@ namespace IM_API.Configs
|
||||
x.AddConsumer<RequestFriendSignalRHandler>();
|
||||
x.AddConsumer<FriendAddConversationHandler>();
|
||||
x.AddConsumer<MessageCreatedDbHandler>();
|
||||
x.AddConsumer<GroupJoinConversationHandler>();
|
||||
x.AddConsumer<GroupJoinDbHandler>();
|
||||
x.AddConsumer<GroupJoinSignalrHandler>();
|
||||
x.AddConsumer<GroupRequestSignalRHandler>();
|
||||
x.AddConsumer<Application.EventHandlers.GroupRequestHandler.NextEventHandler>();
|
||||
x.AddConsumer<Application.EventHandlers.GroupRequestUpdateHandler.NextEventHandler>();
|
||||
x.AddConsumer<GroupInviteSignalRHandler>();
|
||||
x.AddConsumer<RequestDbHandler>();
|
||||
x.AddConsumer<SignalRHandler>();
|
||||
x.AddConsumer<RequestUpdateSignalrHandler>();
|
||||
x.UsingRabbitMq((ctx,cfg) =>
|
||||
{
|
||||
cfg.Host(options.Host, "/", h =>
|
||||
|
||||
@@ -32,12 +32,40 @@ namespace IM_API.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<GroupInfoDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> CreateGroup([FromBody]GroupCreateDto groupCreateDto)
|
||||
public async Task<IActionResult> CreateGroup([FromBody]GroupCreateDto groupCreateDto)
|
||||
{
|
||||
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var groupInfo = await _groupService.CreateGroupAsync(int.Parse(userIdStr), groupCreateDto);
|
||||
var res = new BaseResponse<GroupInfoDto>(groupInfo);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<object?>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> HandleGroupInvite([FromBody]HandleGroupInviteDto dto)
|
||||
{
|
||||
string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
|
||||
await _groupService.HandleGroupInviteAsync(int.Parse(userIdStr), dto);
|
||||
var res = new BaseResponse<object?>();
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<object?>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> HandleGroupRequest([FromBody]HandleGroupRequestDto dto)
|
||||
{
|
||||
string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
await _groupService.HandleGroupRequestAsync(int.Parse(userIdStr),dto);
|
||||
var res = new BaseResponse<object?>();
|
||||
return Ok(res);
|
||||
}
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(BaseResponse<object?>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> InviteUser([FromBody]GroupInviteUserDto dto)
|
||||
{
|
||||
string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
await _groupService.InviteUsersAsync(int.Parse(userIdStr), dto.GroupId, dto.Ids);
|
||||
var res = new BaseResponse<object?>();
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Domain.Events
|
||||
{
|
||||
public record GroupInviteActionUpdateEvent : DomainEvent
|
||||
{
|
||||
public override string EventType => "IM.GROUPS_INVITE_UPDATE";
|
||||
public int UserId { get; set; }
|
||||
public int InviteUserId { get; set; }
|
||||
public int InviteId { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
public GroupInviteState Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
public record GroupInviteEvent : DomainEvent
|
||||
{
|
||||
public override string EventType => "IM.GROUPS_GROUP_INVITE";
|
||||
public override string EventType => "IM.GROUPS_INVITE_ADD";
|
||||
public required List<int> Ids { get; init; }
|
||||
public int GroupId { get; init; }
|
||||
public int UserId { get; init; }
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace IM_API.Domain.Events
|
||||
{
|
||||
public record GroupJoinEvent : DomainEvent
|
||||
{
|
||||
public override string EventType => "IM.GROUPS_MEMBER_ADD";
|
||||
public int UserId { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
public bool IsCreated { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Domain.Events
|
||||
{
|
||||
@@ -8,6 +9,7 @@ namespace IM_API.Domain.Events
|
||||
public int GroupId { get; init; }
|
||||
public int UserId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public GroupRequestState Action { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Domain.Events
|
||||
{
|
||||
public record GroupRequestUpdateEvent : DomainEvent
|
||||
{
|
||||
public override string EventType => "IM.GROUPS_REQUEST_UPDATE";
|
||||
public int UserId { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
public int AdminUserId { get; set; }
|
||||
public int RequestId { get; set; }
|
||||
public GroupRequestState Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace IM_API.Dtos.Group
|
||||
{
|
||||
public class GroupInviteUserDto
|
||||
{
|
||||
public int GroupId { get; set; }
|
||||
public List<int> Ids { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Dtos.Group
|
||||
{
|
||||
public class HandleGroupInviteDto
|
||||
{
|
||||
public int InviteId { get; set; }
|
||||
public GroupInviteState Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Dtos.Group
|
||||
{
|
||||
public class HandleGroupRequestDto
|
||||
{
|
||||
public int RequestId { get; set; }
|
||||
public GroupRequestState Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using IM_API.Models;
|
||||
using IM_API.Tools;
|
||||
using IM_API.VOs.Message;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using StackExchange.Redis;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace IM_API.Hubs
|
||||
@@ -15,14 +16,13 @@ namespace IM_API.Hubs
|
||||
{
|
||||
private IMessageSevice _messageService;
|
||||
private readonly IConversationService _conversationService;
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly IMapper _mapper;
|
||||
public ChatHub(IMessageSevice messageService, IConversationService conversationService, IEventBus eventBus, IMapper mapper)
|
||||
private readonly IDatabase _redis;
|
||||
public ChatHub(IMessageSevice messageService, IConversationService conversationService,
|
||||
IConnectionMultiplexer connectionMultiplexer)
|
||||
{
|
||||
_messageService = messageService;
|
||||
_conversationService = conversationService;
|
||||
_eventBus = eventBus;
|
||||
_mapper = mapper;
|
||||
_redis = connectionMultiplexer.GetDatabase();
|
||||
}
|
||||
|
||||
public async override Task OnConnectedAsync()
|
||||
@@ -32,6 +32,7 @@ namespace IM_API.Hubs
|
||||
Context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
//将用户加入已加入聊天的会话组
|
||||
string userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var keys = await _conversationService.GetUserAllStreamKeyAsync(int.Parse(userIdStr));
|
||||
@@ -39,8 +40,20 @@ namespace IM_API.Hubs
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, key);
|
||||
}
|
||||
//储存用户对应的连接id
|
||||
await _redis.SetAddAsync(RedisKeys.GetConnectionIdKey(userIdStr), Context.ConnectionId);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
if (!Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
string useridStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
await _redis.SetRemoveAsync(RedisKeys.GetConnectionIdKey(useridStr), Context.ConnectionId);
|
||||
}
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
public async Task<HubResponse<MessageBaseVo?>> SendMessage(MessageBaseDto dto)
|
||||
{
|
||||
if (!Context.User.Identity.IsAuthenticated)
|
||||
|
||||
@@ -33,8 +33,4 @@
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Application\EventHandlers\GroupInviteHandler\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using IM_API.Dtos.Group;
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.Interface.Services
|
||||
{
|
||||
@@ -43,6 +44,12 @@ namespace IM_API.Interface.Services
|
||||
/// <param name="desc"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<GroupInfoDto>> GetGroupListAsync(int userId, int page, int limit, bool desc);
|
||||
|
||||
Task UpdateGroupConversationAsync(GroupUpdateConversationDto dto);
|
||||
|
||||
Task HandleGroupInviteAsync(int userid, HandleGroupInviteDto dto);
|
||||
Task HandleGroupRequestAsync(int userid, HandleGroupRequestDto dto);
|
||||
Task MakeGroupRequestAsync(int userId,int? adminUserId,int groupId);
|
||||
Task MakeGroupMemberAsync(int userId, int groupId, GroupMemberRole? role);
|
||||
}
|
||||
}
|
||||
|
||||
+1117
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace IM_API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class updategroupgroupiduserid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "group_request_ibfk_2",
|
||||
table: "group_request");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_group_request_UserId",
|
||||
table: "group_request",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "group_request_ibfk_2",
|
||||
table: "group_request",
|
||||
column: "UserId",
|
||||
principalTable: "users",
|
||||
principalColumn: "ID");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "group_request_ibfk_2",
|
||||
table: "group_request");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_group_request_UserId",
|
||||
table: "group_request");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "group_request_ibfk_2",
|
||||
table: "group_request",
|
||||
column: "GroupId",
|
||||
principalTable: "users",
|
||||
principalColumn: "ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,6 +513,8 @@ namespace IM_API.Migrations
|
||||
b.HasKey("Id")
|
||||
.HasName("PRIMARY");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex(new[] { "GroupId" }, "GroupId")
|
||||
.HasDatabaseName("GroupId1");
|
||||
|
||||
@@ -985,15 +987,15 @@ namespace IM_API.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("group_request_ibfk_1");
|
||||
|
||||
b.HasOne("IM_API.Models.User", "GroupNavigation")
|
||||
b.HasOne("IM_API.Models.User", "User")
|
||||
.WithMany("GroupRequests")
|
||||
.HasForeignKey("GroupId")
|
||||
.HasForeignKey("UserId")
|
||||
.IsRequired()
|
||||
.HasConstraintName("group_request_ibfk_2");
|
||||
|
||||
b.Navigation("Group");
|
||||
|
||||
b.Navigation("GroupNavigation");
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IM_API.Models.LoginLog", b =>
|
||||
|
||||
@@ -37,5 +37,5 @@ public partial class GroupRequest
|
||||
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
|
||||
public virtual User GroupNavigation { get; set; } = null!;
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
@@ -475,8 +475,8 @@ public partial class ImContext : DbContext
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("group_request_ibfk_1");
|
||||
|
||||
entity.HasOne(d => d.GroupNavigation).WithMany(p => p.GroupRequests)
|
||||
.HasForeignKey(d => d.GroupId)
|
||||
entity.HasOne(d => d.User).WithMany(p => p.GroupRequests)
|
||||
.HasForeignKey(d => d.UserId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("group_request_ibfk_2");
|
||||
});
|
||||
|
||||
@@ -18,31 +18,25 @@ namespace IM_API.Services
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILogger<GroupService> _logger;
|
||||
private readonly IPublishEndpoint _endPoint;
|
||||
public GroupService(ImContext context, IMapper mapper, ILogger<GroupService> logger, IPublishEndpoint publishEndpoint)
|
||||
private readonly IUserService _userService;
|
||||
public GroupService(ImContext context, IMapper mapper, ILogger<GroupService> logger,
|
||||
IPublishEndpoint publishEndpoint, IUserService userService)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
_endPoint = publishEndpoint;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
private async Task<List<GroupInvite>> GetGroupInvites(int userId, int groupId, List<int> ids)
|
||||
private async Task<List<int>> validFriendshipAsync (int userId, List<int> ids)
|
||||
{
|
||||
DateTime dateTime = DateTime.Now;
|
||||
DateTime dateTime = DateTime.UtcNow;
|
||||
//验证被邀请用户是否为好友
|
||||
var validFriendIds = await _context.Friends
|
||||
return await _context.Friends
|
||||
.Where(f => f.UserId == userId && ids.Contains(f.FriendId))
|
||||
.Select(f => f.FriendId)
|
||||
.ToListAsync();
|
||||
//创建群成员对象
|
||||
return validFriendIds.Select(fid => new GroupInvite
|
||||
{
|
||||
Created = dateTime,
|
||||
GroupId = groupId,
|
||||
InvitedUser = fid,
|
||||
StateEnum = GroupInviteState.Pending,
|
||||
InviteUser = userId
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<GroupInfoDto> CreateGroupAsync(int userId, GroupCreateDto groupCreateDto)
|
||||
@@ -57,31 +51,21 @@ namespace IM_API.Services
|
||||
group.GroupMaster = userId;
|
||||
_context.Groups.Add(group);
|
||||
await _context.SaveChangesAsync();
|
||||
var groupInvites = new List<GroupInvite>();
|
||||
if (userIds.Count > 0)
|
||||
{
|
||||
groupInvites = await GetGroupInvites(userId,group.Id, userIds);
|
||||
_context.GroupInvites.AddRange(groupInvites);
|
||||
//邀请好友
|
||||
await InviteUsersAsync(userId, group.Id ,userIds);
|
||||
}
|
||||
var groupMember = new GroupMember
|
||||
{
|
||||
UserId = userId,
|
||||
Created = dateTime,
|
||||
RoleEnum = GroupMemberRole.Master,
|
||||
GroupId = group.Id
|
||||
};
|
||||
_context.GroupMembers.Add(groupMember);
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
await _endPoint.Publish(new GroupInviteEvent
|
||||
await _endPoint.Publish(new GroupJoinEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
AggregateId = userId.ToString(),
|
||||
GroupId = group.Id,
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAt = dateTime,
|
||||
Ids = groupInvites.Select(x => x.Id).ToList(),
|
||||
OperatorId = userId,
|
||||
UserId = userId
|
||||
UserId = userId,
|
||||
IsCreated = true
|
||||
});
|
||||
return _mapper.Map<GroupInfoDto>(group);
|
||||
}
|
||||
@@ -101,7 +85,43 @@ namespace IM_API.Services
|
||||
{
|
||||
var group = await _context.Groups.FirstOrDefaultAsync(
|
||||
x => x.Id == groupId) ?? throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
|
||||
|
||||
//过滤非好友
|
||||
var groupInviteIds = await validFriendshipAsync(userId, userIds);
|
||||
var inviteList = groupInviteIds.Select(id => new GroupInvite
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
GroupId = group.Id,
|
||||
InviteUser = userId,
|
||||
InvitedUser = id,
|
||||
StateEnum = GroupInviteState.Pending
|
||||
}).ToList();
|
||||
_context.GroupInvites.AddRange(inviteList);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteEvent
|
||||
{
|
||||
GroupId = groupId,
|
||||
AggregateId = userId.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
Ids = userIds,
|
||||
OperatorId = userId,
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task MakeGroupMemberAsync(int userId, int groupId ,GroupMemberRole? role)
|
||||
{
|
||||
var isExist = await _context.GroupMembers.AnyAsync(x => x.GroupId == groupId && x.UserId == userId);
|
||||
if (isExist) return;
|
||||
var groupMember = new GroupMember
|
||||
{
|
||||
UserId = userId,
|
||||
Created = DateTime.UtcNow,
|
||||
RoleEnum = role ?? GroupMemberRole.Normal,
|
||||
GroupId = groupId
|
||||
};
|
||||
_context.GroupMembers.Add(groupMember);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task JoinGroupAsync(int userId, int groupId)
|
||||
@@ -136,5 +156,96 @@ namespace IM_API.Services
|
||||
_context.Groups.Update(group);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task HandleGroupInviteAsync(int userid, HandleGroupInviteDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
var inviteInfo = await _context.GroupInvites.FirstOrDefaultAsync(x => x.Id == dto.InviteId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
if (inviteInfo.InvitedUser != userid) throw new BaseException(CodeDefine.AUTH_FAILED);
|
||||
inviteInfo.StateEnum = dto.Action;
|
||||
_context.GroupInvites.Update(inviteInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
await _endPoint.Publish(new GroupInviteActionUpdateEvent
|
||||
{
|
||||
Action = dto.Action,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = inviteInfo.GroupId,
|
||||
InviteId = inviteInfo.Id,
|
||||
InviteUserId = inviteInfo.InviteUser.Value,
|
||||
OperatorId = userid,
|
||||
UserId = userid
|
||||
|
||||
});
|
||||
}
|
||||
public async Task HandleGroupRequestAsync(int userid, HandleGroupRequestDto dto)
|
||||
{
|
||||
var user = _userService.GetUserInfoAsync(userid);
|
||||
//判断请求存在
|
||||
var requestInfo = await _context.GroupRequests.FirstOrDefaultAsync(x => x.Id == dto.RequestId)
|
||||
?? throw new BaseException(CodeDefine.INVALID_ACTION);
|
||||
//判断成员存在
|
||||
var memberInfo = await _context.GroupMembers.FirstOrDefaultAsync(x => x.UserId == userid)
|
||||
?? throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
//判断成员权限
|
||||
if (memberInfo.RoleEnum != GroupMemberRole.Master && memberInfo.RoleEnum != GroupMemberRole.Administrator)
|
||||
throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
|
||||
|
||||
requestInfo.StateEnum = dto.Action;
|
||||
_context.GroupRequests.Update(requestInfo);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestUpdateEvent
|
||||
{
|
||||
Action = requestInfo.StateEnum,
|
||||
AdminUserId = userid,
|
||||
AggregateId = userid.ToString(),
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
EventId = Guid.NewGuid(),
|
||||
GroupId = requestInfo.GroupId,
|
||||
OperatorId = userid,
|
||||
UserId = requestInfo.UserId,
|
||||
RequestId = requestInfo.Id
|
||||
});
|
||||
}
|
||||
public async Task MakeGroupRequestAsync(int userId, int? adminUserId, int groupId)
|
||||
{
|
||||
var requestInfo = await _context.GroupRequests
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId && x.GroupId == groupId);
|
||||
if (requestInfo != null) return;
|
||||
|
||||
var member = await _context.GroupMembers.FirstOrDefaultAsync(
|
||||
x => x.UserId == adminUserId && x.GroupId == groupId);
|
||||
var request = new GroupRequest
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Description = string.Empty,
|
||||
GroupId = groupId,
|
||||
UserId = userId,
|
||||
StateEnum = GroupRequestState.Pending
|
||||
};
|
||||
if(member != null && (
|
||||
member.RoleEnum == GroupMemberRole.Administrator || member.RoleEnum == GroupMemberRole.Master))
|
||||
{
|
||||
request.StateEnum = GroupRequestState.Passed;
|
||||
}
|
||||
|
||||
_context.GroupRequests.Add(request);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _endPoint.Publish(new GroupRequestEvent
|
||||
{
|
||||
OccurredAt = DateTime.UtcNow,
|
||||
Description = request.Description,
|
||||
GroupId = request.GroupId,
|
||||
Action = request.StateEnum,
|
||||
UserId = userId,
|
||||
AggregateId = userId.ToString(),
|
||||
EventId = Guid.NewGuid(),
|
||||
OperatorId = userId
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,10 @@
|
||||
{
|
||||
public static class RedisKeys
|
||||
{
|
||||
public static string GetUserinfoKey(string userId)
|
||||
{
|
||||
return $"user::uinfo::{userId}";
|
||||
}
|
||||
public static string GetUserinfoKeyByUsername(string username)
|
||||
{
|
||||
return $"user::uinfobyid::{username}";
|
||||
}
|
||||
public static string GetSequenceIdKey(string streamKey)
|
||||
{
|
||||
return $"chat::seq::{streamKey}";
|
||||
}
|
||||
public static string GetSequenceIdLockKey(string streamKey)
|
||||
{
|
||||
return $"lock::seq::{streamKey}";
|
||||
}
|
||||
public static string GetUserinfoKey(string userId) => $"user::uinfo::{userId}";
|
||||
public static string GetUserinfoKeyByUsername(string username) => $"user::uinfobyid::{username}";
|
||||
public static string GetSequenceIdKey(string streamKey) => $"chat::seq::{streamKey}";
|
||||
public static string GetSequenceIdLockKey(string streamKey) => $"lock::seq::{streamKey}";
|
||||
public static string GetConnectionIdKey(string userId) => $"signalr::user::con::{userId}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using IM_API.Models;
|
||||
|
||||
namespace IM_API.VOs.Group
|
||||
{
|
||||
public class GroupInviteActionUpdateVo
|
||||
{
|
||||
public int GroupId { get; set; }
|
||||
public int InviteUserId { get; set; }
|
||||
public int InvitedUserId { get; set; }
|
||||
public int InviteId { get; set; }
|
||||
public GroupInviteState Action { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace IM_API.VOs.Group
|
||||
{
|
||||
public class GroupInviteVo
|
||||
{
|
||||
public int GroupId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace IM_API.VOs.Group
|
||||
{
|
||||
public class GroupJoinVo
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace IM_API.VOs.Group
|
||||
{
|
||||
public class GroupRequestUpdateVo
|
||||
{
|
||||
public int RequestId { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user