/** * ProcessMessageUseCase - 处理消息用例 * 处理消息的业务逻辑,协调 Repository 和 SSEClient * 纯业务逻辑,无UI依赖 */ import { messageRepository, userCacheRepository, CachedMessage } from '@/database'; import { wsClient, WSEventType } from '../../data/datasources/WSClient'; import { MessageMapper } from '@/data/mappers'; import { Message, Conversation, GroupNotice, createMessage, isMessageFromCurrentUser, shouldIncrementUnread, buildGroupNoticeText, } from '../entities/Message'; import { messageService } from '../../services/messageService'; import { api } from '../../services/api'; const cachedMessageToMessage = (cached: CachedMessage): Message => ({ id: cached.id, conversationId: cached.conversationId, senderId: cached.senderId, seq: cached.seq, segments: cached.segments || [], createdAt: cached.createdAt, status: cached.status as Message['status'], }); // 事件类型定义 export type MessageUseCaseEventType = | 'message_received' | 'message_recalled' | 'typing_status' | 'group_notice' | 'connection_changed' | 'error'; export interface MessageUseCaseEvent { type: MessageUseCaseEventType; payload: any; timestamp: number; } export type MessageUseCaseSubscriber = (event: MessageUseCaseEvent) => void; // 已读状态保护记录 interface ReadStateRecord { timestamp: number; version: number; lastReadSeq: number; clearTimer?: ReturnType; } class ProcessMessageUseCase { private subscribers: Set = new Set(); private unsubscribeFns: Array<() => void> = []; private processedMessageIds: Set = new Set(); private processedMessageIdsExpiry: Map = new Map(); private pendingReadMap: Map = new Map(); private readStateVersion = 0; private pendingUserRequests: Map> = new Map(); private currentUserId: string | null = null; // 常量 private readonly READ_STATE_PROTECTION_DELAY = 5000; // 5秒 private readonly MESSAGE_ID_EXPIRY = 5 * 60 * 1000; // 5分钟 /** * 初始化用例 */ initialize(currentUserId: string | null): void { this.currentUserId = currentUserId; this.setupWSListeners(); } /** * 销毁用例 */ destroy(): void { this.unsubscribeFns.forEach((fn) => fn()); this.unsubscribeFns = []; this.subscribers.clear(); this.processedMessageIds.clear(); this.processedMessageIdsExpiry.clear(); this.pendingReadMap.clear(); this.pendingUserRequests.clear(); } /** * 设置SSE监听器 */ private setupWSListeners(): void { // 监听私聊消息 const unsubChat = wsClient.on('chat', (message) => { this.handleNewMessage(message); }); // 监听群聊消息 const unsubGroupMessage = wsClient.on('group_message', (message) => { this.handleNewMessage(message); }); // 监听私聊已读回执 const unsubRead = wsClient.on('read', (message) => { this.handleReadReceipt(message); }); // 监听群聊已读回执 const unsubGroupRead = wsClient.on('group_read', (message) => { this.handleGroupReadReceipt(message); }); // 监听私聊消息撤回 const unsubRecall = wsClient.on('recall', (message) => { this.handleRecallMessage(message); }); // 监听群聊消息撤回 const unsubGroupRecall = wsClient.on('group_recall', (message) => { this.handleGroupRecallMessage(message); }); // 监听群聊输入状态 const unsubGroupTyping = wsClient.on('group_typing', (message) => { this.handleGroupTyping(message); }); // 监听群通知 const unsubGroupNotice = wsClient.on('group_notice', (message) => { this.handleGroupNotice(message); }); // 监听连接状态 const unsubConnected = wsClient.on('connected', () => { this.notifySubscribers({ type: 'connection_changed', payload: { connected: true }, timestamp: Date.now(), }); }); const unsubDisconnected = wsClient.on('disconnected', () => { this.notifySubscribers({ type: 'connection_changed', payload: { connected: false }, timestamp: Date.now(), }); }); this.unsubscribeFns = [ unsubChat, unsubGroupMessage, unsubRead, unsubGroupRead, unsubRecall, unsubGroupRecall, unsubGroupTyping, unsubGroupNotice, unsubConnected, unsubDisconnected, ]; } /** * 处理新消息 */ private async handleNewMessage(message: any): Promise { const { conversation_id, id, sender_id, seq, segments, created_at, _isAck } = message; // 消息去重检查 if (this.isMessageProcessed(id)) { return; } this.markMessageAsProcessed(id); // 构造消息对象 const newMessage = createMessage({ id, conversationId: String(conversation_id), senderId: sender_id, seq, segments: segments || [], createdAt: created_at, }); // 异步获取发送者信息(如果是群聊且不是当前用户发送的) if (message.type === 'group_message' && sender_id && sender_id !== this.currentUserId) { this.enrichMessageWithSender(newMessage); } // 异步保存到本地数据库 const isRead = _isAck || sender_id === this.currentUserId; messageRepository.saveMessage({ id: newMessage.id, conversationId: newMessage.conversationId, senderId: newMessage.senderId, seq: newMessage.seq, segments: newMessage.segments, createdAt: newMessage.createdAt, status: newMessage.status || 'normal', isRead, }).catch((error) => { console.error('[ProcessMessageUseCase] 保存消息失败:', error); }); // 通知订阅者 this.notifySubscribers({ type: 'message_received', payload: { message: newMessage, isCurrentUser: sender_id === this.currentUserId, isAck: _isAck, }, timestamp: Date.now(), }); } /** * 丰富消息的发送者信息 */ private async enrichMessageWithSender(message: Message): Promise { try { const user = await this.getSenderInfo(message.senderId); if (user) { message.sender = user; // 重新通知,包含发送者信息 this.notifySubscribers({ type: 'message_received', payload: { message, isCurrentUser: false, isUpdated: true, }, timestamp: Date.now(), }); } } catch (error) { console.error('[ProcessMessageUseCase] 获取发送者信息失败:', error); } } /** * 获取发送者信息(带缓存和去重) */ private async getSenderInfo(userId: string): Promise { // 先检查本地缓存 const cachedUser = await userCacheRepository.get(userId); if (cachedUser) { return cachedUser; } // 检查是否已有正在进行的请求 const pendingRequest = this.pendingUserRequests.get(userId); if (pendingRequest) { return pendingRequest; } // 发起新请求 const request = this.fetchUserInfo(userId); this.pendingUserRequests.set(userId, request); try { const user = await request; return user; } finally { this.pendingUserRequests.delete(userId); } } /** * 从服务器获取用户信息 */ private async fetchUserInfo(userId: string): Promise { try { const response = await api.get(`/users/${userId}`); if (response.code === 0 && response.data) { await userCacheRepository.save(response.data as any); return response.data; } return null; } catch (error) { console.error(`[ProcessMessageUseCase] 获取用户信息失败: ${userId}`, error); return null; } } /** * 检查消息是否已处理 */ private isMessageProcessed(messageId: string): boolean { return this.processedMessageIds.has(messageId); } /** * 标记消息已处理 */ private markMessageAsProcessed(messageId: string): void { this.processedMessageIds.add(messageId); this.processedMessageIdsExpiry.set(messageId, Date.now()); // 定期清理 if (this.processedMessageIds.size > 1000) { this.cleanupProcessedMessageIds(); } } /** * 清理过期的消息ID */ private cleanupProcessedMessageIds(): void { const now = Date.now(); for (const [id, timestamp] of this.processedMessageIdsExpiry.entries()) { if (now - timestamp > this.MESSAGE_ID_EXPIRY) { this.processedMessageIds.delete(id); this.processedMessageIdsExpiry.delete(id); } } } /** * 处理私聊已读回执 */ private handleReadReceipt(message: any): void { // 可以在这里处理对方已读的状态更新 } /** * 处理群聊已读回执 */ private handleGroupReadReceipt(message: any): void { // 可以在这里处理群聊已读状态更新 } /** * 处理私聊消息撤回 */ private handleRecallMessage(message: any): Promise { const { conversation_id, message_id } = message; // 更新本地数据库状态 messageRepository .updateStatus(message_id, 'recalled', true) .catch((error: unknown) => { console.error('[ProcessMessageUseCase] 更新撤回状态失败:', error); }); // 通知订阅者 this.notifySubscribers({ type: 'message_recalled', payload: { conversationId: String(conversation_id), messageId: message_id, isGroup: false, }, timestamp: Date.now(), }); return Promise.resolve(); } /** * 处理群聊消息撤回 */ private handleGroupRecallMessage(message: any): Promise { const { conversation_id, message_id } = message; // 更新本地数据库状态 messageRepository .updateStatus(message_id, 'recalled', true) .catch((error: unknown) => { console.error('[ProcessMessageUseCase] 更新撤回状态失败:', error); }); // 通知订阅者 this.notifySubscribers({ type: 'message_recalled', payload: { conversationId: String(conversation_id), messageId: message_id, isGroup: true, }, timestamp: Date.now(), }); return Promise.resolve(); } /** * 处理群聊输入状态 */ private handleGroupTyping(message: any): void { const { group_id, user_id, is_typing } = message; this.notifySubscribers({ type: 'typing_status', payload: { groupId: String(group_id), userId: user_id, isTyping: is_typing, }, timestamp: Date.now(), }); } /** * 处理群通知 */ private handleGroupNotice(message: any): void { const { notice_type, group_id, data, timestamp, message_id, seq } = message; const groupIdStr = String(group_id); const notice: GroupNotice = { type: notice_type, groupId: groupIdStr, data: data || {}, timestamp: timestamp || Date.now(), messageId: message_id, seq, }; // 通知订阅者 this.notifySubscribers({ type: 'group_notice', payload: { notice, text: buildGroupNoticeText(notice), }, timestamp: Date.now(), }); } /** * 标记会话已读 */ async markAsRead(conversationId: string, seq: number): Promise { const existingRecord = this.pendingReadMap.get(conversationId); // 使用seq去重 if (existingRecord && seq <= existingRecord.lastReadSeq) { return; } // 清除可能存在的旧定时器 if (existingRecord?.clearTimer) { clearTimeout(existingRecord.clearTimer); } // 递增全局版本号 this.readStateVersion++; const currentVersion = this.readStateVersion; // 记录已读状态 this.pendingReadMap.set(conversationId, { timestamp: Date.now(), version: currentVersion, lastReadSeq: seq, }); // 更新本地数据库 await messageRepository.markConversationAsRead(conversationId); // 调用API try { await messageService.markAsRead(conversationId, seq); } catch (error) { console.error('[ProcessMessageUseCase] 标记已读API失败:', error); this.pendingReadMap.delete(conversationId); throw error; } // 设置延迟清除保护 const clearTimer = setTimeout(() => { const record = this.pendingReadMap.get(conversationId); if (record && record.version === currentVersion) { this.pendingReadMap.delete(conversationId); } }, this.READ_STATE_PROTECTION_DELAY); this.pendingReadMap.set(conversationId, { timestamp: Date.now(), version: currentVersion, lastReadSeq: seq, clearTimer, }); } /** * 检查会话是否有进行中的已读请求 */ hasPendingRead(conversationId: string): boolean { return this.pendingReadMap.has(conversationId); } /** * 获取进行中的已读记录 */ getPendingReadRecord(conversationId: string): ReadStateRecord | undefined { return this.pendingReadMap.get(conversationId); } /** * 发送消息 */ async sendMessage( conversationId: string, segments: any[], options?: { replyToId?: string } ): Promise { try { const response = await messageService.sendMessage(conversationId, { segments, reply_to_id: options?.replyToId, }); if (response) { const message = createMessage({ id: response.id, conversationId, senderId: this.currentUserId || '', seq: response.seq, segments, createdAt: new Date().toISOString(), }); // 保存到本地数据库 await messageRepository.saveMessage({ id: message.id, conversationId: message.conversationId, senderId: message.senderId, seq: message.seq, segments: message.segments, createdAt: message.createdAt, status: message.status || 'normal', isRead: true, }); return message; } return null; } catch (error) { console.error('[ProcessMessageUseCase] 发送消息失败:', error); throw error; } } /** * 获取本地消息 */ async getLocalMessages(conversationId: string, limit: number = 20): Promise { const cached = await messageRepository.getByConversation(conversationId, limit); return cached.map(cachedMessageToMessage); } /** * 获取历史消息 */ async getHistoryMessages( conversationId: string, beforeSeq: number, limit: number = 20 ): Promise { // 先从本地获取 const localMessages = await messageRepository.getBeforeSeq( conversationId, beforeSeq, limit ); if (localMessages.length >= limit) { return localMessages.map(cachedMessageToMessage); } // 本地数据不足,从服务端获取 try { const response = await messageService.getMessages( conversationId, undefined, beforeSeq, limit ); if (response?.messages && response.messages.length > 0) { const serverMessages = response.messages.map((m: any) => createMessage({ id: m.id, conversationId: m.conversation_id || conversationId, senderId: m.sender_id, seq: m.seq, segments: m.segments || [], createdAt: m.created_at, status: m.status || 'normal', }) ); // 保存到本地 await messageRepository.saveMessagesBatch( MessageMapper.toCachedMessages(serverMessages, conversationId) ); return serverMessages; } } catch (error) { console.error('[ProcessMessageUseCase] 获取历史消息失败:', error); } return localMessages.map(cachedMessageToMessage); } /** * 从服务端同步消息 */ async syncMessagesFromServer( conversationId: string, afterSeq?: number ): Promise { try { const response = await messageService.getMessages(conversationId, afterSeq); if (response?.messages && response.messages.length > 0) { const messages = response.messages.map((m: any) => createMessage({ id: m.id, conversationId: m.conversation_id || conversationId, senderId: m.sender_id, seq: m.seq, segments: m.segments || [], createdAt: m.created_at, status: m.status || 'normal', }) ); await messageRepository.saveMessagesBatch( MessageMapper.toCachedMessages(response.messages, conversationId) ); return messages; } } catch (error) { console.error('[ProcessMessageUseCase] 同步消息失败:', error); } return []; } /** * 订阅事件 */ subscribe(subscriber: MessageUseCaseSubscriber): () => void { this.subscribers.add(subscriber); return () => { this.subscribers.delete(subscriber); }; } /** * 通知订阅者 */ private notifySubscribers(event: MessageUseCaseEvent): void { this.subscribers.forEach((subscriber) => { try { subscriber(event); } catch (error) { console.error('[ProcessMessageUseCase] 订阅者执行失败:', error); } }); } /** * 获取当前用户ID */ getCurrentUserId(): string | null { return this.currentUserId; } /** * 设置当前用户ID */ setCurrentUserId(userId: string | null): void { this.currentUserId = userId; } } export const processMessageUseCase = new ProcessMessageUseCase(); export default processMessageUseCase;