Files
frontend/src/stores/message/MessageStateManager.ts
lafay 1bee7ea551
All checks were successful
Frontend CI / ota-android (push) Successful in 11m20s
Frontend CI / build-and-push-web (push) Successful in 34m19s
Frontend CI / build-android-apk (push) Successful in 57m45s
refactor(message): modularize MessageManager architecture
重构消息管理模块,将单体 MessageManager 拆分为职责单一的子模块:

- MessageStateManager: 纯状态管理
- MessageDeduplication: 消息去重服务
- UserCacheService: 用户缓存服务
- ReadReceiptManager: 已读回执管理
- ConversationOperations: 会话操作
- MessageSendService: 消息发送
- MessageSyncService: 消息同步
- WSMessageHandler: WebSocket 消息处理

新增类型定义和常量文件,保持原有 API 向后兼容。修复 CallScreen 状态值错误,补充 PostDetailScreen 缺失样式,优化 GroupInfoPanel 导入。
2026-03-31 16:13:24 +08:00

261 lines
7.9 KiB
TypeScript

/**
* 消息状态管理器
* 纯状态管理类,不包含业务逻辑
* 负责管理所有消息相关的内存状态
*/
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<string, ConversationResponse>): 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<MessageResponse>): 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<string, MessageResponse>();
[...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();