build-flow.api/src/modules/im/Application/Chat/Handlers/GetConversationsQueryHandler.cs

92 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using BuildFlow.Platform.Application.Users.DTOs;
using BuildFlow.Platform.Application.Users.Queries;
using IM.Application.Chat.DTOs;
using IM.Application.Chat.Queries;
using IM.Domain.Common;
using IM.Infrastructure.Persistence;
using MediatR;
namespace IM.Application.Chat.Handlers;
/// <summary>
/// 用户会话列表查询处理器。聚合最后消息与未读数。
/// </summary>
public class GetConversationsQueryHandler(
ImDbContext db,
IMediator mediator,
IUnreadCounter unreadCounter)
: IRequestHandler<GetConversationsQuery, List<ConversationDto>>
{
public async Task<List<ConversationDto>> Handle(GetConversationsQuery request, CancellationToken ct)
{
// 一次性 Include 参与者与群组导航属性,避免 N+1 查询
var participants = await db.ConversationParticipants
.Where(p => p.UserId == request.UserId)
.Include(p => p.ConversationEntity)
.ThenInclude(c => c.Participants)
.Include(p => p.ConversationEntity)
.ThenInclude(c => c.GroupEntity)
.Where(p => !p.ConversationEntity.IsDeleted)
// 排序:置顶优先(PinnedAt 倒序),其次按最后消息时间倒序
.OrderByDescending(p => p.IsPinned)
.ThenByDescending(p => p.PinnedAt)
.ThenByDescending(p => p.ConversationEntity.LastMessageAt)
.ToListAsync(ct);
// 私聊会话需解析"对方"用户以展示头像/昵称;批量收集去重后一次性查询 Platform,避免逐会话往返。
var otherUserIds = participants
.Where(p => p.ConversationEntity.Type == Domain.Enums.ConversationTypeEnum.Private)
.Select(p => p.ConversationEntity.Participants
.FirstOrDefault(op => op.UserId != request.UserId)?.UserId)
.Where(id => id.HasValue)
.Select(id => id!.Value)
.Distinct()
.ToList();
var users = otherUserIds.Count > 0
? await mediator.Send(new GetUserSummariesQuery(otherUserIds), ct)
: new Dictionary<Guid, UserSummaryDto>();
var redisUnread = await unreadCounter.GetByUserAsync(
request.UserId,
participants.Select(p => p.ConversationId),
ct);
return participants.Select(p =>
{
string? otherUserName = null;
string? otherUserAvatar = null;
Guid? otherUserId = null;
if (p.ConversationEntity.Type == Domain.Enums.ConversationTypeEnum.Private)
{
var otherParticipant = p.ConversationEntity.Participants
.FirstOrDefault(op => op.UserId != request.UserId);
if (otherParticipant is not null)
{
otherUserId = otherParticipant.UserId;
var userInfo = users.GetValueOrDefault(otherParticipant.UserId);
otherUserName = userInfo?.Username;
otherUserAvatar = userInfo?.Avatar;
}
}
return new ConversationDto(
p.ConversationEntity.Id,
p.ConversationEntity.Type,
p.ConversationEntity.LastMessageContent,
p.ConversationEntity.LastMessageAt,
redisUnread.TryGetValue(p.ConversationId, out var unreadCount) ? unreadCount : p.UnreadCount,
p.ConversationEntity.GroupEntity?.Name,
p.ConversationEntity.GroupEntity?.Avatar,
p.ConversationEntity.GroupEntity?.Id,
otherUserId,
otherUserName,
otherUserAvatar,
p.IsPinned,
p.IsMuted,
p.Remark);
}).ToList();
}
}