using System.Text.Json; using Hangfire; using Microsoft.Extensions.Logging; using XiangYi.Application.DTOs.Requests; using XiangYi.Application.DTOs.Responses; using XiangYi.Application.Interfaces; using XiangYi.Application.Jobs; using XiangYi.Core.Constants; using XiangYi.Core.Entities.Biz; using XiangYi.Core.Enums; using XiangYi.Core.Exceptions; using XiangYi.Core.Interfaces; namespace XiangYi.Application.Services; /// /// 聊天服务实现 /// public class ChatService : IChatService { private readonly IRepository _userRepository; private readonly IRepository _sessionRepository; private readonly IRepository _messageRepository; private readonly IRepository _profileRepository; private readonly IRepository _photoRepository; private readonly INotificationService _notificationService; private readonly IBackgroundJobClient _backgroundJobClient; private readonly ILogger _logger; public ChatService( IRepository userRepository, IRepository sessionRepository, IRepository messageRepository, IRepository profileRepository, IRepository photoRepository, INotificationService notificationService, IBackgroundJobClient backgroundJobClient, ILogger logger) { _userRepository = userRepository; _sessionRepository = sessionRepository; _messageRepository = messageRepository; _profileRepository = profileRepository; _photoRepository = photoRepository; _notificationService = notificationService; _backgroundJobClient = backgroundJobClient; _logger = logger; } #region 发送消息 /// public async Task SendMessageAsync(long senderId, SendMessageRequest request) { // 验证会话存在且用户有权限 var session = await _sessionRepository.GetByIdAsync(request.SessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(senderId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 验证接收者是会话的另一方 var receiverId = GetOtherUserId(senderId, session); if (receiverId != request.ReceiverId) { throw new BusinessException(ErrorCodes.InvalidParameter, "接收者ID不正确"); } // 验证消息类型 if (request.MessageType < (int)MessageType.Text || request.MessageType > (int)MessageType.Image) { throw new BusinessException(ErrorCodes.InvalidMessageType, "无效的消息类型"); } // 当收到新消息时,自动恢复接收者已删除的会话 var needUpdateSession = false; if (session.User1Id == receiverId && session.User1Deleted) { session.User1Deleted = false; needUpdateSession = true; _logger.LogInformation("恢复接收者已删除的会话: SessionId={SessionId}, ReceiverId={ReceiverId}", session.Id, receiverId); } else if (session.User2Id == receiverId && session.User2Deleted) { session.User2Deleted = false; needUpdateSession = true; _logger.LogInformation("恢复接收者已删除的会话: SessionId={SessionId}, ReceiverId={ReceiverId}", session.Id, receiverId); } // 检查是否是该会话的首条消息(用于发送首次消息通知) var isFirstMessage = !await _messageRepository.ExistsAsync(m => m.SessionId == request.SessionId && m.SenderId == senderId && m.Status == (int)MessageStatus.Normal); // 创建消息 var message = new ChatMessage { SessionId = request.SessionId, SenderId = senderId, ReceiverId = request.ReceiverId, MessageType = request.MessageType, Content = request.Content, VoiceUrl = request.VoiceUrl, VoiceDuration = request.VoiceDuration, Status = (int)MessageStatus.Normal, IsRead = false, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; var createdMessage = await _messageRepository.AddAsync(message); // 更新会话最后消息信息和未读数(如果需要恢复会话,也一并更新) await UpdateSessionLastMessageAsync(session, createdMessage, senderId, needUpdateSession); _logger.LogInformation("发送消息成功: MessageId={MessageId}, SessionId={SessionId}, SenderId={SenderId}, ReceiverId={ReceiverId}", createdMessage.Id, request.SessionId, senderId, request.ReceiverId); // 如果是首次消息,发送通知给接收者(异步,不阻塞主流程) if (isFirstMessage) { _ = Task.Run(async () => { try { await _notificationService.SendFirstMessageNotificationAsync(request.ReceiverId, senderId); } catch (Exception ex) { _logger.LogError(ex, "发送首次消息通知失败: ReceiverId={ReceiverId}, SenderId={SenderId}", request.ReceiverId, senderId); } }); } // 创建5分钟延迟任务,检查接收者是否回复 try { _backgroundJobClient.Schedule( job => job.SendNoReplyReminderAsync(createdMessage.Id, senderId, request.ReceiverId, request.SessionId), TimeSpan.FromMinutes(5)); _logger.LogInformation("已创建5分钟未回复提醒任务: MessageId={MessageId}", createdMessage.Id); } catch (Exception ex) { _logger.LogError(ex, "创建5分钟未回复提醒任务失败: MessageId={MessageId}", createdMessage.Id); } return new SendMessageResponse { MessageId = createdMessage.Id, CreateTime = createdMessage.CreateTime }; } #endregion #region 会话管理 /// public async Task> GetSessionsAsync(long userId) { _logger.LogInformation("获取会话列表: UserId={UserId}", userId); // 先查询所有相关会话(包括已删除的),用于诊断 var allSessions = await _sessionRepository.GetListAsync(s => s.User1Id == userId || s.User2Id == userId); _logger.LogInformation("用户相关的所有会话(含已删除): Count={Count}, UserId={UserId}", allSessions.Count, userId); foreach (var s in allSessions) { _logger.LogInformation("会话详情: SessionId={SessionId}, User1Id={User1Id}, User2Id={User2Id}, User1Deleted={User1Deleted}, User2Deleted={User2Deleted}, LastMessageTime={LastMessageTime}", s.Id, s.User1Id, s.User2Id, s.User1Deleted, s.User2Deleted, s.LastMessageTime); } // 获取用户参与的所有会话,排除已删除的 var sessions = await _sessionRepository.GetListAsync(s => (s.User1Id == userId && !s.User1Deleted) || (s.User2Id == userId && !s.User2Deleted)); _logger.LogInformation("查询到会话数量(排除已删除): Count={Count}, UserId={UserId}", sessions.Count, userId); // 按最后消息时间排序 sessions = sessions.OrderByDescending(s => s.LastMessageTime ?? s.CreateTime).ToList(); var responses = new List(); foreach (var session in sessions) { var otherUserId = GetOtherUserId(userId, session); var otherUser = await _userRepository.GetByIdAsync(otherUserId); // 获取最后一条消息 ChatMessage? lastMessage = null; if (session.LastMessageId.HasValue) { lastMessage = await _messageRepository.GetByIdAsync(session.LastMessageId.Value); } // 获取未读数 var unreadCount = session.User1Id == userId ? session.User1UnreadCount : session.User2UnreadCount; responses.Add(new ChatSessionResponse { SessionId = session.Id, OtherUserId = otherUserId, OtherNickname = otherUser?.Nickname, OtherAvatar = otherUser?.Avatar, OtherIsRealName = otherUser?.IsRealName ?? false, LastMessageContent = GetMessageDisplayContent(lastMessage), LastMessageType = lastMessage?.MessageType, LastMessageTime = session.LastMessageTime, UnreadCount = unreadCount }); } return responses; } /// public async Task GetOrCreateSessionAsync(long userId1, long userId2) { // 确保User1Id < User2Id var (user1Id, user2Id) = userId1 < userId2 ? (userId1, userId2) : (userId2, userId1); // 查找现有会话 var existingSessions = await _sessionRepository.GetListAsync(s => s.User1Id == user1Id && s.User2Id == user2Id); var existingSession = existingSessions.FirstOrDefault(); if (existingSession != null) { // 如果会话存在但被当前用户删除了,恢复它 var needUpdate = false; if (userId1 == user1Id && existingSession.User1Deleted) { existingSession.User1Deleted = false; needUpdate = true; } else if (userId1 == user2Id && existingSession.User2Deleted) { existingSession.User2Deleted = false; needUpdate = true; } if (needUpdate) { existingSession.UpdateTime = DateTime.Now; await _sessionRepository.UpdateAsync(existingSession); _logger.LogInformation("恢复已删除的聊天会话: SessionId={SessionId}, UserId={UserId}", existingSession.Id, userId1); } return existingSession.Id; } // 创建新会话 var session = new ChatSession { User1Id = user1Id, User2Id = user2Id, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; var createdSession = await _sessionRepository.AddAsync(session); _logger.LogInformation("创建聊天会话: User1Id={User1Id}, User2Id={User2Id}, SessionId={SessionId}", user1Id, user2Id, createdSession.Id); return createdSession.Id; } /// public async Task CanAccessSessionAsync(long userId, long sessionId) { var session = await _sessionRepository.GetByIdAsync(sessionId); if (session == null) { return false; } return IsUserInSession(userId, session); } /// public async Task DeleteSessionAsync(long userId, long sessionId) { var session = await _sessionRepository.GetByIdAsync(sessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(userId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 软删除:标记该用户已删除此会话 // 如果是用户1删除,设置User1Deleted = true // 如果是用户2删除,设置User2Deleted = true if (session.User1Id == userId) { session.User1Deleted = true; } else if (session.User2Id == userId) { session.User2Deleted = true; } session.UpdateTime = DateTime.Now; await _sessionRepository.UpdateAsync(session); _logger.LogInformation("用户删除会话: UserId={UserId}, SessionId={SessionId}", userId, sessionId); } #endregion #region 消息管理 /// public async Task> GetMessagesAsync(long userId, GetMessagesRequest request) { // 验证会话存在且用户有权限 var session = await _sessionRepository.GetByIdAsync(request.SessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(userId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 获取消息列表 var (messages, total) = await _messageRepository.GetPagedListAsync( m => m.SessionId == request.SessionId && m.Status == (int)MessageStatus.Normal, request.PageIndex, request.PageSize, m => m.CreateTime, isDescending: true); var responses = messages.Select(m => new ChatMessageResponse { MessageId = m.Id, SessionId = m.SessionId, SenderId = m.SenderId, ReceiverId = m.ReceiverId, MessageType = m.MessageType, Content = m.Content, VoiceUrl = m.VoiceUrl, VoiceDuration = m.VoiceDuration, ExtraData = m.ExtraData, IsRead = m.IsRead, CreateTime = m.CreateTime, IsSelf = m.SenderId == userId }).ToList(); return new PagedResult { Items = responses, Total = total, PageIndex = request.PageIndex, PageSize = request.PageSize }; } /// public async Task MarkMessagesAsReadAsync(long userId, long sessionId) { // 验证会话存在且用户有权限 var session = await _sessionRepository.GetByIdAsync(sessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(userId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 获取未读消息(接收者是当前用户的消息) var unreadMessages = await _messageRepository.GetListAsync(m => m.SessionId == sessionId && m.ReceiverId == userId && !m.IsRead && m.Status == (int)MessageStatus.Normal); // 标记消息为已读 foreach (var message in unreadMessages) { message.IsRead = true; message.UpdateTime = DateTime.Now; await _messageRepository.UpdateAsync(message); } // 始终重置会话未读数(即使没有未读消息,也确保未读数为0) var needUpdateSession = false; if (session.User1Id == userId && session.User1UnreadCount > 0) { session.User1UnreadCount = 0; needUpdateSession = true; } else if (session.User2Id == userId && session.User2UnreadCount > 0) { session.User2UnreadCount = 0; needUpdateSession = true; } if (needUpdateSession) { session.UpdateTime = DateTime.Now; await _sessionRepository.UpdateAsync(session); } _logger.LogInformation("标记消息已读: SessionId={SessionId}, UserId={UserId}, Count={Count}", sessionId, userId, unreadMessages.Count); return unreadMessages.Count; } /// public async Task GetUnreadCountAsync(long userId) { // 获取用户参与的所有会话 var sessions = await _sessionRepository.GetListAsync(s => s.User1Id == userId || s.User2Id == userId); var totalUnread = 0; foreach (var session in sessions) { totalUnread += session.User1Id == userId ? session.User1UnreadCount : session.User2UnreadCount; } return totalUnread; } #endregion #region 交换功能 /// public async Task ExchangeWeChatAsync(long senderId, ExchangeWeChatRequest request) { // 验证会话存在且用户有权限 var session = await _sessionRepository.GetByIdAsync(request.SessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(senderId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 验证接收者是会话的另一方 var receiverId = GetOtherUserId(senderId, session); if (receiverId != request.ReceiverId) { throw new BusinessException(ErrorCodes.InvalidParameter, "接收者ID不正确"); } // 创建交换请求消息 var extraData = new ExchangeExtraData { Status = 0 // 待响应 }; var message = new ChatMessage { SessionId = request.SessionId, SenderId = senderId, ReceiverId = request.ReceiverId, MessageType = (int)MessageType.ExchangeWeChatRequest, Content = "请求交换微信", ExtraData = JsonSerializer.Serialize(extraData), Status = (int)MessageStatus.Normal, IsRead = false, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; var createdMessage = await _messageRepository.AddAsync(message); // 更新会话最后消息信息 await UpdateSessionLastMessageAsync(session, createdMessage, senderId); _logger.LogInformation("发送交换微信请求: MessageId={MessageId}, SessionId={SessionId}, SenderId={SenderId}", createdMessage.Id, request.SessionId, senderId); return new ExchangeRequestResponse { RequestMessageId = createdMessage.Id, CreateTime = createdMessage.CreateTime }; } /// public async Task ExchangePhotoAsync(long senderId, ExchangePhotoRequest request) { // 验证会话存在且用户有权限 var session = await _sessionRepository.GetByIdAsync(request.SessionId); if (session == null) { throw new BusinessException(ErrorCodes.SessionNotFound, "会话不存在"); } if (!IsUserInSession(senderId, session)) { throw new BusinessException(ErrorCodes.SessionAccessDenied, "无权访问该会话"); } // 验证接收者是会话的另一方 var receiverId = GetOtherUserId(senderId, session); if (receiverId != request.ReceiverId) { throw new BusinessException(ErrorCodes.InvalidParameter, "接收者ID不正确"); } // 创建交换请求消息 var extraData = new ExchangeExtraData { Status = 0 // 待响应 }; var message = new ChatMessage { SessionId = request.SessionId, SenderId = senderId, ReceiverId = request.ReceiverId, MessageType = (int)MessageType.ExchangePhotoRequest, Content = "请求交换照片", ExtraData = JsonSerializer.Serialize(extraData), Status = (int)MessageStatus.Normal, IsRead = false, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; var createdMessage = await _messageRepository.AddAsync(message); // 更新会话最后消息信息 await UpdateSessionLastMessageAsync(session, createdMessage, senderId); _logger.LogInformation("发送交换照片请求: MessageId={MessageId}, SessionId={SessionId}, SenderId={SenderId}", createdMessage.Id, request.SessionId, senderId); return new ExchangeRequestResponse { RequestMessageId = createdMessage.Id, CreateTime = createdMessage.CreateTime }; } /// public async Task RespondExchangeAsync(long userId, RespondExchangeRequest request) { // 获取原始请求消息 var requestMessage = await _messageRepository.GetByIdAsync(request.RequestMessageId); if (requestMessage == null) { throw new BusinessException(ErrorCodes.ExchangeRequestNotFound, "交换请求不存在"); } // 验证是接收者响应 if (requestMessage.ReceiverId != userId) { throw new BusinessException(ErrorCodes.CannotRespondOwnRequest, "只有接收者可以响应交换请求"); } // 验证消息类型是交换请求 if (requestMessage.MessageType != (int)MessageType.ExchangeWeChatRequest && requestMessage.MessageType != (int)MessageType.ExchangePhotoRequest) { throw new BusinessException(ErrorCodes.InvalidMessageType, "该消息不是交换请求"); } // 解析并验证状态 var extraData = string.IsNullOrEmpty(requestMessage.ExtraData) ? new ExchangeExtraData() : JsonSerializer.Deserialize(requestMessage.ExtraData) ?? new ExchangeExtraData(); if (extraData.Status != 0) { throw new BusinessException(ErrorCodes.ExchangeAlreadyResponded, "该请求已被响应"); } // 更新请求消息状态 extraData.Status = request.IsAgreed ? 1 : 2; // 如果同意,获取双方的数据 string? exchangedData = null; if (request.IsAgreed) { if (requestMessage.MessageType == (int)MessageType.ExchangeWeChatRequest) { // 获取双方微信号 var senderProfile = (await _profileRepository.GetListAsync(p => p.UserId == requestMessage.SenderId)).FirstOrDefault(); var receiverProfile = (await _profileRepository.GetListAsync(p => p.UserId == requestMessage.ReceiverId)).FirstOrDefault(); extraData.SenderWeChat = senderProfile?.WeChatNo; extraData.ReceiverWeChat = receiverProfile?.WeChatNo; // 包含 RequestMessageId 和 Status 以便前端精确匹配和状态更新 exchangedData = JsonSerializer.Serialize(new { RequestMessageId = request.RequestMessageId, Status = 1, // 已同意 SenderWeChat = extraData.SenderWeChat, ReceiverWeChat = extraData.ReceiverWeChat }); } else { // 获取双方照片 var senderPhotos = await _photoRepository.GetListAsync(p => p.UserId == requestMessage.SenderId); var receiverPhotos = await _photoRepository.GetListAsync(p => p.UserId == requestMessage.ReceiverId); extraData.SenderPhotos = senderPhotos.OrderBy(p => p.Sort).Select(p => p.PhotoUrl).ToList(); extraData.ReceiverPhotos = receiverPhotos.OrderBy(p => p.Sort).Select(p => p.PhotoUrl).ToList(); // 包含 RequestMessageId 和 Status 以便前端精确匹配和状态更新 exchangedData = JsonSerializer.Serialize(new { RequestMessageId = request.RequestMessageId, Status = 1, // 已同意 SenderPhotos = extraData.SenderPhotos, ReceiverPhotos = extraData.ReceiverPhotos }); } } // 更新请求消息 requestMessage.ExtraData = JsonSerializer.Serialize(extraData); requestMessage.UpdateTime = DateTime.Now; await _messageRepository.UpdateAsync(requestMessage); // 创建结果消息 var resultMessageType = requestMessage.MessageType == (int)MessageType.ExchangeWeChatRequest ? (int)MessageType.ExchangeWeChatResult : (int)MessageType.ExchangePhotoResult; var resultExtraData = new ExchangeExtraData { RequestMessageId = request.RequestMessageId, Status = extraData.Status, SenderWeChat = extraData.SenderWeChat, ReceiverWeChat = extraData.ReceiverWeChat, SenderPhotos = extraData.SenderPhotos, ReceiverPhotos = extraData.ReceiverPhotos }; var resultMessage = new ChatMessage { SessionId = requestMessage.SessionId, SenderId = userId, ReceiverId = requestMessage.SenderId, MessageType = resultMessageType, Content = request.IsAgreed ? "已同意交换" : "已拒绝交换", ExtraData = JsonSerializer.Serialize(resultExtraData), Status = (int)MessageStatus.Normal, IsRead = false, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; var createdResultMessage = await _messageRepository.AddAsync(resultMessage); // 更新会话最后消息信息 var session = await _sessionRepository.GetByIdAsync(requestMessage.SessionId); if (session != null) { await UpdateSessionLastMessageAsync(session, createdResultMessage, userId); } _logger.LogInformation("响应交换请求: RequestMessageId={RequestMessageId}, ResultMessageId={ResultMessageId}, IsAgreed={IsAgreed}", request.RequestMessageId, createdResultMessage.Id, request.IsAgreed); return new ExchangeRespondResponse { ResultMessageId = createdResultMessage.Id, IsAgreed = request.IsAgreed, ExchangedData = exchangedData, SessionId = requestMessage.SessionId, RequesterId = requestMessage.SenderId, RequestMessageType = requestMessage.MessageType, RequestMessageId = request.RequestMessageId }; } #endregion #region 私有方法 /// /// 检查用户是否在会话中 /// private static bool IsUserInSession(long userId, ChatSession session) { return session.User1Id == userId || session.User2Id == userId; } /// /// 获取会话中的另一个用户ID /// private static long GetOtherUserId(long userId, ChatSession session) { return session.User1Id == userId ? session.User2Id : session.User1Id; } /// /// 获取消息显示内容 /// private static string? GetMessageDisplayContent(ChatMessage? message) { if (message == null) return null; return message.MessageType switch { (int)MessageType.Text => message.Content, (int)MessageType.Voice => "[语音消息]", (int)MessageType.Image => "[图片]", (int)MessageType.ExchangeWeChatRequest => "[请求交换微信]", (int)MessageType.ExchangeWeChatResult => "[交换微信结果]", (int)MessageType.ExchangePhotoRequest => "[请求交换照片]", (int)MessageType.ExchangePhotoResult => "[交换照片结果]", _ => message.Content }; } /// /// 更新会话最后消息信息 /// /// 会话 /// 消息 /// 发送者ID /// 是否强制更新(用于恢复已删除会话等场景) private async Task UpdateSessionLastMessageAsync(ChatSession session, ChatMessage message, long senderId, bool forceUpdate = false) { session.LastMessageId = message.Id; session.LastMessageTime = message.CreateTime; session.UpdateTime = DateTime.Now; // 增加接收者的未读数 if (session.User1Id == senderId) { session.User2UnreadCount++; } else { session.User1UnreadCount++; } await _sessionRepository.UpdateAsync(session); } #endregion #region 静态方法(用于属性测试) /// /// 验证消息是否应该被持久化(静态方法,用于测试) /// /// 消息类型 /// 消息内容 /// 语音URL /// 是否应该被持久化 public static bool ShouldMessageBePersisted(int messageType, string? content, string? voiceUrl) { // 文本消息需要有内容 if (messageType == (int)MessageType.Text) { return !string.IsNullOrWhiteSpace(content); } // 语音消息需要有URL if (messageType == (int)MessageType.Voice) { return !string.IsNullOrWhiteSpace(voiceUrl); } // 图片消息需要有内容(URL) if (messageType == (int)MessageType.Image) { return !string.IsNullOrWhiteSpace(content); } return false; } /// /// 验证消息持久化后可以被查询到(静态方法,用于测试) /// /// 消息列表 /// 消息ID /// 消息是否存在于列表中 public static bool IsMessageInList(List messages, long messageId) { return messages.Any(m => m.Id == messageId && m.Status == (int)MessageStatus.Normal); } /// /// 计算交换请求状态流转(静态方法,用于测试) /// /// 当前状态:0待响应 1已同意 2已拒绝 /// 是否同意 /// 新状态,如果无法流转返回-1 public static int CalculateExchangeStatusTransition(int currentStatus, bool isAgreed) { // 只有待响应状态可以流转 if (currentStatus != 0) { return -1; // 无法流转 } return isAgreed ? 1 : 2; } /// /// 验证交换请求状态流转是否有效(静态方法,用于测试) /// /// 原状态 /// 目标状态 /// 是否有效 public static bool IsValidExchangeStatusTransition(int fromStatus, int toStatus) { // 只有从待响应(0)可以流转到已同意(1)或已拒绝(2) if (fromStatus == 0 && (toStatus == 1 || toStatus == 2)) { return true; } return false; } /// /// 验证交换请求响应后状态是否正确(静态方法,用于测试) /// /// 原始状态 /// 是否同意 /// 新状态 /// 状态是否正确 public static bool IsExchangeResponseStatusCorrect(int originalStatus, bool isAgreed, int newStatus) { // 原始状态必须是待响应 if (originalStatus != 0) { return false; } // 同意应该变成1,拒绝应该变成2 var expectedStatus = isAgreed ? 1 : 2; return newStatus == expectedStatus; } #endregion }