add(conversation):完善私聊好友会话服务
This commit is contained in:
commit
41963dde70
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+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")]
|
||||
|
||||
@ -1 +1 @@
|
||||
0157672e490e31c525582995ff9972f067909b74fb5aeca923e211efe4aa3af8
|
||||
7390b702e3c578dad3a8fa4fa4cc93b25ccd34a9b353beca60372a7182717d73
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
backend/IM_API/Application/Interfaces/IEventBus.cs
Normal file
9
backend/IM_API/Application/Interfaces/IEventBus.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
9
backend/IM_API/Application/Interfaces/IEventHandler.cs
Normal file
9
backend/IM_API/Application/Interfaces/IEventHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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]
|
||||
|
||||
13
backend/IM_API/Domain/Events/DomainEvent.cs
Normal file
13
backend/IM_API/Domain/Events/DomainEvent.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
22
backend/IM_API/Domain/Events/MessageCreatedEvent.cs
Normal file
22
backend/IM_API/Domain/Events/MessageCreatedEvent.cs
Normal 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; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
6
backend/IM_API/Domain/Interfaces/IEvent.cs
Normal file
6
backend/IM_API/Domain/Interfaces/IEvent.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace IM_API.Domain.Interfaces
|
||||
{
|
||||
public interface IEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
51
backend/IM_API/Dtos/ConversationDto.cs
Normal file
51
backend/IM_API/Dtos/ConversationDto.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
32
backend/IM_API/Infrastructure/EventBus/InMemoryEventBus.cs
Normal file
32
backend/IM_API/Infrastructure/EventBus/InMemoryEventBus.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!;
|
||||
}
|
||||
|
||||
@ -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!;
|
||||
|
||||
@ -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\n(0:需管理员同意,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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
7
backend/IM_API/Tools/SignalRMethodDefine.cs
Normal file
7
backend/IM_API/Tools/SignalRMethodDefine.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace IM_API.Tools
|
||||
{
|
||||
public class SignalRMethodDefine
|
||||
{
|
||||
public static string ReceiveMessage = "ReceiveMessage";
|
||||
}
|
||||
}
|
||||
17
backend/IM_API/Tools/StreamKeyBuilder.cs
Normal file
17
backend/IM_API/Tools/StreamKeyBuilder.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user