add(conversation):完善私聊好友会话服务

This commit is contained in:
西街长安 2025-12-30 19:19:08 +08:00
commit 41963dde70
35 changed files with 456 additions and 54 deletions

View File

@ -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+f42e990c45b8536af0a4b37e4e12a46c917ca02c")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5274f0d22d4fe646d03a3bc0ea6621d299074816")]
[assembly: System.Reflection.AssemblyProductAttribute("IMTest")]
[assembly: System.Reflection.AssemblyTitleAttribute("IMTest")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
0157672e490e31c525582995ff9972f067909b74fb5aeca923e211efe4aa3af8
7390b702e3c578dad3a8fa4fa4cc93b25ccd34a9b353beca60372a7182717d73

View File

@ -0,0 +1,88 @@
using AutoMapper;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Exceptions;
using IM_API.Interface.Services;
using IM_API.Models;
using IM_API.Services;
using IM_API.Tools;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace IM_API.Application.EventHandlers
{
public class ConversationEventHandler : IEventHandler<MessageCreatedEvent>
{
private readonly IConversationService _conversationService;
private readonly ILogger<ConversationEventHandler> _logger;
private readonly ImContext _context;
private readonly IMapper _mapper;
public ConversationEventHandler(
IConversationService conversationService,
ILogger<ConversationEventHandler> logger,
ImContext imContext,
IMapper mapper
)
{
_conversationService = conversationService;
_logger = logger;
_context = imContext;
_mapper = mapper;
}
/*
*
*
*
*/
public async Task Handle(MessageCreatedEvent @event)
{
//此处仅处理私聊会话创建
if (@event.ChatType == ChatType.GROUP)
{
return;
}
var conversation = await _context.Conversations.FirstOrDefaultAsync(
x => x.UserId == @event.MsgSenderId && x.TargetId == @event.MsgRecipientId
);
//如果首次发消息则创建双方会话
if (conversation is null)
{
Conversation senderCon = _mapper.Map<Conversation>(@event);
Conversation ReceptCon = _mapper.Map<Conversation>(@event);
ReceptCon.UserId = @event.MsgRecipientId;
ReceptCon.TargetId = @event.MsgSenderId;
ReceptCon.UnreadCount += 1;
ReceptCon.LastReadMessageId = null;
_context.Conversations.AddRange(senderCon,ReceptCon);
await _context.SaveChangesAsync();
}
else
{
Conversation senderCon = conversation;
Conversation? ReceptCon = await _context.Conversations.FirstOrDefaultAsync(
x => x.UserId == @event.MsgRecipientId && x.TargetId == @event.MsgSenderId);
if (ReceptCon is null)
{
_logger.LogError("ConversationEventHandlerError接收者会话对象缺失Event:{Event}", JsonConvert.SerializeObject(@event));
throw new BaseException(CodeDefine.SYSTEM_ERROR);
}
//更新发送者conversation
senderCon.UnreadCount = 0;
senderCon.LastReadMessageId = @event.MessageId;
senderCon.LastMessage = @event.MessageContent;
senderCon.LastMessageTime = DateTime.Now;
//更新接收者conversation
ReceptCon.UnreadCount += 1;
ReceptCon.LastMessage = @event.MessageContent;
senderCon.LastMessageTime = DateTime.Now;
_context.Conversations.UpdateRange(senderCon, ReceptCon);
await _context.SaveChangesAsync();
}
}
}
}

View File

@ -0,0 +1,23 @@
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Hubs;
using IM_API.Tools;
using Microsoft.AspNetCore.SignalR;
namespace IM_API.Application.EventHandlers
{
public class SignalREventHandler : IEventHandler<MessageCreatedEvent>
{
private readonly IHubContext<ChatHub> _hub;
public SignalREventHandler(IHubContext<ChatHub> hub)
{
_hub = hub;
}
public async Task Handle(MessageCreatedEvent @event)
{
var streamKey = @event.StreamKey;
await _hub.Clients.Group(streamKey).SendAsync(SignalRMethodDefine.ReceiveMessage, @event);
}
}
}

View File

@ -0,0 +1,9 @@
using IM_API.Domain.Interfaces;
namespace IM_API.Application.Interfaces
{
public interface IEventBus
{
Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent;
}
}

View File

@ -0,0 +1,9 @@
using IM_API.Domain.Interfaces;
namespace IM_API.Application.Interfaces
{
public interface IEventHandler<in TEvent> where TEvent : IEvent
{
Task Handle(TEvent @event);
}
}

View File

@ -1,4 +1,5 @@
using AutoMapper;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Models;
@ -59,11 +60,46 @@ namespace IM_API.Configs
.ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.TimeStamp))
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
.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.ChatType, opt => opt.Ignore())
.ForMember(dest => dest.MsgType, opt => opt.Ignore())
;
//会话对象深拷贝
CreateMap<Conversation, Conversation>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.UserId, opt => opt.Ignore())
.ForMember(dest => dest.TargetId, opt => opt.Ignore())
.ForMember(dest => dest.ChatType, opt => opt.Ignore())
.ForMember(dest => dest.StreamKey, opt => opt.Ignore())
;
//消息对象转消息创建事件对象
CreateMap<Message, MessageCreatedEvent>()
.ForMember(dest => dest.MessageMsgType, opt => opt.MapFrom(src => src.MsgTypeEnum))
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => src.ChatTypeEnum))
.ForMember(dest => dest.MessageContent, opt => opt.MapFrom(src => src.Content))
.ForMember(dest => dest.State, opt => opt.MapFrom(src => src.StateEnum))
.ForMember(dest => dest.MessageCreated, opt => opt.MapFrom(src => src.Created))
.ForMember(dest => dest.MessageId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.MsgRecipientId, opt => opt.MapFrom(src => src.Recipient))
.ForMember(dest => dest.MsgSenderId, opt => opt.MapFrom(src => src.Sender))
.ForMember(dest => dest.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
;
//消息发送事件转换会话对象
CreateMap<MessageCreatedEvent, Conversation>()
.ForMember(dest => dest.LastReadMessageId, opt => opt.MapFrom(src => src.MessageId))
.ForMember(dest => dest.LastMessage, opt => opt.MapFrom(src => src.MessageContent))
.ForMember(dest => dest.ChatType, opt => opt.MapFrom(src => (int)src.ChatType))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(src => src.MsgSenderId))
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.MsgRecipientId))
.ForMember(dest => dest.UnreadCount, opt => opt.MapFrom(src => 0))
.ForMember(dest => dest.StreamKey, opt => opt.MapFrom(src => src.StreamKey))
.ForMember(dest => dest.LastMessageTime, opt => opt.MapFrom(src => DateTime.Now))
;
}
}
}

