前端:

1、完善创建群聊逻辑
后端:
1、完善群聊相关接口
This commit is contained in:
2026-02-11 22:44:28 +08:00
parent dc6ecf224d
commit 77db20dc38
55 changed files with 1986 additions and 95 deletions
+7 -2
View File
@@ -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.
@@ -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);
}
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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));
}
}
}
@@ -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
});
}
}
}
}
@@ -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
});
}
}
}
}
@@ -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);
}
}
}
+16
View File
@@ -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 =>
+29 -1
View File
@@ -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; }
}
}
+18 -5
View File
@@ -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)
-4
View File
@@ -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);
}
}
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 =>
+1 -1
View File
@@ -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!;
}
+2 -2
View File
@@ -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");
});
+141 -30
View File
@@ -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;
}
}
}
+5 -16
View File
@@ -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; }
}
}
+8
View File
@@ -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; }
}
}