diff --git a/backend/IMTest/Service/GroupServiceTest.cs b/backend/IMTest/Service/GroupServiceTest.cs index fed2ebe..445150a 100644 --- a/backend/IMTest/Service/GroupServiceTest.cs +++ b/backend/IMTest/Service/GroupServiceTest.cs @@ -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(); + + 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(); // 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); diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.dll b/backend/IMTest/bin/Debug/net8.0/IMTest.dll index a98768f..a0cda45 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IMTest.dll and b/backend/IMTest/bin/Debug/net8.0/IMTest.dll differ diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.pdb b/backend/IMTest/bin/Debug/net8.0/IMTest.pdb index 1a557b1..f9575cb 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IMTest.pdb and b/backend/IMTest/bin/Debug/net8.0/IMTest.pdb differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.dll b/backend/IMTest/bin/Debug/net8.0/IM_API.dll index a277eeb..a3b4ee9 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.dll and b/backend/IMTest/bin/Debug/net8.0/IM_API.dll differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.exe b/backend/IMTest/bin/Debug/net8.0/IM_API.exe index d52763f..d0ffae4 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.exe and b/backend/IMTest/bin/Debug/net8.0/IM_API.exe differ diff --git a/backend/IMTest/bin/Debug/net8.0/IM_API.pdb b/backend/IMTest/bin/Debug/net8.0/IM_API.pdb index 4ff55ad..4d12f0b 100644 Binary files a/backend/IMTest/bin/Debug/net8.0/IM_API.pdb and b/backend/IMTest/bin/Debug/net8.0/IM_API.pdb differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs index 2e5bea1..2a2b83e 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfo.cs @@ -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")] diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache index c244404..3735720 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache @@ -1 +1 @@ -6a1eca496991f1712085223e3079932051f23c0e5f7568bc971c393fde95395b +1b9e709aa84e0b4f6260cd10cf25bfc3a30c60e75a3966fc7d4cdf489eae898b diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache index 41b6cf3..278b1a7 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache and b/backend/IMTest/obj/Debug/net8.0/IMTest.csproj.AssemblyReference.cache differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/IMTest.dll index a98768f..a0cda45 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/IMTest.dll differ diff --git a/backend/IMTest/obj/Debug/net8.0/IMTest.pdb b/backend/IMTest/obj/Debug/net8.0/IMTest.pdb index 1a557b1..f9575cb 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/IMTest.pdb and b/backend/IMTest/obj/Debug/net8.0/IMTest.pdb differ diff --git a/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll index efaca8c..9f9bfd6 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/ref/IMTest.dll differ diff --git a/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll b/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll index efaca8c..9f9bfd6 100644 Binary files a/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll and b/backend/IMTest/obj/Debug/net8.0/refint/IMTest.dll differ diff --git a/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/RequestDbHandler.cs b/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/RequestDbHandler.cs new file mode 100644 index 0000000..c5c7620 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/RequestDbHandler.cs @@ -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 + { + private readonly IGroupService _groupService; + public RequestDbHandler(IGroupService groupService) + { + _groupService = groupService; + } + + public async Task Consume(ConsumeContext context) + { + var @event = context.Message; + if(@event.Action == Models.GroupInviteState.Passed) + { + await _groupService.MakeGroupRequestAsync(@event.UserId, @event.InviteUserId,@event.GroupId); + } + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/SignalRHandler.cs b/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/SignalRHandler.cs new file mode 100644 index 0000000..7c09dc1 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupInviteActionUpdateHandler/SignalRHandler.cs @@ -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 + { + private IHubContext _hub; + public SignalRHandler(IHubContext hub) + { + _hub = hub; + } + + public async Task Consume(ConsumeContext context) + { + var @event = context.Message; + var msg = new HubResponse("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); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupInviteHandler/GroupInviteSignalRHandler.cs b/backend/IM_API/Application/EventHandlers/GroupInviteHandler/GroupInviteSignalRHandler.cs new file mode 100644 index 0000000..fc49076 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupInviteHandler/GroupInviteSignalRHandler.cs @@ -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 + { + private readonly IHubContext _hub; + public GroupInviteSignalRHandler(IHubContext hub) + { + _hub = hub; + } + + public async Task Consume(ConsumeContext context) + { + var @event = context.Message; + var list = @event.Ids.Select(id => id.ToString()).ToArray(); + var msg = new HubResponse("Event", new GroupInviteVo { GroupId = @event.GroupId, UserId = @event.UserId }); + await _hub.Clients.Users(list).SendAsync("ReceiveMessage", msg); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinConversationHandler.cs b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinConversationHandler.cs new file mode 100644 index 0000000..6992475 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinConversationHandler.cs @@ -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 + { + private IConversationService _conversationService; + public GroupJoinConversationHandler(IConversationService conversationService) + { + _conversationService = conversationService; + } + + public async Task Consume(ConsumeContext context) + { + var @event = context.Message; + await _conversationService.MakeConversationAsync(@event.UserId, @event.GroupId, Models.ChatType.GROUP); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinDbHandler.cs b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinDbHandler.cs new file mode 100644 index 0000000..a4ba4d2 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinDbHandler.cs @@ -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 + { + private readonly IGroupService _groupService; + public GroupJoinDbHandler(IGroupService groupService) + { + _groupService = groupService; + } + + public async Task Consume(ConsumeContext context) + { + await _groupService.MakeGroupMemberAsync(context.Message.UserId, + context.Message.GroupId, context.Message.IsCreated ? + Models.GroupMemberRole.Master : Models.GroupMemberRole.Normal); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinSignalrHandler.cs b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinSignalrHandler.cs new file mode 100644 index 0000000..deb7493 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupJoinHandler/GroupJoinSignalrHandler.cs @@ -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 + { + private readonly IHubContext _hub; + private readonly IDatabase _redis; + public GroupJoinSignalrHandler(IHubContext hub, IConnectionMultiplexer connectionMultiplexer) + { + _hub = hub; + _redis = connectionMultiplexer.GetDatabase(); + } + + public async Task Consume(ConsumeContext 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("Event",msg)); + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupRequestHandler/GroupRequestSignalRHandler.cs b/backend/IM_API/Application/EventHandlers/GroupRequestHandler/GroupRequestSignalRHandler.cs index 8fd19ec..69f0351 100644 --- a/backend/IM_API/Application/EventHandlers/GroupRequestHandler/GroupRequestSignalRHandler.cs +++ b/backend/IM_API/Application/EventHandlers/GroupRequestHandler/GroupRequestSignalRHandler.cs @@ -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 + public class GroupRequestSignalRHandler(IHubContext hubContext) : IConsumer { - Task IConsumer.Consume(ConsumeContext context) + private readonly IHubContext _hub = hubContext; + + public async Task Consume(ConsumeContext context) { - throw new NotImplementedException(); + } } } diff --git a/backend/IM_API/Application/EventHandlers/GroupRequestHandler/NextEventHandler.cs b/backend/IM_API/Application/EventHandlers/GroupRequestHandler/NextEventHandler.cs new file mode 100644 index 0000000..8e19129 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupRequestHandler/NextEventHandler.cs @@ -0,0 +1,31 @@ +using IM_API.Domain.Events; +using MassTransit; + +namespace IM_API.Application.EventHandlers.GroupRequestHandler +{ + public class NextEventHandler : IConsumer + { + private readonly IPublishEndpoint _endpoint; + public NextEventHandler(IPublishEndpoint endpoint) + { + _endpoint = endpoint; + } + + public async Task Consume(ConsumeContext 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 + }); + } + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/NextEventHandler.cs b/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/NextEventHandler.cs new file mode 100644 index 0000000..4bbcb8e --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/NextEventHandler.cs @@ -0,0 +1,31 @@ +using IM_API.Domain.Events; +using MassTransit; + +namespace IM_API.Application.EventHandlers.GroupRequestUpdateHandler +{ + public class NextEventHandler : IConsumer + { + private readonly IPublishEndpoint _endpoint; + public NextEventHandler(IPublishEndpoint endpoint) + { + _endpoint = endpoint; + } + + public async Task Consume(ConsumeContext 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 + }); + } + } + } +} diff --git a/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/RequestUpdateSignalrHandler.cs b/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/RequestUpdateSignalrHandler.cs new file mode 100644 index 0000000..3692545 --- /dev/null +++ b/backend/IM_API/Application/EventHandlers/GroupRequestUpdateHandler/RequestUpdateSignalrHandler.cs @@ -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 + { + private readonly IHubContext _hub; + public RequestUpdateSignalrHandler(IHubContext hub) + { + _hub = hub; + } + + public async Task Consume(ConsumeContext context) + { + var msg = new HubResponse("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); + } + } +} diff --git a/backend/IM_API/Configs/MQConfig.cs b/backend/IM_API/Configs/MQConfig.cs index 8fcbf2d..bcac5d6 100644 --- a/backend/IM_API/Configs/MQConfig.cs +++ b/backend/IM_API/Configs/MQConfig.cs @@ -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(); x.AddConsumer(); x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); x.UsingRabbitMq((ctx,cfg) => { cfg.Host(options.Host, "/", h => diff --git a/backend/IM_API/Controllers/GroupController.cs b/backend/IM_API/Controllers/GroupController.cs index 3c81ec8..0f7582f 100644 --- a/backend/IM_API/Controllers/GroupController.cs +++ b/backend/IM_API/Controllers/GroupController.cs @@ -32,12 +32,40 @@ namespace IM_API.Controllers [HttpPost] [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] - public async Task CreateGroup([FromBody]GroupCreateDto groupCreateDto) + public async Task CreateGroup([FromBody]GroupCreateDto groupCreateDto) { var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier); var groupInfo = await _groupService.CreateGroupAsync(int.Parse(userIdStr), groupCreateDto); var res = new BaseResponse(groupInfo); return Ok(res); } + + [HttpPost] + [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] + public async Task HandleGroupInvite([FromBody]HandleGroupInviteDto dto) + { + string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier)!; + await _groupService.HandleGroupInviteAsync(int.Parse(userIdStr), dto); + var res = new BaseResponse(); + return Ok(res); + } + [HttpPost] + [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] + public async Task HandleGroupRequest([FromBody]HandleGroupRequestDto dto) + { + string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier); + await _groupService.HandleGroupRequestAsync(int.Parse(userIdStr),dto); + var res = new BaseResponse(); + return Ok(res); + } + [HttpPost] + [ProducesResponseType(typeof(BaseResponse), StatusCodes.Status200OK)] + public async Task InviteUser([FromBody]GroupInviteUserDto dto) + { + string userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier); + await _groupService.InviteUsersAsync(int.Parse(userIdStr), dto.GroupId, dto.Ids); + var res = new BaseResponse(); + return Ok(res); + } } } diff --git a/backend/IM_API/Domain/Events/GroupInviteActionUpdateEvent.cs b/backend/IM_API/Domain/Events/GroupInviteActionUpdateEvent.cs new file mode 100644 index 0000000..3368dfe --- /dev/null +++ b/backend/IM_API/Domain/Events/GroupInviteActionUpdateEvent.cs @@ -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; } + } +} diff --git a/backend/IM_API/Domain/Events/GroupInviteEvent.cs b/backend/IM_API/Domain/Events/GroupInviteEvent.cs index b4bc47c..3b2ebde 100644 --- a/backend/IM_API/Domain/Events/GroupInviteEvent.cs +++ b/backend/IM_API/Domain/Events/GroupInviteEvent.cs @@ -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 Ids { get; init; } public int GroupId { get; init; } public int UserId { get; init; } diff --git a/backend/IM_API/Domain/Events/GroupJoinEvent.cs b/backend/IM_API/Domain/Events/GroupJoinEvent.cs new file mode 100644 index 0000000..a73cc3c --- /dev/null +++ b/backend/IM_API/Domain/Events/GroupJoinEvent.cs @@ -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; + } +} diff --git a/backend/IM_API/Domain/Events/GroupRequestEvent.cs b/backend/IM_API/Domain/Events/GroupRequestEvent.cs index 8c65739..ade919d 100644 --- a/backend/IM_API/Domain/Events/GroupRequestEvent.cs +++ b/backend/IM_API/Domain/Events/GroupRequestEvent.cs @@ -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; } } } diff --git a/backend/IM_API/Domain/Events/GroupRequestUpdateEvent.cs b/backend/IM_API/Domain/Events/GroupRequestUpdateEvent.cs new file mode 100644 index 0000000..579dbda --- /dev/null +++ b/backend/IM_API/Domain/Events/GroupRequestUpdateEvent.cs @@ -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; } + } +} diff --git a/backend/IM_API/Dtos/Group/GroupInviteUserDto.cs b/backend/IM_API/Dtos/Group/GroupInviteUserDto.cs new file mode 100644 index 0000000..df1d926 --- /dev/null +++ b/backend/IM_API/Dtos/Group/GroupInviteUserDto.cs @@ -0,0 +1,8 @@ +namespace IM_API.Dtos.Group +{ + public class GroupInviteUserDto + { + public int GroupId { get; set; } + public List Ids { get; set; } + } +} diff --git a/backend/IM_API/Dtos/Group/HandleGroupInviteDto.cs b/backend/IM_API/Dtos/Group/HandleGroupInviteDto.cs new file mode 100644 index 0000000..038a4bd --- /dev/null +++ b/backend/IM_API/Dtos/Group/HandleGroupInviteDto.cs @@ -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; } + } +} diff --git a/backend/IM_API/Dtos/Group/HandleGroupRequestDto.cs b/backend/IM_API/Dtos/Group/HandleGroupRequestDto.cs new file mode 100644 index 0000000..566739c --- /dev/null +++ b/backend/IM_API/Dtos/Group/HandleGroupRequestDto.cs @@ -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; } + } +} diff --git a/backend/IM_API/Hubs/ChatHub.cs b/backend/IM_API/Hubs/ChatHub.cs index e6f5bb3..14bd2b8 100644 --- a/backend/IM_API/Hubs/ChatHub.cs +++ b/backend/IM_API/Hubs/ChatHub.cs @@ -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> SendMessage(MessageBaseDto dto) { if (!Context.User.Identity.IsAuthenticated) diff --git a/backend/IM_API/IM_API.csproj b/backend/IM_API/IM_API.csproj index 9f720ca..6361cdc 100644 --- a/backend/IM_API/IM_API.csproj +++ b/backend/IM_API/IM_API.csproj @@ -33,8 +33,4 @@ - - - - diff --git a/backend/IM_API/Interface/Services/IGroupService.cs b/backend/IM_API/Interface/Services/IGroupService.cs index 4cc72d0..1b80b62 100644 --- a/backend/IM_API/Interface/Services/IGroupService.cs +++ b/backend/IM_API/Interface/Services/IGroupService.cs @@ -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 /// /// Task> 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); } } diff --git a/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.Designer.cs b/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.Designer.cs new file mode 100644 index 0000000..866d3ee --- /dev/null +++ b/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.Designer.cs @@ -0,0 +1,1117 @@ +// +using System; +using IM_API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IM_API.Migrations +{ + [DbContext(typeof(ImContext))] + [Migration("20260211065853_update-group-groupid-userid")] + partial class updategroupgroupiduserid + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("latin1_swedish_ci") + .HasAnnotation("ProductVersion", "8.0.21") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "latin1"); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("状态(0:正常,2:封禁) "); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("更新时间 "); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RoleId" }, "RoleId"); + + b.ToTable("admins", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("int(11)"); + + b.Property("LastMessage") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("最后一条最新消息"); + + b.Property("LastMessageTime") + .HasColumnType("datetime") + .HasComment("最后一条消息发送时间"); + + b.Property("LastReadSequenceId") + .HasColumnType("int(11)") + .HasColumnName("lastReadMessageId") + .HasComment("最后一条未读消息ID "); + + b.Property("MessageId") + .HasColumnType("int(11)"); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.Property("TargetId") + .HasColumnType("int(11)") + .HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) "); + + b.Property("UnreadCount") + .HasColumnType("int(11)") + .HasComment("未读消息数 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("MessageId"); + + b.HasIndex(new[] { "LastReadSequenceId" }, "LastReadSequenceId"); + + b.HasIndex(new[] { "UserId" }, "Userid"); + + b.ToTable("conversations", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3:Pad,4:未知)"); + + b.Property("LastLogin") + .HasColumnType("datetime") + .HasComment("最后一次登录 "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("设备所属用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userid") + .HasDatabaseName("Userid1"); + + b.ToTable("devices", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("文件类型 "); + + b.Property("MessageId") + .HasColumnType("int(11)") + .HasComment("关联消息ID "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("文件名 "); + + b.Property("Size") + .HasColumnType("int(11)") + .HasComment("文件大小(单位:KB) "); + + b.Property("Url") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasColumnName("URL") + .HasComment("文件储存URL "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "MessageId" }, "Messageld"); + + b.ToTable("files", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("好友头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("好友关系创建时间"); + + b.Property("FriendId") + .HasColumnType("int(11)") + .HasComment("用户2ID"); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("好友备注名"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户ID"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID"); + + b.HasIndex(new[] { "UserId", "FriendId" }, "Userld"); + + b.HasIndex(new[] { "FriendId" }, "用户2id"); + + b.ToTable("friends", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("申请时间 "); + + b.Property("Description") + .HasColumnType("text") + .HasComment("申请附言 "); + + b.Property("RemarkName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("备注"); + + b.Property("RequestUser") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.Property("ResponseUser") + .HasColumnType("int(11)") + .HasComment("被申请人 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "RequestUser" }, "RequestUser"); + + b.HasIndex(new[] { "ResponseUser" }, "ResponseUser"); + + b.ToTable("friend_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AllMembersBanned") + .HasColumnType("tinyint(4)") + .HasComment("全员禁言(0允许发言,2全员禁言)"); + + b.Property("Announcement") + .HasColumnType("text") + .HasComment("群公告"); + + b.Property("Auhority") + .HasColumnType("tinyint(4)") + .HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)"); + + b.Property("Avatar") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("群头像"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("群聊创建时间"); + + b.Property("GroupMaster") + .HasColumnType("int(11)") + .HasComment("群主"); + + b.Property("LastMessage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastSenderName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateTime") + .HasColumnType("datetime(6)"); + + b.Property("MaxSequenceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("群聊名称"); + + b.Property("Status") + .HasColumnType("tinyint(4)") + .HasComment("群聊状态\r\n(1:正常,2:封禁)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupMaster" }, "GroupMaster"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID1"); + + b.ToTable("groups", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("InviteUser") + .HasColumnType("int(11)") + .HasComment("邀请用户"); + + b.Property("InvitedUser") + .HasColumnType("int(11)") + .HasComment("被邀请用户"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "GroupId"); + + b.HasIndex(new[] { "InviteUser" }, "InviteUser"); + + b.HasIndex(new[] { "InvitedUser" }, "InvitedUser"); + + b.ToTable("group_invite", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("加入群聊时间"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号"); + + b.Property("Role") + .HasColumnType("tinyint(4)") + .HasComment("成员角色(0:普通成员,1:管理员,2:群主)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("用户编号"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "GroupId" }, "Groupld"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID2"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld1"); + + b.ToTable("group_member", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("入群附言"); + + b.Property("GroupId") + .HasColumnType("int(11)") + .HasComment("群聊编号\r\n"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("申请人 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("UserId"); + + b.HasIndex(new[] { "GroupId" }, "GroupId") + .HasDatabaseName("GroupId1"); + + b.ToTable("group_request", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Dtype") + .HasColumnType("tinyint(4)") + .HasColumnName("DType") + .HasComment("设备类型(通Devices/DType) "); + + b.Property("Logined") + .HasColumnType("datetime") + .HasComment("登录时间 "); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) "); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("登录用户 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld2"); + + b.ToTable("login_log", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatType") + .HasColumnType("tinyint(4)") + .HasComment("聊天类型\r\n(0:私聊,1:群聊)"); + + b.Property("ClientMsgId") + .HasColumnType("char(36)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("消息内容 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("发送时间 "); + + b.Property("MsgType") + .HasColumnType("tinyint(4)") + .HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)"); + + b.Property("Recipient") + .HasColumnType("int(11)") + .HasComment("接收者(私聊为用户ID,群聊为群聊ID) "); + + b.Property("Sender") + .HasColumnType("int(11)") + .HasComment("发送者 "); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.Property("State") + .HasColumnType("tinyint(4)") + .HasComment("消息状态(0:已发送,1:已撤回) "); + + b.Property("StreamKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("消息推送唯一标识符"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex("SequenceId", "StreamKey") + .IsUnique(); + + b.HasIndex(new[] { "Sender" }, "Sender"); + + b.ToTable("messages", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("通知内容"); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间"); + + b.Property("Ntype") + .HasColumnType("tinyint(4)") + .HasColumnName("NType") + .HasComment("通知类型(0:文本)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasComment("通知标题"); + + b.Property("UserId") + .HasColumnType("int(11)") + .HasComment("接收人(为空为全体通知)"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "UserId" }, "Userld") + .HasDatabaseName("Userld3"); + + b.ToTable("notifications", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("int(11)") + .HasComment("权限编码 "); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("权限名称 "); + + b.Property("Ptype") + .HasColumnType("int(11)") + .HasColumnName("PType") + .HasComment("权限类型(0:增,1:删,2:改,3:查) "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("permissions", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.Property("Id") + .HasColumnType("int(11)") + .HasColumnName("ID"); + + b.Property("PermissionId") + .HasColumnType("int(11)") + .HasComment("权限 "); + + b.Property("RoleId") + .HasColumnType("int(11)") + .HasComment("角色 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "PermissionId" }, "Permissionld"); + + b.HasIndex(new[] { "RoleId" }, "Roleld"); + + b.ToTable("permissionarole", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime") + .HasComment("创建时间 "); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述 "); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("角色名称 "); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.ToTable("roles", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int(11)") + .HasColumnName("ID"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasComment("用户头像链接"); + + b.Property("Created") + .ValueGeneratedOnAdd() + .HasColumnType("datetime") + .HasDefaultValueSql("'1970-01-01 00:00:00'") + .HasComment("创建时间"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(4)") + .HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除"); + + b.Property("NickName") + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("用户昵称"); + + b.Property("OnlineStatus") + .HasColumnType("tinyint(4)") + .HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("密码"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(4)") + .HasDefaultValueSql("'1'") + .HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)"); + + b.Property("Updated") + .HasColumnType("datetime") + .HasComment("修改时间"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("唯一用户名"); + + b.HasKey("Id") + .HasName("PRIMARY"); + + b.HasIndex(new[] { "Id" }, "ID") + .HasDatabaseName("ID3"); + + b.HasIndex(new[] { "Username" }, "Username") + .IsUnique(); + + b.ToTable("users", (string)null); + + MySqlEntityTypeBuilderExtensions.HasCharSet(b, "utf8mb4"); + MySqlEntityTypeBuilderExtensions.UseCollation(b, "utf8mb4_general_ci"); + }); + + modelBuilder.Entity("IM_API.Models.Admin", b => + { + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Admins") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("admins_ibfk_1"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Conversation", b => + { + b.HasOne("IM_API.Models.Message", null) + .WithMany("Conversations") + .HasForeignKey("MessageId"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("Conversations") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("conversations_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Device", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Devices") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("devices_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.File", b => + { + b.HasOne("IM_API.Models.Message", "Message") + .WithMany("Files") + .HasForeignKey("MessageId") + .IsRequired() + .HasConstraintName("files_ibfk_1"); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("IM_API.Models.Friend", b => + { + b.HasOne("IM_API.Models.User", "FriendNavigation") + .WithMany("FriendFriendNavigations") + .HasForeignKey("FriendId") + .IsRequired() + .HasConstraintName("用户2id"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("FriendUsers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("用户id"); + + b.Navigation("FriendNavigation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.FriendRequest", b => + { + b.HasOne("IM_API.Models.User", "RequestUserNavigation") + .WithMany("FriendRequestRequestUserNavigations") + .HasForeignKey("RequestUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "ResponseUserNavigation") + .WithMany("FriendRequestResponseUserNavigations") + .HasForeignKey("ResponseUser") + .IsRequired() + .HasConstraintName("friend_request_ibfk_2"); + + b.Navigation("RequestUserNavigation"); + + b.Navigation("ResponseUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.HasOne("IM_API.Models.User", "GroupMasterNavigation") + .WithMany("Groups") + .HasForeignKey("GroupMaster") + .IsRequired() + .HasConstraintName("groups_ibfk_1"); + + b.Navigation("GroupMasterNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupInvite", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupInvites") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_invite_ibfk_2"); + + b.HasOne("IM_API.Models.User", "InviteUserNavigation") + .WithMany("GroupInviteInviteUserNavigations") + .HasForeignKey("InviteUser") + .HasConstraintName("group_invite_ibfk_1"); + + b.HasOne("IM_API.Models.User", "InvitedUserNavigation") + .WithMany("GroupInviteInvitedUserNavigations") + .HasForeignKey("InvitedUser") + .HasConstraintName("group_invite_ibfk_3"); + + b.Navigation("Group"); + + b.Navigation("InviteUserNavigation"); + + b.Navigation("InvitedUserNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.GroupMember", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupMembers") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_member_ibfk_2"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupMembers") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_member_ibfk_1"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.GroupRequest", b => + { + b.HasOne("IM_API.Models.Group", "Group") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_1"); + + b.HasOne("IM_API.Models.User", "User") + .WithMany("GroupRequests") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.LoginLog", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("LoginLogs") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("login_log_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.HasOne("IM_API.Models.User", "SenderNavigation") + .WithMany("Messages") + .HasForeignKey("Sender") + .IsRequired() + .HasConstraintName("messages_ibfk_1"); + + b.Navigation("SenderNavigation"); + }); + + modelBuilder.Entity("IM_API.Models.Notification", b => + { + b.HasOne("IM_API.Models.User", "User") + .WithMany("Notifications") + .HasForeignKey("UserId") + .IsRequired() + .HasConstraintName("notifications_ibfk_1"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IM_API.Models.Permissionarole", b => + { + b.HasOne("IM_API.Models.Permission", "Permission") + .WithMany("Permissionaroles") + .HasForeignKey("PermissionId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_2"); + + b.HasOne("IM_API.Models.Role", "Role") + .WithMany("Permissionaroles") + .HasForeignKey("RoleId") + .IsRequired() + .HasConstraintName("permissionarole_ibfk_1"); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IM_API.Models.Group", b => + { + b.Navigation("GroupInvites"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + }); + + modelBuilder.Entity("IM_API.Models.Message", b => + { + b.Navigation("Conversations"); + + b.Navigation("Files"); + }); + + modelBuilder.Entity("IM_API.Models.Permission", b => + { + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.Role", b => + { + b.Navigation("Admins"); + + b.Navigation("Permissionaroles"); + }); + + modelBuilder.Entity("IM_API.Models.User", b => + { + b.Navigation("Conversations"); + + b.Navigation("Devices"); + + b.Navigation("FriendFriendNavigations"); + + b.Navigation("FriendRequestRequestUserNavigations"); + + b.Navigation("FriendRequestResponseUserNavigations"); + + b.Navigation("FriendUsers"); + + b.Navigation("GroupInviteInviteUserNavigations"); + + b.Navigation("GroupInviteInvitedUserNavigations"); + + b.Navigation("GroupMembers"); + + b.Navigation("GroupRequests"); + + b.Navigation("Groups"); + + b.Navigation("LoginLogs"); + + b.Navigation("Messages"); + + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.cs b/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.cs new file mode 100644 index 0000000..018a446 --- /dev/null +++ b/backend/IM_API/Migrations/20260211065853_update-group-groupid-userid.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updategroupgroupiduserid : Migration + { + /// + 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"); + } + + /// + 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"); + } + } +} diff --git a/backend/IM_API/Migrations/ImContextModelSnapshot.cs b/backend/IM_API/Migrations/ImContextModelSnapshot.cs index 2b600b9..8159df8 100644 --- a/backend/IM_API/Migrations/ImContextModelSnapshot.cs +++ b/backend/IM_API/Migrations/ImContextModelSnapshot.cs @@ -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 => diff --git a/backend/IM_API/Models/Grouprequest.cs b/backend/IM_API/Models/Grouprequest.cs index 2b774b1..a372610 100644 --- a/backend/IM_API/Models/Grouprequest.cs +++ b/backend/IM_API/Models/Grouprequest.cs @@ -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!; } diff --git a/backend/IM_API/Models/ImContext.cs b/backend/IM_API/Models/ImContext.cs index 8304f50..e09154c 100644 --- a/backend/IM_API/Models/ImContext.cs +++ b/backend/IM_API/Models/ImContext.cs @@ -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"); }); diff --git a/backend/IM_API/Services/GroupService.cs b/backend/IM_API/Services/GroupService.cs index 1822bc7..c78ebb9 100644 --- a/backend/IM_API/Services/GroupService.cs +++ b/backend/IM_API/Services/GroupService.cs @@ -18,31 +18,25 @@ namespace IM_API.Services private readonly IMapper _mapper; private readonly ILogger _logger; private readonly IPublishEndpoint _endPoint; - public GroupService(ImContext context, IMapper mapper, ILogger logger, IPublishEndpoint publishEndpoint) + private readonly IUserService _userService; + public GroupService(ImContext context, IMapper mapper, ILogger logger, + IPublishEndpoint publishEndpoint, IUserService userService) { _context = context; _mapper = mapper; _logger = logger; _endPoint = publishEndpoint; + _userService = userService; } - private async Task> GetGroupInvites(int userId, int groupId, List ids) + private async Task> validFriendshipAsync (int userId, List 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 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(); 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(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; + } } } diff --git a/backend/IM_API/Tools/RedisKeys.cs b/backend/IM_API/Tools/RedisKeys.cs index f1717e9..d7949be 100644 --- a/backend/IM_API/Tools/RedisKeys.cs +++ b/backend/IM_API/Tools/RedisKeys.cs @@ -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}"; } } diff --git a/backend/IM_API/VOs/Group/GroupInviteActionUpdateVo.cs b/backend/IM_API/VOs/Group/GroupInviteActionUpdateVo.cs new file mode 100644 index 0000000..23fdd23 --- /dev/null +++ b/backend/IM_API/VOs/Group/GroupInviteActionUpdateVo.cs @@ -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; } + } +} diff --git a/backend/IM_API/VOs/Group/GroupInviteVo.cs b/backend/IM_API/VOs/Group/GroupInviteVo.cs new file mode 100644 index 0000000..f56d3cd --- /dev/null +++ b/backend/IM_API/VOs/Group/GroupInviteVo.cs @@ -0,0 +1,9 @@ +namespace IM_API.VOs.Group +{ + public class GroupInviteVo + { + public int GroupId { get; set; } + public int UserId { get; set; } + + } +} diff --git a/backend/IM_API/VOs/Group/GroupJoinVo.cs b/backend/IM_API/VOs/Group/GroupJoinVo.cs new file mode 100644 index 0000000..c6b85a4 --- /dev/null +++ b/backend/IM_API/VOs/Group/GroupJoinVo.cs @@ -0,0 +1,8 @@ +namespace IM_API.VOs.Group +{ + public class GroupJoinVo + { + public int UserId { get; set; } + public int GroupId { get; set; } + } +} diff --git a/backend/IM_API/VOs/Group/GroupRequestUpdateVo.cs b/backend/IM_API/VOs/Group/GroupRequestUpdateVo.cs new file mode 100644 index 0000000..337f6e5 --- /dev/null +++ b/backend/IM_API/VOs/Group/GroupRequestUpdateVo.cs @@ -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; } + } +} diff --git a/frontend/web/src/App.vue b/frontend/web/src/App.vue index 79bb67b..122b383 100644 --- a/frontend/web/src/App.vue +++ b/frontend/web/src/App.vue @@ -20,8 +20,7 @@ onMounted(async () => { } }) - diff --git a/frontend/web/src/components/ContextMenu.vue b/frontend/web/src/components/ContextMenu.vue new file mode 100644 index 0000000..f109a43 --- /dev/null +++ b/frontend/web/src/components/ContextMenu.vue @@ -0,0 +1,98 @@ + + + + + \ No newline at end of file diff --git a/frontend/web/src/components/groups/CreateGroup.vue b/frontend/web/src/components/groups/CreateGroup.vue index 6a4dd93..3e2a68c 100644 --- a/frontend/web/src/components/groups/CreateGroup.vue +++ b/frontend/web/src/components/groups/CreateGroup.vue @@ -1,14 +1,16 @@