View File

@ -1,4 +1,8 @@
using IM_API.Dtos;
using IM_API.Application.EventHandlers;
using IM_API.Application.Interfaces;
using IM_API.Domain.Events;
using IM_API.Dtos;
using IM_API.Infrastructure.EventBus;
using IM_API.Interface.Services;
using IM_API.Services;
using IM_API.Tools;
@ -18,6 +22,9 @@ namespace IM_API.Configs
services.AddTransient<IFriendSerivce, FriendService>();
services.AddTransient<IMessageSevice, MessageService>();
services.AddTransient<IConversationService, ConversationService>();
services.AddScoped<IEventBus, InMemoryEventBus>();
services.AddScoped<IEventHandler<MessageCreatedEvent>, SignalREventHandler>();
services.AddScoped<IEventHandler<MessageCreatedEvent>, ConversationEventHandler>();
services.AddSingleton<IJWTService, JWTService>();
services.AddSingleton<IRefreshTokenService, RedisRefreshTokenService>();
return services;

View File

@ -25,7 +25,7 @@ namespace IM_API.Controllers
{
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
var list = await _conversationSerivice.GetConversationsAsync(int.Parse(userIdStr));
var res = new BaseResponse<List<Conversation>>(list);
var res = new BaseResponse<List<ConversationDto>>(list);
return Ok(res);
}
[HttpPost]

View File

