/** * 消息状态管理器 * 纯状态管理类,不包含业务逻辑 * 负责管理所有消息相关的内存状态 */ import type { ConversationResponse, MessageResponse, } from '../../types/dto'; import type { MessageManagerState, MessageSubscriber, MessageEvent, IMessageStateManager, } from './types'; export class MessageStateManager implements IMessageStateManager { private state: MessageManagerState; constructor() { this.state = { conversations: new Map(), conversationList: [], messagesMap: new Map(), totalUnreadCount: 0, systemUnreadCount: 0, isWSConnected: false, currentConversationId: null, isLoadingConversations: false, loadingMessagesSet: new Set(), isInitialized: false, subscribers: new Set(), typingUsersMap: new Map(), mutedStatusMap: new Map(), }; } // ==================== 获取状态 ==================== getState(): MessageManagerState { return this.state; } getConversations(): ConversationResponse[] { return this.state.conversationList; } getConversation(conversationId: string): ConversationResponse | null { const normalizedId = this.normalizeConversationId(conversationId); return this.state.conversations.get(normalizedId) || null; } getMessages(conversationId: string): MessageResponse[] { const normalizedId = this.normalizeConversationId(conversationId); return this.state.messagesMap.get(normalizedId) || []; } getUnreadCount(): { total: number; system: number } { return { total: this.state.totalUnreadCount, system: this.state.systemUnreadCount, }; } isConnected(): boolean { return this.state.isWSConnected; } isLoading(): boolean { return this.state.isLoadingConversations; } isLoadingMessages(conversationId: string): boolean { const normalizedId = this.normalizeConversationId(conversationId); return this.state.loadingMessagesSet.has(normalizedId); } getTypingUsers(groupId: string): string[] { return this.state.typingUsersMap.get(groupId) || []; } isMuted(groupId: string): boolean { return this.state.mutedStatusMap.get(groupId) || false; } getActiveConversation(): string | null { return this.state.currentConversationId; } // ==================== 设置状态 ==================== setConversations(conversations: Map): void { this.state.conversations = conversations; this.updateConversationList(); } updateConversation(conversation: ConversationResponse): void { const id = this.normalizeConversationId(conversation.id); this.state.conversations.set(id, { ...conversation, id }); this.updateConversationList(); } removeConversation(conversationId: string): void { const normalizedId = this.normalizeConversationId(conversationId); const target = this.state.conversations.get(normalizedId); const removedUnread = target?.unread_count || 0; this.state.conversations.delete(normalizedId); this.state.messagesMap.delete(normalizedId); this.state.totalUnreadCount = Math.max(0, this.state.totalUnreadCount - removedUnread); if (this.state.currentConversationId === normalizedId) { this.state.currentConversationId = null; } this.updateConversationList(); } setMessages(conversationId: string, messages: MessageResponse[]): void { const normalizedId = this.normalizeConversationId(conversationId); this.state.messagesMap.set(normalizedId, messages); } addMessage(conversationId: string, message: MessageResponse): void { const normalizedId = this.normalizeConversationId(conversationId); const existing = this.state.messagesMap.get(normalizedId) || []; const exists = existing.some(m => String(m.id) === String(message.id)); if (!exists) { const updated = this.mergeMessagesById(existing, [message]); this.state.messagesMap.set(normalizedId, updated); } } updateMessage(conversationId: string, messageId: string, updates: Partial): void { const normalizedId = this.normalizeConversationId(conversationId); const messages = this.state.messagesMap.get(normalizedId); if (!messages) return; const updated = messages.map(m => String(m.id) === String(messageId) ? { ...m, ...updates } : m ); this.state.messagesMap.set(normalizedId, updated); } setUnreadCount(total: number, system: number): void { this.state.totalUnreadCount = total; this.state.systemUnreadCount = system; } setSSEConnected(connected: boolean): void { this.state.isWSConnected = connected; } setCurrentConversation(conversationId: string | null): void { this.state.currentConversationId = conversationId; } setLoading(loading: boolean): void { this.state.isLoadingConversations = loading; } setLoadingMessages(conversationId: string, loading: boolean): void { const normalizedId = this.normalizeConversationId(conversationId); if (loading) { this.state.loadingMessagesSet.add(normalizedId); } else { this.state.loadingMessagesSet.delete(normalizedId); } } setTypingUsers(groupId: string, users: string[]): void { this.state.typingUsersMap.set(groupId, users); } setMutedStatus(groupId: string, isMuted: boolean): void { this.state.mutedStatusMap.set(groupId, isMuted); } setInitialized(initialized: boolean): void { this.state.isInitialized = initialized; } // ==================== 订阅机制 ==================== subscribe(subscriber: MessageSubscriber): () => void { this.state.subscribers.add(subscriber); return () => { this.state.subscribers.delete(subscriber); }; } notifySubscribers(event: MessageEvent): void { this.state.subscribers.forEach(subscriber => { try { subscriber(event); } catch (error) { console.error('[MessageStateManager] 订阅者执行失败:', error); } }); } // ==================== 工具方法 ==================== private normalizeConversationId(conversationId: string | number | null | undefined): string { return conversationId == null ? '' : String(conversationId); } /** * 更新会话列表排序 * 排序规则:置顶优先,再按最后消息时间排序 */ private updateConversationList(): void { const list = Array.from(this.state.conversations.values()).sort((a, b) => { const aPinned = a.is_pinned ? 1 : 0; const bPinned = b.is_pinned ? 1 : 0; if (aPinned !== bPinned) { return bPinned - aPinned; } const aTime = new Date(a.last_message_at || a.updated_at || 0).getTime(); const bTime = new Date(b.last_message_at || b.updated_at || 0).getTime(); return bTime - aTime; }); this.state.conversationList = list; } /** * 按 message_id 合并消息并按 seq 排序 * 后者覆盖前者,可用来吸收更完整的同 ID 消息体 */ private mergeMessagesById(base: MessageResponse[], incoming: MessageResponse[]): MessageResponse[] { if (incoming.length === 0) return base; const merged = new Map(); [...base, ...incoming].forEach(msg => { merged.set(String(msg.id), msg); }); return Array.from(merged.values()).sort((a, b) => a.seq - b.seq); } // ==================== 重置 ==================== reset(): void { this.state.conversations.clear(); this.state.conversationList = []; this.state.messagesMap.clear(); this.state.totalUnreadCount = 0; this.state.systemUnreadCount = 0; this.state.isWSConnected = false; this.state.currentConversationId = null; this.state.isLoadingConversations = false; this.state.loadingMessagesSet.clear(); this.state.isInitialized = false; this.state.typingUsersMap.clear(); this.state.mutedStatusMap.clear(); } } // 单例导出 export const messageStateManager = new MessageStateManager();