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();
}
}