@ -0,0 +1,13 @@
using IM_API.Domain.Interfaces;
namespace IM_API.Domain.Events
{
public abstract record DomainEvent: IEvent
{
public Guid EventId { get; init; } = Guid.NewGuid();
public DateTime OccurredAt { get; init; } = DateTime.UtcNow;
public long OperatorId { get; init; }
public string AggregateId { get; init; } = "";
public abstract string EventType { get; }
}
}

View File

@ -0,0 +1,22 @@
using IM_API.Dtos;
using IM_API.Models;
namespace IM_API.Domain.Events
{
public record MessageCreatedEvent : DomainEvent
{
public override string EventType => "IM.MESSAGE.MESSAGE_CREATED";
public ChatType ChatType { get; set; }
public MessageMsgType MessageMsgType { get; set; }
public int MessageId { get; set; }
public string MessageContent { get; set; }
public int MsgSenderId { get; set; }
public int MsgRecipientId { get; set; }
public MessageState State { get; set; }
public DateTime MessageCreated { get; set; }
public string StreamKey { get; set; }
}
}

View File

@ -0,0 +1,6 @@
namespace IM_API.Domain.Interfaces
{
public interface IEvent
{
}
}

View File

@ -0,0 +1,51 @@
using IM_API.Models;
namespace IM_API.Dtos
{
public class ConversationDto
{
public int Id { get; set; }
/// <summary>
/// 用户
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 对方ID群聊为群聊ID单聊为单聊ID
/// </summary>
public int TargetId { get; set; }
/// <summary>
/// 最后一条未读消息ID
/// </summary>
public int? LastReadMessageId { get; set; }
/// <summary>
/// 未读消息数
/// </summary>
public int UnreadCount { get; set; }
public int ChatType { get; set; }
/// <summary>
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; } = null!;
/// <summary>
/// 最后一条消息时间
/// </summary>
public DateTime? DateTime { get; set; } = null;
/// <summary>
/// 对方昵称
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// 对方头像
/// </summary>
public string? TargetAvatar { get; set; }
public virtual Message? LastReadMessage { get; set; }
}
}

View File

@ -9,19 +9,27 @@ namespace IM_API.Hubs
public class ChatHub:Hub
{
private IMessageSevice _messageService;
public ChatHub(IMessageSevice messageService)
private readonly IConversationService _conversationService;
public ChatHub(IMessageSevice messageService, IConversationService conversationService)
{
_messageService = messageService;
_conversationService = conversationService;
}
public async override Task OnConnectedAsync()
{
if (!Context.User.Identity.IsAuthenticated)
{
await Clients.Caller.SendAsync("ReceiveMessage",new BaseResponse<object?>(CodeDefine.AUTH_FAILED));
Context.Abort();
return;
}
//将用户加入已加入聊天的会话组
string userIdStr = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var keys = await _conversationService.GetUserAllStreamKeyAsync(int.Parse(userIdStr));
foreach(var key in keys)
{
await Groups.AddToGroupAsync(Context.ConnectionId,key);
}
await base.OnConnectedAsync();
}
public async Task SendPrivateMessage(MessageBaseDto dto)

View File

@ -0,0 +1,32 @@
using IM_API.Application.Interfaces;
using IM_API.Domain.Interfaces;
namespace IM_API.Infrastructure.EventBus
{
public class InMemoryEventBus : IEventBus
{
private IServiceProvider _serviceProvider;
private ILogger<InMemoryEventBus> _logger;
public InMemoryEventBus(IServiceProvider serviceProvider, ILogger<InMemoryEventBus> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task PublishAsync<TEvent>(TEvent @event) where TEvent : IEvent
{
var handlers = _serviceProvider.GetServices<IEventHandler<TEvent>>();
foreach (var handler in handlers)
{
try
{
await handler.Handle(@event);
}
catch(Exception e)
{
_logger.LogError("EventHandler error:"+e.Message, e);
}
}
}
}
}

View File

@ -22,6 +22,12 @@ namespace IM_API.Interface.Services
/// </summary>
/// <param name="userId">用户id</param>
/// <returns></returns>
Task<List<Conversation>> GetConversationsAsync(int userId);
Task<List<ConversationDto>> GetConversationsAsync(int userId);
/// <summary>
/// 获取指定用户的所有推送标识符
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
Task<List<string>> GetUserAllStreamKeyAsync(int userId);
}
}

