using System.Text.RegularExpressions; using BuildFlow.Shared.Domain.Exceptions; using IM.Application.Chat.DTOs; using IM.Application.Chat.Queries; using IM.Domain.Common; using IM.Infrastructure.Mongo; using IM.Infrastructure.Persistence; using MediatR; using MongoDB.Bson; using MongoDB.Entities; namespace IM.Application.Chat.Handlers; /// /// 历史消息搜索处理器。基于 MongoDB 对消息正文、发送者名称和文件名进行模糊检索。 /// public class SearchMessagesQueryHandler( ImDbContext db, IUserInfoService userInfoService) : IRequestHandler { public async Task Handle(SearchMessagesQuery request, CancellationToken ct) { var isParticipant = await db.ConversationParticipants .AnyAsync(p => p.ConversationId == request.ConversationId && p.UserId == request.UserId, ct); if (!isParticipant) { throw new UnauthorizedException("无权搜索该会话的消息"); } var keyword = request.Keyword.Trim(); if (keyword.Length == 0) { return new MessageHistoryDto([], false); } var escaped = Regex.Escape(keyword); var regex = new BsonRegularExpression(escaped, "i"); var messages = await DB.Default.Find() .Match(f => f.And( f.Eq(m => m.ConversationId, request.ConversationId), f.Or( f.Regex(m => m.Content, regex), f.Regex(m => m.SenderName, regex), f.Regex("Metadata.FileName", regex)))) .Sort(m => m.CreatedAt, Order.Descending) .Limit(request.Limit) .ExecuteAsync(ct); var dtos = await ToMessageDtosAsync(messages, ct); return new MessageHistoryDto(dtos, false); } private async Task> ToMessageDtosAsync(List messages, CancellationToken ct) { var senderIds = messages .Where(m => string.IsNullOrEmpty(m.SenderAvatar)) .Select(m => m.SenderId) .Distinct() .ToList(); var users = senderIds.Count > 0 ? await userInfoService.GetUsersAsync(senderIds, ct) : new Dictionary(); return messages.Select(m => { var avatar = m.SenderAvatar; if (string.IsNullOrEmpty(avatar)) { avatar = users.GetValueOrDefault(m.SenderId.ToString()).Avatar; } return new MessageDto( m.ID, m.ConversationId, m.SenderId, m.SenderName, avatar, m.ContentType, m.Content, m.Metadata != null ? new MessageMetadataDto( m.Metadata.FileName, m.Metadata.FileSize, m.Metadata.FileUrl, m.Metadata.ImageUrl, m.Metadata.ImageWidth, m.Metadata.ImageHeight, m.Metadata.EmojiCode) { BucketName = m.Metadata.BucketName, ObjectKey = m.Metadata.ObjectKey, ContentType = m.Metadata.ContentType } : null, m.ReplyToId, m.CreatedAt, m.MentionUserIds, m.MentionAll, m.IsRecalled, m.RecalledAt, m.RecalledBy, m.ForwardFromMessageId, m.ForwardFromSenderName, m.ForwardFromConversationId); }).ToList(); } }