前端:

1、会话列表、消息界面展示与后端打通
后端:
1、修复会话和消息服务现存问题
2、会话对象不再返回Message对象,而是使用MessageBaseDto替代
3、修改查询会话列表和会话信息的逻辑
4、新增消息列表查询
文档:
后端代码规范文档新增从数据库同步到模型的命令
This commit is contained in:
2026-01-18 22:32:55 +08:00
parent d855c8f8fb
commit e7dbb651a2
32 changed files with 294 additions and 837 deletions
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+5274f0d22d4fe646d03a3bc0ea6621d299074816")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d855c8f8fb8edc48b6c55f08ee2ebf74415cc5ea")]
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
@@ -1 +1 @@
7390b702e3c578dad3a8fa4fa4cc93b25ccd34a9b353beca60372a7182717d73
0e2c3a5367325662a3e7db912bec1cd772d661d101def6cb95abbceb77be09ff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+23
View File
@@ -54,6 +54,7 @@ namespace IM_API.Configs
.ForMember(dest => dest.ReceiverId, opt => opt.MapFrom(src => src.Recipient))
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
.ForMember(dest => dest.TimeStamp, opt => opt.MapFrom(src => src.Created))
.ForMember(dest => dest.GroupMemberId , opt => opt.MapFrom(src => src.GroupMemberId))
;
CreateMap<MessageBaseDto, Message>()
.ForMember(dest => dest.Sender, opt => opt.MapFrom(src => src.SenderId))
@@ -64,6 +65,7 @@ namespace IM_API.Configs
.ForMember(dest => dest.Recipient, opt => opt.MapFrom(src => src.ReceiverId))
.ForMember(dest => dest.StreamKey, opt => opt.Ignore() )
.ForMember(dest => dest.StateEnum, opt => opt.MapFrom(src => MessageState.Sent))
.ForMember(dest => dest.GroupMemberId, opt => opt.MapFrom(src => src.GroupMemberId))
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
.ForMember(dest => dest.MsgType, opt => opt.Ignore())
;
@@ -102,6 +104,27 @@ namespace IM_API.Configs
.ForMember(dest => dest.LastMessageTime, opt => opt.MapFrom(src => DateTime.Now))
;
//创建会话对象
CreateMap<Conversation, ConversationDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.LastMessage, opt => opt.MapFrom(src => src.LastMessage))
.ForMember(dest => dest.LastReadMessage, opt => opt.MapFrom(src => src.LastReadMessage))
.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.LastReadMessageId))
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatType))
.ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.LastMessageTime))
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.TargetId))
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => src.UnreadCount))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.UserId));
CreateMap<Friend, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.RemarkName));
CreateMap<Group, ConversationDto>()
.ForMember(dest => dest.TargetAvatar, opt => opt.MapFrom(src => src.Avatar))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
}
@@ -28,6 +28,14 @@ namespace IM_API.Controllers
var res = new BaseResponse<List<ConversationDto>>(list);
return Ok(res);
}
[HttpGet]
public async Task<IActionResult> Get([FromQuery]int conversationId)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var conversation = await _conversationSerivice.GetConversationByIdAsync(int.Parse(userIdStr), conversationId);
var res = new BaseResponse<ConversationDto>(conversation);
return Ok(res);
}
[HttpPost]
public async Task<IActionResult> Clear()
{
@@ -3,6 +3,7 @@ using IM_API.Interface.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace IM_API.Controllers
@@ -33,5 +34,13 @@ namespace IM_API.Controllers
await _messageService.SendGroupMessageAsync(int.Parse(userIdstr), dto.ReceiverId, dto);
return Ok(new BaseResponse<object?>());
}
[HttpGet]
public async Task<IActionResult> GetMessageList([Required]int conversationId, int? msgId, int? pageSize)
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var msgList = await _messageService.GetMessagesAsync(int.Parse(userIdStr), conversationId, msgId, pageSize, false);
var res = new BaseResponse<List<MessageBaseDto>>(msgList);
return Ok(res);
}
}
}
+1 -1
View File
@@ -46,6 +46,6 @@ namespace IM_API.Dtos
/// 对方头像
/// </summary>
public string? TargetAvatar { get; set; }
public virtual Message? LastReadMessage { get; set; }
public MessageBaseDto? LastReadMessage { get; set; }
}
}
+13 -2
View File
@@ -1,5 +1,16 @@
namespace IM_API.Dtos
{
public record MessageBaseDto(
string Type,string ChatType, string? MsgId,int SenderId,int ReceiverId,string Content,DateTime TimeStamp);
public record MessageBaseDto
{
// 使用 { get; init; } 确保对象创建后不可修改,且支持无参构造
public string Type { get; init; } = default!;
public string ChatType { get; init; } = default!;
public string? MsgId { get; init; }
public int SenderId { get; init; }
public int ReceiverId { get; init; }
public int? GroupMemberId { get; init; }
public string Content { get; init; } = default!;
public DateTime TimeStamp { get; init; }
public MessageBaseDto() { }
}
}
@@ -29,5 +29,11 @@ namespace IM_API.Interface.Services
/// <param name="userId"></param>
/// <returns></returns>
Task<List<string>> GetUserAllStreamKeyAsync(int userId);
/// <summary>
/// 获取单个conversation信息
/// </summary>
/// <param name="conversationId"></param>
/// <returns></returns>
Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId);
}
}
@@ -21,24 +21,14 @@ namespace IM_API.Interface.Services
/// <returns></returns>
Task<bool> SendGroupMessageAsync(int senderId,int groupId,MessageBaseDto dto);
/// <summary>
/// 获取私聊消息列表
/// 获取消息列表
/// </summary>
/// <param name="userAId"></param>
/// <param name="userBId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="conversationId">会话id(用于获取指定用户间聊天消息)</param>
/// <param name="msgId">消息id</param>
/// <param name="pageSize">获取消息数量</param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId,int userBId,int page,int pageSize,bool desc);
/// <summary>
/// 获取群聊消息列表
/// </summary>
/// <param name="groupId"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="desc"></param>
/// <returns></returns>
Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc);
Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId,int? msgId,int? pageSize,bool desc);
/// <summary>
/// 获取未读消息数
/// </summary>
+3 -9
View File
@@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
namespace IM_API.Models;
public partial class ImContext : DbContext
{
public ImContext()
{
}
public ImContext(DbContextOptions<ImContext> options)
: base(options)
{
@@ -50,10 +45,6 @@ public partial class ImContext : DbContext
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseMySql("server=frp-era.com;port=26582;database=IM;user=product;password=12345678", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.44-mysql"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
@@ -547,6 +538,9 @@ public partial class ImContext : DbContext
entity.Property(e => e.Created)
.HasComment("发送时间 ")
.HasColumnType("datetime");
entity.Property(e => e.GroupMemberId)
.HasComment("若为群消息则表示具体的成员id")
.HasColumnType("int(11)");
entity.Property(e => e.MsgType)
.HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)")
.HasColumnType("tinyint(4)");
-732
View File
@@ -1,732 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
namespace IM_API.Models;
public partial class ImDbContext : DbContext
{
public ImDbContext()
{
}
public ImDbContext(DbContextOptions<ImDbContext> options)
: base(options)
{
}
public virtual DbSet<Admin> Admins { get; set; }
public virtual DbSet<Conversation> Conversations { get; set; }
public virtual DbSet<Device> Devices { get; set; }
public virtual DbSet<File> Files { get; set; }
public virtual DbSet<Friend> Friends { get; set; }
public virtual DbSet<FriendRequest> FriendRequests { get; set; }
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<GroupInvite> GroupInvites { get; set; }
public virtual DbSet<GroupMember> GroupMembers { get; set; }
public virtual DbSet<GroupRequest> GroupRequests { get; set; }
public virtual DbSet<LoginLog> LoginLogs { get; set; }
public virtual DbSet<Message> Messages { get; set; }
public virtual DbSet<Notification> Notifications { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Permissionarole> Permissionaroles { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseMySql("server=frp-era.com;port=26582;database=IM;user=product;password=12345678", Microsoft.EntityFrameworkCore.ServerVersion.Parse("5.7.44-mysql"));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.UseCollation("latin1_swedish_ci")
.HasCharSet("latin1");
modelBuilder.Entity<Admin>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("admins")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.RoleId, "RoleId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Password)
.HasMaxLength(50)
.HasComment("密码");
entity.Property(e => e.RoleId)
.HasComment("角色")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("状态(0:正常,2:封禁) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Updated)
.HasComment("更新时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Username)
.HasMaxLength(50)
.HasComment("用户名");
entity.HasOne(d => d.Role).WithMany(p => p.Admins)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("admins_ibfk_1");
});
modelBuilder.Entity<Conversation>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("conversations")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userid");
entity.HasIndex(e => e.LastReadMessageId, "lastReadMessageId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.ChatType).HasColumnType("int(11)");
entity.Property(e => e.LastReadMessageId)
.HasComment("最后一条已读消息ID ")
.HasColumnType("int(11)")
.HasColumnName("lastMessageId");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.Property(e => e.TargetId)
.HasComment("对方ID(群聊为群聊ID,单聊为单聊ID) ")
.HasColumnType("int(11)");
entity.Property(e => e.UnreadCount)
.HasComment("未读消息数 ")
.HasColumnType("int(11)");
entity.Property(e => e.UserId)
.HasComment("用户")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("conversations_ibfk_1");
});
modelBuilder.Entity<Device>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("devices")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userid");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Dtype)
.HasComment("设备类型(\r\n0:Android,1:Ios,2:PC,3Pad,4:未知)")
.HasColumnType("tinyint(4)")
.HasColumnName("DType");
entity.Property(e => e.LastLogin)
.HasComment("最后一次登录 ")
.HasColumnType("datetime");
entity.Property(e => e.UserId)
.HasComment("设备所属用户 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Devices)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("devices_ibfk_1");
});
modelBuilder.Entity<File>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("files")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.MessageId, "Messageld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.MessageId)
.HasComment("关联消息ID ")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("文件名 ");
entity.Property(e => e.Size)
.HasComment("文件大小(单位:KB ")
.HasColumnType("int(11)");
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasComment("文件类型 ");
entity.Property(e => e.Url)
.HasMaxLength(100)
.HasComment("文件储存URL ")
.HasColumnName("URL");
entity.HasOne(d => d.Message).WithMany(p => p.Files)
.HasForeignKey(d => d.MessageId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("files_ibfk_1");
});
modelBuilder.Entity<Friend>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("friends")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => new { e.UserId, e.FriendId }, "Userld");
entity.HasIndex(e => e.FriendId, "用户2id");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("好友头像");
entity.Property(e => e.Created)
.HasComment("好友关系创建时间")
.HasColumnType("datetime");
entity.Property(e => e.FriendId)
.HasComment("用户2ID")
.HasColumnType("int(11)");
entity.Property(e => e.RemarkName)
.HasMaxLength(20)
.HasComment("好友备注名");
entity.Property(e => e.Status)
.HasComment("当前好友关系状态\r\n(0:待通过,1:已添加,2:已拒绝,3:已拉黑)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("用户ID")
.HasColumnType("int(11)");
entity.HasOne(d => d.FriendNavigation).WithMany(p => p.FriendFriendNavigations)
.HasForeignKey(d => d.FriendId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("用户2id");
entity.HasOne(d => d.User).WithMany(p => p.FriendUsers)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("用户id");
});
modelBuilder.Entity<FriendRequest>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("friend_request")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.RequestUser, "RequestUser");
entity.HasIndex(e => e.ResponseUser, "ResponseUser");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("申请时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("申请附言 ")
.HasColumnType("text");
entity.Property(e => e.RequestUser)
.HasComment("申请人 ")
.HasColumnType("int(11)");
entity.Property(e => e.ResponseUser)
.HasComment("被申请人 ")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("申请状态(0:待通过,1:拒绝,2:同意,3:拉黑) ")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.RequestUserNavigation).WithMany(p => p.FriendRequestRequestUserNavigations)
.HasForeignKey(d => d.RequestUser)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("friend_request_ibfk_1");
entity.HasOne(d => d.ResponseUserNavigation).WithMany(p => p.FriendRequestResponseUserNavigations)
.HasForeignKey(d => d.ResponseUser)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("friend_request_ibfk_2");
});
modelBuilder.Entity<Group>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("groups")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupMaster, "GroupMaster");
entity.HasIndex(e => e.Id, "ID");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.AllMembersBanned)
.HasComment("全员禁言(0允许发言,2全员禁言)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Announcement)
.HasComment("群公告")
.HasColumnType("text");
entity.Property(e => e.Auhority)
.HasComment("群权限\r\n(0:需管理员同意,1:任意人可加群,2:不允许任何人加入)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Created)
.HasComment("群聊创建时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupMaster)
.HasComment("群主")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasMaxLength(20)
.HasComment("群聊名称");
entity.Property(e => e.Status)
.HasComment("群聊状态\r\n(1:正常,2:封禁)")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.GroupMasterNavigation).WithMany(p => p.Groups)
.HasForeignKey(d => d.GroupMaster)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("groups_ibfk_1");
});
modelBuilder.Entity<GroupInvite>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_invite")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "GroupId");
entity.HasIndex(e => e.InviteUser, "InviteUser");
entity.HasIndex(e => e.InvitedUser, "InvitedUser");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupId)
.HasComment("群聊编号")
.HasColumnType("int(11)");
entity.Property(e => e.InviteUser)
.HasComment("邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.InvitedUser)
.HasComment("被邀请用户")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("当前状态(0:待被邀请人同意\r\n1:被邀请人已同意)")
.HasColumnType("tinyint(4)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupInvites)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_invite_ibfk_2");
entity.HasOne(d => d.InviteUserNavigation).WithMany(p => p.GroupInviteInviteUserNavigations)
.HasForeignKey(d => d.InviteUser)
.HasConstraintName("group_invite_ibfk_1");
entity.HasOne(d => d.InvitedUserNavigation).WithMany(p => p.GroupInviteInvitedUserNavigations)
.HasForeignKey(d => d.InvitedUser)
.HasConstraintName("group_invite_ibfk_3");
});
modelBuilder.Entity<GroupMember>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_member")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "Groupld");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasDefaultValueSql("'1970-01-01 00:00:00'")
.HasComment("加入群聊时间")
.HasColumnType("datetime");
entity.Property(e => e.GroupId)
.HasComment("群聊编号")
.HasColumnType("int(11)");
entity.Property(e => e.Role)
.HasComment("成员角色(0:普通成员,1:管理员,2:群主)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("用户编号")
.HasColumnType("int(11)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupMemberGroups)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_member_ibfk_2");
entity.HasOne(d => d.User).WithMany(p => p.GroupMemberUsers)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_member_ibfk_1");
});
modelBuilder.Entity<GroupRequest>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("group_request")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.GroupId, "GroupId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("入群附言")
.HasColumnType("text");
entity.Property(e => e.GroupId)
.HasComment("群聊编号\r\n")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("申请状态(0:待管理员同意,1:已拒绝,2:已同意)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("申请人 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.Group).WithMany(p => p.GroupRequests)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_request_ibfk_1");
entity.HasOne(d => d.GroupNavigation).WithMany(p => p.GroupRequests)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("group_request_ibfk_2");
});
modelBuilder.Entity<LoginLog>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("login_log")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Dtype)
.HasComment("设备类型(通Devices/DType ")
.HasColumnType("tinyint(4)")
.HasColumnName("DType");
entity.Property(e => e.Logined)
.HasComment("登录时间 ")
.HasColumnType("datetime");
entity.Property(e => e.State)
.HasComment("登录状态(0:登陆成功,1:未验证,2:已被拒绝) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.UserId)
.HasComment("登录用户 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.LoginLogs)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("login_log_ibfk_1");
});
modelBuilder.Entity<Message>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("messages")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Sender, "Sender");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.ChatType)
.HasComment("聊天类型\r\n0:私聊,1:群聊)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Content)
.HasComment("消息内容 ")
.HasColumnType("text");
entity.Property(e => e.Created)
.HasComment("发送时间 ")
.HasColumnType("datetime");
entity.Property(e => e.MsgType)
.HasComment("消息类型\r\n(0:文本,1:图片,2:语音,3:视频,4:文件,5:语音聊天,6:视频聊天)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Recipient)
.HasComment("接收者(私聊为用户ID,群聊为群聊ID) ")
.HasColumnType("int(11)");
entity.Property(e => e.Sender)
.HasComment("发送者 ")
.HasColumnType("int(11)");
entity.Property(e => e.State)
.HasComment("消息状态(0:已发送,1:已撤回) ")
.HasColumnType("tinyint(4)");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.HasOne(d => d.SenderNavigation).WithMany(p => p.Messages)
.HasForeignKey(d => d.Sender)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("messages_ibfk_1");
});
modelBuilder.Entity<Notification>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("notifications")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.UserId, "Userld");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Content)
.HasComment("通知内容")
.HasColumnType("text");
entity.Property(e => e.Created)
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.Ntype)
.HasComment("通知类型(0:文本)")
.HasColumnType("tinyint(4)")
.HasColumnName("NType");
entity.Property(e => e.Title)
.HasMaxLength(40)
.HasComment("通知标题");
entity.Property(e => e.UserId)
.HasComment("接收人(为空为全体通知)")
.HasColumnType("int(11)");
entity.HasOne(d => d.User).WithMany(p => p.Notifications)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("notifications_ibfk_1");
});
modelBuilder.Entity<Permission>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("permissions")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Code)
.HasComment("权限编码 ")
.HasColumnType("int(11)");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasComment("权限名称 ");
entity.Property(e => e.Ptype)
.HasComment("权限类型(0:增,1:删,2:改,3:查) ")
.HasColumnType("int(11)")
.HasColumnName("PType");
});
modelBuilder.Entity<Permissionarole>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("permissionarole")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.PermissionId, "Permissionld");
entity.HasIndex(e => e.RoleId, "Roleld");
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.PermissionId)
.HasComment("权限 ")
.HasColumnType("int(11)");
entity.Property(e => e.RoleId)
.HasComment("角色 ")
.HasColumnType("int(11)");
entity.HasOne(d => d.Permission).WithMany(p => p.Permissionaroles)
.HasForeignKey(d => d.PermissionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("permissionarole_ibfk_2");
entity.HasOne(d => d.Role).WithMany(p => p.Permissionaroles)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("permissionarole_ibfk_1");
});
modelBuilder.Entity<Role>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("roles")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Created)
.HasComment("创建时间 ")
.HasColumnType("datetime");
entity.Property(e => e.Description)
.HasComment("角色描述 ")
.HasColumnType("text");
entity.Property(e => e.Name)
.HasMaxLength(20)
.HasComment("角色名称 ");
});
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id).HasName("PRIMARY");
entity
.ToTable("users")
.HasCharSet("utf8mb4")
.UseCollation("utf8mb4_general_ci");
entity.HasIndex(e => e.Id, "ID");
entity.HasIndex(e => e.Username, "Username").IsUnique();
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("用户头像链接");
entity.Property(e => e.Created)
.HasDefaultValueSql("'1970-01-01 00:00:00'")
.HasComment("创建时间")
.HasColumnType("datetime");
entity.Property(e => e.IsDeleted)
.HasComment("软删除标识\r\n0:账号正常\r\n1:账号已删除")
.HasColumnType("tinyint(4)");
entity.Property(e => e.NickName)
.HasMaxLength(50)
.HasComment("用户昵称");
entity.Property(e => e.OnlineStatus)
.HasComment("用户在线状态\r\n0(默认):不在线\r\n1:在线")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Password)
.HasMaxLength(50)
.HasComment("密码");
entity.Property(e => e.Status)
.HasDefaultValueSql("'1'")
.HasComment("账户状态\r\n(0:未激活,1:正常,2:封禁)")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Updated)
.HasComment("修改时间")
.HasColumnType("datetime");
entity.Property(e => e.Username)
.HasMaxLength(50)
.HasComment("唯一用户名");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
+5
View File
@@ -49,6 +49,11 @@ public partial class Message
/// </summary>
public string StreamKey { get; set; } = null!;
/// <summary>
/// 若为群消息则表示具体的成员id
/// </summary>
public int? GroupMemberId { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
public virtual ICollection<File> Files { get; set; } = new List<File>();
+71 -41
View File
@@ -1,4 +1,5 @@
using IM_API.Dtos;
using AutoMapper;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
@@ -10,9 +11,11 @@ namespace IM_API.Services
public class ConversationService : IConversationService
{
private readonly ImContext _context;
public ConversationService(ImContext context)
private readonly IMapper _mapper;
public ConversationService(ImContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
#region
public async Task<bool> ClearConversationsAsync(int userId)
@@ -26,47 +29,41 @@ namespace IM_API.Services
#region
public async Task<List<ConversationDto>> GetConversationsAsync(int userId)
{
var privateQuery = from c in _context.Conversations
join f in _context.Friends on new { c.UserId, c.TargetId}
equals new { UserId = f.UserId, TargetId = f.FriendId}
where c.UserId == userId && c.ChatType == (int)ChatType.PRIVATE
select new ConversationDto
{
Id = c.Id,
UserId = c.UserId,
TargetId = c.TargetId,
LastReadMessageId = c.LastReadMessageId,
LastReadMessage = c.LastReadMessage,
UnreadCount = c.UnreadCount,
ChatType = c.ChatType,
LastMessage = c.LastMessage,
TargetAvatar = f.Avatar,
TargetName = f.RemarkName,
DateTime = c.LastMessageTime
// 1. 获取私聊会话
var privateList = await (from c in _context.Conversations
join f in _context.Friends on new { c.UserId, c.TargetId }
equals new { UserId = f.UserId, TargetId = f.FriendId }
where c.UserId == userId && c.ChatType == (int)ChatType.PRIVATE
select new { c, f.Avatar, f.RemarkName })
.ToListAsync();
};
// 2. 获取群聊会话
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 == (int)ChatType.GROUP
select new { c, g.Avatar, g.Name })
.ToListAsync();
var groupQuery = from c in _context.Conversations
join g in _context.Groups on c.TargetId equals g.Id
where c.UserId == userId && c.ChatType == (int)ChatType.GROUP
select new ConversationDto
{
Id = c.Id,
UserId = c.UserId,
TargetId = c.TargetId,
LastReadMessageId = c.LastReadMessageId,
LastReadMessage = c.LastReadMessage,
UnreadCount = c.UnreadCount,
ChatType = c.ChatType,
LastMessage = c.LastMessage,
TargetAvatar = g.Avatar,
TargetName = g.Name,
DateTime = c.LastMessageTime
};
return await privateQuery
.Concat(groupQuery)
.OrderByDescending(x => x.DateTime)
.ToListAsync();
var privateDtos = privateList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.RemarkName;
return dto;
});
var groupDtos = groupList.Select(x =>
{
var dto = _mapper.Map<ConversationDto>(x.c);
dto.TargetAvatar = x.Avatar;
dto.TargetName = x.Name;
return dto;
});
// 4. 合并并排序
return privateDtos.Concat(groupDtos)
.OrderByDescending(x => x.DateTime)
.ToList();
}
#endregion
#region
@@ -81,6 +78,7 @@ namespace IM_API.Services
#endregion
#region
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
{
return await _context.Conversations.Where(x => x.UserId == userId)
@@ -88,5 +86,37 @@ namespace IM_API.Services
.Distinct()
.ToListAsync();
}
#endregion
#region
public async Task<ConversationDto> GetConversationByIdAsync(int userId, int conversationId)
{
var conversation = await _context.Conversations
.Include(x => x.LastReadMessage)
.FirstOrDefaultAsync(
x => x.UserId == userId && x.Id == conversationId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var dto = _mapper.Map<ConversationDto>(conversation);
//dto.LastReadMessage = _mapper.Map<MessageBaseDto>(conversation);
if(conversation.ChatType == (int)ChatType.PRIVATE)
{
var friendInfo = await _context.Friends.FirstOrDefaultAsync(
x => x.UserId == userId && x.FriendId == conversation.TargetId
);
if (friendInfo is null) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
_mapper.Map(friendInfo,dto);
}
if(conversation.ChatType == (int)ChatType.GROUP)
{
var groupInfo = await _context.Groups.FirstOrDefaultAsync(
x => x.Id == conversation.TargetId
);
if (groupInfo is null) throw new BaseException(CodeDefine.GROUP_NOT_FOUND);
_mapper.Map(groupInfo, dto);
}
return dto;
}
#endregion
}
}
+31 -7
View File
@@ -20,14 +20,38 @@ namespace IM_API.Services
_mapper = mapper;
}
public Task<List<MessageBaseDto>> GetGroupMessagesAsync(int groupId, int page, int pageSize, bool desc)
public async Task<List<MessageBaseDto>> GetMessagesAsync(int userId, int conversationId, int? msgId, int? pageSize, bool desc)
{
throw new NotImplementedException();
}
public Task<List<MessageBaseDto>> GetPrivateMessagesAsync(int userAId, int userBId, int page, int pageSize, bool desc)
{
throw new NotImplementedException();
//获取会话信息,用于获取双方聊天的唯一标识streamkey
Conversation? conversation = await _context.Conversations.FirstOrDefaultAsync(
x => x.Id == conversationId && x.UserId == userId
);
if (conversation is null) throw new BaseException(CodeDefine.CONVERSATION_NOT_FOUND);
var query = _context.Messages.AsQueryable();
if(msgId != null)
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id < msgId.Value
)
.OrderByDescending(x => x.Id);
}
else
{
query = query.Where(
x => x.StreamKey == conversation.StreamKey && x.Id > conversation.LastReadMessageId
);
}
if(pageSize != null)
{
query = query.Take(pageSize.Value);
}
var msgList = await query
.ToListAsync();
msgList = msgList
.OrderBy(x => x.Created)
.ThenBy(t => t.Id)
.ToList();
return _mapper.Map<List<MessageBaseDto>>(msgList);
}
public Task<int> GetUnreadCountAsync(int userId)