View File

@ -18,27 +18,33 @@ public partial class Conversation
public int TargetId { get; set; }
/// <summary>
/// 消息类型同Messages.MsgType
/// 最后一条未读消息ID
/// </summary>
public int MsgType { get; set; }
/// <summary>
/// 最后一条消息ID
/// </summary>
public int LastMessageId { get; set; }
public int? LastReadMessageId { get; set; }
/// <summary>
/// 未读消息数
/// </summary>
public int UnreadCount { get; set; }
public string TargetName { get; set; } = null!;
public string? TargetAvatar { get; set; }
public int ChatType { get; set; }
public virtual Message LastMessage { get; set; } = null!;
/// <summary>
/// 消息推送唯一标识符
/// </summary>
public string StreamKey { get; set; } = null!;
/// <summary>
/// 最后一条最新消息
/// </summary>
public string LastMessage { get; set; } = null!;
/// <summary>
/// 最后一条消息发送时间
/// </summary>
public DateTime LastMessageTime { get; set; }
public virtual Message? LastReadMessage { get; set; }
public virtual User User { get; set; } = null!;
}

View File

@ -44,6 +44,11 @@ public partial class Group
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// 群头像
/// </summary>
public string Avatar { get; set; } = null!;
public virtual ICollection<GroupInvite> GroupInvites { get; set; } = new List<GroupInvite>();
public virtual User GroupMasterNavigation { get; set; } = null!;

View File

@ -49,17 +49,10 @@ public partial class ImContext : DbContext
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySql(
"server=frp-era.com;port=26582;database=IM;user=product;password=12345678",
ServerVersion.Parse("5.7.44-mysql")
);
}
}
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)
{
@ -117,18 +110,25 @@ public partial class ImContext : DbContext
entity.HasIndex(e => e.UserId, "Userid");
entity.HasIndex(e => e.LastMessageId, "lastMessageId");
entity.HasIndex(e => e.LastReadMessageId, "lastMessageId");
entity.Property(e => e.Id)
.HasColumnType("int(11)")
.HasColumnName("ID");
entity.Property(e => e.LastMessageId)
.HasComment("最后一条消息ID ")
entity.Property(e => e.ChatType).HasColumnType("int(11)");
entity.Property(e => e.LastMessage)
.HasMaxLength(255)
.HasComment("最后一条最新消息");
entity.Property(e => e.LastMessageTime)
.HasComment("最后一条消息发送时间")
.HasColumnType("datetime");
entity.Property(e => e.LastReadMessageId)
.HasComment("最后一条未读消息ID ")
.HasColumnType("int(11)")
.HasColumnName("lastMessageId");
entity.Property(e => e.MsgType)
.HasComment("消息类型同Messages.MsgType ")
.HasColumnType("int(11)");
.HasColumnName("lastReadMessageId");
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.Property(e => e.TargetId)
.HasComment("对方ID群聊为群聊ID单聊为单聊ID ")
.HasColumnType("int(11)");
@ -139,9 +139,8 @@ public partial class ImContext : DbContext
.HasComment("用户")
.HasColumnType("int(11)");
entity.HasOne(d => d.LastMessage).WithMany(p => p.Conversations)
.HasForeignKey(d => d.LastMessageId)
.OnDelete(DeleteBehavior.ClientSetNull)
entity.HasOne(d => d.LastReadMessage).WithMany(p => p.Conversations)
.HasForeignKey(d => d.LastReadMessageId)
.HasConstraintName("conversations_ibfk_2");
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
@ -337,6 +336,9 @@ public partial class ImContext : DbContext
entity.Property(e => e.Auhority)
.HasComment("群权限\r\n0需管理员同意,1任意人可加群,2不允许任何人加入")
.HasColumnType("tinyint(4)");
entity.Property(e => e.Avatar)
.HasMaxLength(255)
.HasComment("群头像");
entity.Property(e => e.Created)
.HasComment("群聊创建时间")
.HasColumnType("datetime");
@ -557,6 +559,9 @@ public partial class ImContext : DbContext
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)

View File

