diff --git a/backend/IMTest/Service/UserServiceTest.cs b/backend/IMTest/Service/UserServiceTest.cs index dc42a40..8858b14 100644 --- a/backend/IMTest/Service/UserServiceTest.cs +++ b/backend/IMTest/Service/UserServiceTest.cs @@ -8,6 +8,7 @@ using IM_API.Tools; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; +using StackExchange.Redis; using System; using System.Threading.Tasks; using Xunit; @@ -38,7 +39,8 @@ public class UserServiceTests var loggerMock = new Mock>(); var mapper = CreateMapper(); var mockCache = new Mock(); - return new UserService(context, loggerMock.Object, mapper , mockCache.Object); + var res = new Mock(); + return new UserService(context, loggerMock.Object, mapper , mockCache.Object, res.Object); } // ========== GetUserInfoAsync ========== diff --git a/backend/IMTest/bin/Debug/net8.0/IMTest.dll b/backend/IMTest/bin/Debug/net8.0/IMTest.dll index 73b4391..a98768f 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 e3c14e8..1a557b1 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 7a93c5d..a277eeb 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 1700806..d52763f 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 b2e0cf5..4ff55ad 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 f0ff9d3..2e5bea1 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+139037085bdb672bb115b4b9506955f7bc733672")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+10f79fb537b71581876f17031e09898ddf99e367")] [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 d0e48f2..c244404 100644 --- a/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache +++ b/backend/IMTest/obj/Debug/net8.0/IMTest.AssemblyInfoInputs.cache @@ -1 +1 @@ -07f6bfbf17c54c7629f2ad097053140e01b65bf5b6fd786f5da1ceb943f92ba1 +6a1eca496991f1712085223e3079932051f23c0e5f7568bc971c393fde95395b 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 38e3c93..41b6cf3 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 73b4391..a98768f 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 e3c14e8..1a557b1 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 3655594..efaca8c 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 3655594..efaca8c 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/MessageCreatedHandler/ConversationEventHandler.cs b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs index 02cf786..60c18da 100644 --- a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs +++ b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/ConversationEventHandler.cs @@ -18,15 +18,18 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler private readonly IConversationService _conversationService; private readonly ILogger _logger; private readonly IUserService _userSerivce; + private readonly IGroupService _groupService; public ConversationEventHandler( IConversationService conversationService, ILogger logger, - IUserService userService + IUserService userService, + IGroupService groupService ) { _conversationService = conversationService; _logger = logger; _userSerivce = userService; + _groupService = groupService; } public async Task Consume(ConsumeContext context) @@ -35,14 +38,13 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler if (@event.ChatType == ChatType.GROUP) { var userinfo = await _userSerivce.GetUserInfoAsync(@event.MsgSenderId); - await _conversationService.UpdateConversationAfterSentAsync(new Dtos.Conversation.UpdateConversationDto + await _groupService.UpdateGroupConversationAsync(new Dtos.Group.GroupUpdateConversationDto { - LastMessage = $"{userinfo.NickName}:{@event.MessageContent}", - LastSequenceId = @event.SequenceId, - ReceiptId = @event.MsgRecipientId, - SenderId = @event.MsgSenderId, - StreamKey = @event.StreamKey, - DateTime = @event.MessageCreated + GroupId = @event.MsgRecipientId, + LastMessage = @event.MessageContent, + LastSenderName = userinfo.NickName, + LastUpdateTime = @event.MessageCreated, + MaxSequenceId = @event.SequenceId }); } else diff --git a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs index caebb7b..8166f2f 100644 --- a/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs +++ b/backend/IM_API/Application/EventHandlers/MessageCreatedHandler/SignalREventHandler.cs @@ -3,6 +3,7 @@ using IM_API.Application.Interfaces; using IM_API.Domain.Events; using IM_API.Dtos; using IM_API.Hubs; +using IM_API.Interface.Services; using IM_API.Models; using IM_API.Tools; using IM_API.VOs.Message; @@ -15,10 +16,12 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler { private readonly IHubContext _hub; private readonly IMapper _mapper; - public SignalREventHandler(IHubContext hub, IMapper mapper) + private readonly IUserService _userService; + public SignalREventHandler(IHubContext hub, IMapper mapper,IUserService userService) { _hub = hub; _mapper = mapper; + _userService = userService; } public async Task Consume(ConsumeContext context) @@ -29,6 +32,9 @@ namespace IM_API.Application.EventHandlers.MessageCreatedHandler { var entity = _mapper.Map(@event); var messageBaseVo = _mapper.Map(entity); + var senderinfo = await _userService.GetUserInfoAsync(@event.MsgSenderId); + messageBaseVo.SenderName = senderinfo.NickName; + messageBaseVo.SenderAvatar = senderinfo.Avatar ?? ""; await _hub.Clients.Group(@event.StreamKey).SendAsync("ReceiveMessage", new HubResponse("Event", messageBaseVo)); } catch (Exception ex) diff --git a/backend/IM_API/Controllers/AuthController.cs b/backend/IM_API/Controllers/AuthController.cs index 2e7cf0d..5d17d89 100644 --- a/backend/IM_API/Controllers/AuthController.cs +++ b/backend/IM_API/Controllers/AuthController.cs @@ -8,6 +8,7 @@ using IM_API.VOs.Auth; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; namespace IM_API.Controllers { @@ -39,13 +40,20 @@ namespace IM_API.Controllers [HttpPost] public async Task Login(LoginRequestDto dto) { + Stopwatch sw = Stopwatch.StartNew(); var user = await _authService.LoginAsync(dto); + _logger.LogInformation("服务耗时: {ms}ms", sw.ElapsedMilliseconds); var userInfo = _mapper.Map(user); + _logger.LogInformation("序列化耗时: {ms}ms", sw.ElapsedMilliseconds); //生成凭证 (string token,DateTime expiresAt) = _jwtService.CreateAccessTokenForUser(user.Id,user.Username,"user"); + _logger.LogInformation("Token生成耗时: {ms}ms", sw.ElapsedMilliseconds); //生成刷新凭证 + string refreshToken = await _refreshTokenService.CreateRefreshTokenAsync(user.Id); + _logger.LogInformation("RefreshToken生成耗时: {ms}ms", sw.ElapsedMilliseconds); var res = new BaseResponse(new LoginVo(userInfo,token,refreshToken, expiresAt)); + _logger.LogInformation("总耗时: {ms}ms", sw.ElapsedMilliseconds); return Ok(res); } [HttpPost] diff --git a/backend/IM_API/Dtos/Group/GroupUpdateConversationDto.cs b/backend/IM_API/Dtos/Group/GroupUpdateConversationDto.cs new file mode 100644 index 0000000..c92dd9c --- /dev/null +++ b/backend/IM_API/Dtos/Group/GroupUpdateConversationDto.cs @@ -0,0 +1,11 @@ +namespace IM_API.Dtos.Group +{ + public class GroupUpdateConversationDto + { + public int GroupId { get; set; } + public long MaxSequenceId { get; set; } + public string LastMessage { get; set; } + public string LastSenderName { get; set; } + public DateTimeOffset LastUpdateTime { get; set; } + } +} diff --git a/backend/IM_API/Interface/Services/IGroupService.cs b/backend/IM_API/Interface/Services/IGroupService.cs index 17b2d17..4cc72d0 100644 --- a/backend/IM_API/Interface/Services/IGroupService.cs +++ b/backend/IM_API/Interface/Services/IGroupService.cs @@ -43,5 +43,6 @@ namespace IM_API.Interface.Services /// /// Task> GetGroupListAsync(int userId, int page, int limit, bool desc); + Task UpdateGroupConversationAsync(GroupUpdateConversationDto dto); } } diff --git a/backend/IM_API/Migrations/20260208124430_update-group.Designer.cs b/backend/IM_API/Migrations/20260208124430_update-group.Designer.cs new file mode 100644 index 0000000..3dddeea --- /dev/null +++ b/backend/IM_API/Migrations/20260208124430_update-group.Designer.cs @@ -0,0 +1,1115 @@ +// +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("20260208124430_update-group")] + partial class updategroup + { + /// + 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(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", "GroupNavigation") + .WithMany("GroupRequests") + .HasForeignKey("GroupId") + .IsRequired() + .HasConstraintName("group_request_ibfk_2"); + + b.Navigation("Group"); + + b.Navigation("GroupNavigation"); + }); + + 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/20260208124430_update-group.cs b/backend/IM_API/Migrations/20260208124430_update-group.cs new file mode 100644 index 0000000..337e334 --- /dev/null +++ b/backend/IM_API/Migrations/20260208124430_update-group.cs @@ -0,0 +1,65 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IM_API.Migrations +{ + /// + public partial class updategroup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastMessage", + table: "groups", + type: "longtext", + nullable: false, + collation: "utf8mb4_general_ci") + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "LastSenderName", + table: "groups", + type: "longtext", + nullable: false, + collation: "utf8mb4_general_ci") + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "LastUpdateTime", + table: "groups", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "MaxSequenceId", + table: "groups", + type: "bigint", + nullable: false, + defaultValue: 0L); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastMessage", + table: "groups"); + + migrationBuilder.DropColumn( + name: "LastSenderName", + table: "groups"); + + migrationBuilder.DropColumn( + name: "LastUpdateTime", + table: "groups"); + + migrationBuilder.DropColumn( + name: "MaxSequenceId", + table: "groups"); + } + } +} diff --git a/backend/IM_API/Migrations/ImContextModelSnapshot.cs b/backend/IM_API/Migrations/ImContextModelSnapshot.cs index f0ee0e2..2b600b9 100644 --- a/backend/IM_API/Migrations/ImContextModelSnapshot.cs +++ b/backend/IM_API/Migrations/ImContextModelSnapshot.cs @@ -354,6 +354,20 @@ namespace IM_API.Migrations .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) diff --git a/backend/IM_API/Models/Group.cs b/backend/IM_API/Models/Group.cs index dc44d15..8d06fee 100644 --- a/backend/IM_API/Models/Group.cs +++ b/backend/IM_API/Models/Group.cs @@ -50,6 +50,11 @@ public partial class Group /// 群头像 /// public string Avatar { get; set; } = null!; + public long MaxSequenceId { get; set; } = 0; + public string LastMessage { get; set; } = string.Empty; + public string LastSenderName { get; set; } = string.Empty; + public DateTimeOffset LastUpdateTime { get; set; } = DateTime.UtcNow; + public virtual ICollection GroupInvites { get; set; } = new List(); diff --git a/backend/IM_API/Models/User.cs b/backend/IM_API/Models/User.cs index 3528eda..ece733f 100644 --- a/backend/IM_API/Models/User.cs +++ b/backend/IM_API/Models/User.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace IM_API.Models; @@ -58,32 +59,33 @@ public partial class User /// 用户头像链接 /// public string? Avatar { get; set; } + [JsonIgnore] public virtual ICollection Conversations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection Devices { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection FriendFriendNavigations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection FriendRequestRequestUserNavigations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection FriendRequestResponseUserNavigations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection FriendUsers { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection GroupInviteInviteUserNavigations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection GroupInviteInvitedUserNavigations { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection GroupMembers { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection GroupRequests { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection Groups { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection LoginLogs { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection Messages { get; set; } = new List(); - + [JsonIgnore] public virtual ICollection Notifications { get; set; } = new List(); } diff --git a/backend/IM_API/Services/ConversationService.cs b/backend/IM_API/Services/ConversationService.cs index 7f18d38..52c6856 100644 --- a/backend/IM_API/Services/ConversationService.cs +++ b/backend/IM_API/Services/ConversationService.cs @@ -42,7 +42,7 @@ namespace IM_API.Services var groupList = await (from c in _context.Conversations join g in _context.Groups on c.TargetId equals g.Id where c.UserId == userId && c.ChatType == ChatType.GROUP - select new { c, g.Avatar, g.Name }) + select new { c, g.Avatar, g.Name,g.MaxSequenceId,g.LastMessage }) .ToListAsync(); var privateDtos = privateList.Select(x => @@ -58,6 +58,9 @@ namespace IM_API.Services var dto = _mapper.Map(x.c); dto.TargetAvatar = x.Avatar; dto.TargetName = x.Name; + dto.UnreadCount = (int)(x.MaxSequenceId - x.c.LastReadSequenceId ?? 0); + dto.LastSequenceId = x.MaxSequenceId; + dto.LastMessage = x.LastMessage; return dto; }); diff --git a/backend/IM_API/Services/GroupService.cs b/backend/IM_API/Services/GroupService.cs index ff3396b..1822bc7 100644 --- a/backend/IM_API/Services/GroupService.cs +++ b/backend/IM_API/Services/GroupService.cs @@ -125,5 +125,16 @@ namespace IM_API.Services .ToListAsync(); return list; } + public async Task UpdateGroupConversationAsync(GroupUpdateConversationDto dto) + { + var group = await _context.Groups.FirstOrDefaultAsync(x => x.Id == dto.GroupId); + if (group is null) return; + group.LastMessage = dto.LastMessage; + group.MaxSequenceId = dto.MaxSequenceId; + group.LastSenderName = dto.LastSenderName; + group.LastUpdateTime = dto.LastUpdateTime; + _context.Groups.Update(group); + await _context.SaveChangesAsync(); + } } } diff --git a/backend/IM_API/Services/MessageService.cs b/backend/IM_API/Services/MessageService.cs index 73f53b7..07b0528 100644 --- a/backend/IM_API/Services/MessageService.cs +++ b/backend/IM_API/Services/MessageService.cs @@ -11,6 +11,7 @@ using IM_API.VOs.Message; using MassTransit; using Microsoft.EntityFrameworkCore; using StackExchange.Redis; +using System.Text.Json; using System.Text.RegularExpressions; using static MassTransit.Monitoring.Performance.BuiltInCounters; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; @@ -26,9 +27,11 @@ namespace IM_API.Services //private readonly IEventBus _eventBus; private readonly IPublishEndpoint _endpoint; private readonly ISequenceIdService _sequenceIdService; + private readonly IUserService _userService; public MessageService( ImContext context, ILogger logger, IMapper mapper, - IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService + IPublishEndpoint publishEndpoint, ISequenceIdService sequenceIdService, + IUserService userService ) { _context = context; @@ -37,6 +40,7 @@ namespace IM_API.Services //_eventBus = eventBus; _endpoint = publishEndpoint; _sequenceIdService = sequenceIdService; + _userService = userService; } public async Task> GetMessagesAsync(int userId,MessageQueryDto dto) @@ -48,6 +52,7 @@ namespace IM_API.Services if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND); var baseQuery = _context.Messages.Where(x => x.StreamKey == conversation.StreamKey); + List messages = new List(); if (dto.Direction == 0) // Before: 找比锚点小的,按倒序排 { if (dto.Cursor.HasValue) @@ -59,20 +64,41 @@ namespace IM_API.Services .Select(m => _mapper.Map(m)) .ToListAsync(); - return list.OrderBy(s => s.SequenceId).ToList(); + messages = list.OrderBy(s => s.SequenceId).ToList(); } else // After: 找比锚点大的,按正序排(用于补洞或刷新) { // 如果 Cursor 为空且是 After,逻辑上说不通,通常直接返回空或报错 if (!dto.Cursor.HasValue) return new List(); - return await baseQuery + messages = await baseQuery .Where(m => m.SequenceId > dto.Cursor.Value) .OrderBy(m => m.SequenceId) // 按时间线正序 .Take(dto.Limit) .Select(m => _mapper.Map(m)) .ToListAsync(); } + //取发送者信息,用于前端展示 + if(messages.Count > 0) + { + var ids = messages + .Select(s => s.SenderId) + .Distinct() + .ToList(); + var userinfoList = await _userService.GetUserInfoListAsync(ids); + // 转为字典,提高查询效率 + var userDict = userinfoList.ToDictionary(x => x.Id, x => x); + + foreach (var item in messages) + { + if(userDict.TryGetValue(item.SenderId, out var user)) + { + item.SenderName = user.NickName; + item.SenderAvatar = user.Avatar ?? ""; + } + } + } + return messages; } public Task GetUnreadCountAsync(int userId) diff --git a/backend/IM_API/VOs/Message/MessageBaseVo.cs b/backend/IM_API/VOs/Message/MessageBaseVo.cs index 57ccb78..a377a4f 100644 --- a/backend/IM_API/VOs/Message/MessageBaseVo.cs +++ b/backend/IM_API/VOs/Message/MessageBaseVo.cs @@ -5,5 +5,7 @@ namespace IM_API.VOs.Message public record MessageBaseVo:MessageBaseDto { public int SequenceId { get; set; } + public string SenderName { get; set; } = ""; + public string SenderAvatar { get; set; } = ""; } } diff --git a/frontend/web/src/components/user/UserHoverCard.vue b/frontend/web/src/components/user/UserHoverCard.vue new file mode 100644 index 0000000..e7ddb9a --- /dev/null +++ b/frontend/web/src/components/user/UserHoverCard.vue @@ -0,0 +1,195 @@ + + + + + \ No newline at end of file diff --git a/frontend/web/src/views/messages/MessageContent.vue b/frontend/web/src/views/messages/MessageContent.vue index 26a3cf9..a1c037b 100644 --- a/frontend/web/src/views/messages/MessageContent.vue +++ b/frontend/web/src/views/messages/MessageContent.vue @@ -10,10 +10,12 @@
+
- +
+
{{ m.senderName }}
{{ m.content }}
{{ m.content }}
@@ -66,6 +68,7 @@ import { GetLocalIso } from '@/utils/dateTool'; import HistoryLoading from '@/components/messages/HistoryLoading.vue'; import { useMessage } from '@/components/messages/useAlert'; import { SYSTEM_BASE_STATUS } from '@/constants/systemBaseStatus'; +import UserHoverCard from '@/components/user/UserHoverCard.vue'; const props = defineProps({ id:{ @@ -81,6 +84,7 @@ const conversationStore = useConversationStore(); const input = ref(''); // 输入框内容 const historyRef = ref(null); // 绑定 DOM 用于滚动 const loadingRef = ref(null) +const userHoverCardRef = ref(null); const myInfo = useAuthStore().userInfo; const conversationInfo = ref(null) @@ -122,6 +126,19 @@ const loadHistoryMsg = async () => { } }; +const handleHoverCard = (e, m) => { + const userInfo = { + name: m.senderName, + avatar: m.senderAvatar, + id: m.senderId + } + userHoverCardRef.value.show(e.target, userInfo); +} + +const closeHoverCard = () => { + userHoverCardRef.value.hide(); +} + watch( () => chatStore.messages, async (newVal) => { @@ -132,10 +149,6 @@ watch( {deep: true} ); -const getAvatar = (userId) => { - return conversationStore.conversations.find(x => x.targetId == userId).targetAvatar; -} - // 自动滚动到底部 const scrollToBottom = async () => { await nextTick(); // 等待 DOM 更新后执行 @@ -216,6 +229,7 @@ const initChat = async (newId) => { const sessionid = generateSessionId( conversationInfo.value.userId, conversationInfo.value.targetId, conversationInfo.value.chatType == MESSAGE_TYPE.GROUP) await chatStore.swtichSession(sessionid,newId); + isFinished.value = false; scrollToBottom(); } @@ -372,6 +386,12 @@ onUnmounted(() => { flex-direction: column; } +.group-sendername { + width: 55px; + font-size: 10px; + text-align: center; +} + /* 消息对齐逻辑 */ .msg { display: flex; diff --git a/frontend/web/src/views/messages/MessageList.vue b/frontend/web/src/views/messages/MessageList.vue index ec6b4d3..2afdf0c 100644 --- a/frontend/web/src/views/messages/MessageList.vue +++ b/frontend/web/src/views/messages/MessageList.vue @@ -17,7 +17,7 @@
+ class="list-item" :class="{active: activeId == s.id}" @click="selectSession(s)">
{{ s.unreadCount ?? 0 }} @@ -41,7 +41,7 @@