@ -110,24 +110,22 @@ public partial class ImDbContext : DbContext
entity.HasIndex(e => e.UserId, "Userid");
entity.HasIndex(e => e.LastMessageId, "lastMessageId");
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.LastMessageId)
.HasComment("最后一条消息ID ")
entity.Property(e => e.LastReadMessageId)
.HasComment("最后一条已读消息ID ")
.HasColumnType("int(11)")
.HasColumnName("lastMessageId");
entity.Property(e => e.MsgType)
.HasComment("消息类型同Messages.MsgType ")
.HasColumnType("int(11)");
entity.Property(e => e.TargetAvatar).HasMaxLength(255);
entity.Property(e => e.StreamKey)
.HasMaxLength(255)
.HasComment("消息推送唯一标识符");
entity.Property(e => e.TargetId)
.HasComment("对方ID群聊为群聊ID单聊为单聊ID ")
.HasColumnType("int(11)");
entity.Property(e => e.TargetName).HasMaxLength(255);
entity.Property(e => e.UnreadCount)
.HasComment("未读消息数 ")
.HasColumnType("int(11)");
@ -135,11 +133,6 @@ public partial class ImDbContext : DbContext
.HasComment("用户")
.HasColumnType("int(11)");
entity.HasOne(d => d.LastMessage).WithMany(p => p.Conversations)
.HasForeignKey(d => d.LastMessageId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("conversations_ibfk_2");
entity.HasOne(d => d.User).WithMany(p => p.Conversations)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
@ -553,6 +546,9 @@ public partial class ImDbContext : DbContext
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)

View File

@ -44,6 +44,11 @@ public partial class Message
/// </summary>
public DateTime Created { get; set; }
/// <summary>
/// 消息推送唯一标识符
/// </summary>
public string StreamKey { get; set; } = null!;
public virtual ICollection<Conversation> Conversations { get; set; } = new List<Conversation>();
public virtual ICollection<File> Files { get; set; } = new List<File>();

View File

@ -24,9 +24,48 @@ namespace IM_API.Services
#endregion
#region
public async Task<List<Conversation>> GetConversationsAsync(int userId)
public async Task<List<ConversationDto>> GetConversationsAsync(int userId)
{
return await _context.Conversations.Where(x => x.UserId == 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
};
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();
}
#endregion
@ -39,6 +78,15 @@ namespace IM_API.Services
await _context.SaveChangesAsync();
return true;
}
#endregion
public async Task<List<string>> GetUserAllStreamKeyAsync(int userId)
{
return await _context.Conversations.Where(x => x.UserId == userId)
.Select(x => x.StreamKey)
.Distinct()
.ToListAsync();
}
}
}

View File

@ -65,6 +65,7 @@ namespace IM_API.Services
if (!isMember) throw new BaseException(CodeDefine.NO_GROUP_PERMISSION);
var message = _mapper.Map<Message>(dto);
message.Sender = senderId;
message.StreamKey = StreamKeyBuilder.Group(groupId);
_context.Messages.Add(message);
await _context.SaveChangesAsync();
return true;
@ -78,6 +79,8 @@ namespace IM_API.Services
if (!isExist) throw new BaseException(CodeDefine.FRIEND_RELATION_NOT_FOUND);
var message = _mapper.Map<Message>(dto);
message.Sender = senderId;
//生成消息流唯一标识符
message.StreamKey = StreamKeyBuilder.Private(dto.SenderId, dto.ReceiverId);
_context.Messages.Add(message);
await _context.SaveChangesAsync();
return true;

View File

@ -0,0 +1,7 @@
namespace IM_API.Tools
{
public class SignalRMethodDefine
{
public static string ReceiveMessage = "ReceiveMessage";
}
}

View File

@ -0,0 +1,17 @@
namespace IM_API.Tools
{
public static class StreamKeyBuilder
{
public static string Private(long a, long b)
{
if (a <= 0 || b <= 0) throw new ArgumentException();
return $"p:{(a < b ? $"{a}_{b}" : $"{b}_{a}")}";
}
public static string Group(long groupId)
{
if (groupId <= 0) throw new ArgumentException();
return $"g:{groupId}";
}
}
}