refactor(message): replace MessageStateManager with zustand store
- Remove MessageStateManager wrapper layer in favor of direct zustand store - Move service modules to services/ subdirectory (ConversationOperations, MessageDeduplication, MessageSendService, MessageSyncService, ReadReceiptManager, UserCacheService, WSMessageHandler) - Add new zustand-based store with subscriptionManager for event handling - Introduce React hooks for message state (useConversations, useMessages, useUnreadCount, etc.) - Update exports in index.ts to reflect new module structure - Deprecate messageManager.ts entry point in favor of message/index.ts - Maintain backward compatibility with existing MessageManager API
This commit is contained in:
@@ -1,59 +1,30 @@
|
||||
/**
|
||||
* MessageManager - 消息管理核心模块(重构版)
|
||||
*
|
||||
* 作为协调者,整合各个专注的服务模块
|
||||
* 保持与原始版本完全兼容的 API
|
||||
* 作为轻量级协调者,整合各个专注的服务模块
|
||||
* 直接使用 zustand store 进行状态管理
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 MessageStateManager 包装层
|
||||
* - 直接使用 zustand store
|
||||
* - 服务层移至 services/ 目录
|
||||
*/
|
||||
|
||||
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
|
||||
import { messageService } from '../../services/messageService';
|
||||
import {
|
||||
wsService,
|
||||
WSChatMessage,
|
||||
WSGroupChatMessage,
|
||||
WSReadMessage,
|
||||
WSGroupReadMessage,
|
||||
WSRecallMessage,
|
||||
WSGroupRecallMessage,
|
||||
WSGroupTypingMessage,
|
||||
WSGroupNoticeMessage,
|
||||
GroupNoticeType,
|
||||
} from '../../services/wsService';
|
||||
import {
|
||||
saveMessage,
|
||||
saveMessagesBatch,
|
||||
getMessagesByConversation,
|
||||
getMaxSeq,
|
||||
getMinSeq,
|
||||
getMessagesBeforeSeq,
|
||||
markConversationAsRead,
|
||||
updateConversationCacheUnreadCount,
|
||||
CachedMessage,
|
||||
getUserCache,
|
||||
saveUserCache,
|
||||
updateMessageStatus,
|
||||
deleteConversation as deleteConversationFromDb,
|
||||
saveConversationsWithRelatedCache,
|
||||
} from '../../services/database';
|
||||
import { api } from '../../services/api';
|
||||
import { vibrateOnMessage } from '../../services/messageVibrationService';
|
||||
import { useAuthStore } from '../authStore';
|
||||
import {
|
||||
type IConversationListPagedSource,
|
||||
SqliteConversationListPagedSource,
|
||||
createRemoteConversationListSource,
|
||||
CONVERSATION_LIST_PAGE_SIZE,
|
||||
} from '../conversationListSources';
|
||||
import type { MessageManagerConversationListDeps } from './types';
|
||||
|
||||
// 导入新模块
|
||||
import { MessageStateManager } from './MessageStateManager';
|
||||
import { MessageDeduplication } from './MessageDeduplication';
|
||||
import { UserCacheService } from './UserCacheService';
|
||||
import { ReadReceiptManager } from './ReadReceiptManager';
|
||||
import { ConversationOperations } from './ConversationOperations';
|
||||
import { MessageSendService } from './MessageSendService';
|
||||
import { MessageSyncService } from './MessageSyncService';
|
||||
import { WSMessageHandler } from './WSMessageHandler';
|
||||
// 导入服务
|
||||
import { MessageDeduplication, messageDeduplication } from './services/MessageDeduplication';
|
||||
import { UserCacheService, userCacheService } from './services/UserCacheService';
|
||||
import { ReadReceiptManager } from './services/ReadReceiptManager';
|
||||
import { ConversationOperations } from './services/ConversationOperations';
|
||||
import { MessageSendService } from './services/MessageSendService';
|
||||
import { MessageSyncService } from './services/MessageSyncService';
|
||||
import { WSMessageHandler } from './services/WSMessageHandler';
|
||||
|
||||
// 导入 store
|
||||
import { useMessageStore, subscriptionManager, normalizeConversationId } from './store';
|
||||
|
||||
// 重新导出类型,保持兼容性
|
||||
export type {
|
||||
@@ -69,7 +40,6 @@ export type {
|
||||
|
||||
class MessageManager {
|
||||
// 子模块
|
||||
private stateManager: MessageStateManager;
|
||||
private deduplication: MessageDeduplication;
|
||||
private userCacheService: UserCacheService;
|
||||
private readReceiptManager: ReadReceiptManager;
|
||||
@@ -84,21 +54,20 @@ class MessageManager {
|
||||
private initializePromise: Promise<void> | null = null;
|
||||
private activatingConversationTasks: Map<string, Promise<void>> = new Map();
|
||||
|
||||
constructor(deps?: import('./types').MessageManagerConversationListDeps) {
|
||||
constructor(deps?: MessageManagerConversationListDeps) {
|
||||
// 初始化子模块
|
||||
this.stateManager = new MessageStateManager();
|
||||
this.deduplication = new MessageDeduplication();
|
||||
this.userCacheService = new UserCacheService();
|
||||
this.readReceiptManager = new ReadReceiptManager(this.stateManager);
|
||||
this.conversationOps = new ConversationOperations(this.stateManager);
|
||||
this.sendService = new MessageSendService(this.stateManager, () => this.getCurrentUserId());
|
||||
this.readReceiptManager = new ReadReceiptManager();
|
||||
this.conversationOps = new ConversationOperations();
|
||||
this.sendService = new MessageSendService(() => this.getCurrentUserId());
|
||||
this.syncService = new MessageSyncService(
|
||||
this.stateManager,
|
||||
() => this.getCurrentUserId(),
|
||||
this.readReceiptManager,
|
||||
this.userCacheService,
|
||||
deps
|
||||
);
|
||||
this.wsHandler = new WSMessageHandler(
|
||||
this.stateManager,
|
||||
this.deduplication,
|
||||
this.userCacheService,
|
||||
() => this.getCurrentUserId(),
|
||||
@@ -119,8 +88,9 @@ class MessageManager {
|
||||
private initAuthListener() {
|
||||
const authState = useAuthStore.getState();
|
||||
this.currentUserId = authState.currentUser?.id || null;
|
||||
const store = useMessageStore.getState();
|
||||
|
||||
if (this.currentUserId && !this.stateManager.getState().isInitialized) {
|
||||
if (this.currentUserId && !store.isInitialized) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@@ -130,17 +100,17 @@ class MessageManager {
|
||||
const prevUserId = this.currentUserId;
|
||||
this.currentUserId = nextUserId;
|
||||
|
||||
if (nextUserId && !this.stateManager.getState().isInitialized) {
|
||||
if (nextUserId && !useMessageStore.getState().isInitialized) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
if (nextUserId && this.stateManager.getActiveConversation()) {
|
||||
this.activateConversation(this.stateManager.getActiveConversation()!, { forceSync: true }).catch(error => {
|
||||
if (nextUserId && useMessageStore.getState().currentConversationId) {
|
||||
this.activateConversation(useMessageStore.getState().currentConversationId!, { forceSync: true }).catch(error => {
|
||||
console.error('[MessageManager] 登录态就绪后重激活会话失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
if (!nextUserId && prevUserId && this.stateManager.getState().isInitialized) {
|
||||
if (!nextUserId && prevUserId && useMessageStore.getState().isInitialized) {
|
||||
this.destroy();
|
||||
}
|
||||
});
|
||||
@@ -154,14 +124,12 @@ class MessageManager {
|
||||
return this.currentUserId;
|
||||
}
|
||||
|
||||
private normalizeConversationId(conversationId: string | number | null | undefined): string {
|
||||
return conversationId == null ? '' : String(conversationId);
|
||||
}
|
||||
|
||||
// ==================== 初始化与销毁 ====================
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.stateManager.getState().isInitialized) {
|
||||
const store = useMessageStore.getState();
|
||||
|
||||
if (store.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,26 +157,26 @@ class MessageManager {
|
||||
// 加载未读数
|
||||
await this.fetchUnreadCount();
|
||||
|
||||
this.stateManager.setInitialized(true);
|
||||
useMessageStore.getState().setInitialized(true);
|
||||
|
||||
// 处理缓冲的 SSE 事件
|
||||
await this.wsHandler.flushBufferedSSEEvents();
|
||||
|
||||
this.wsHandler.setBootstrapping(false);
|
||||
|
||||
this.stateManager.notifySubscribers({
|
||||
subscriptionManager.notifySubscribers({
|
||||
type: 'conversations_updated',
|
||||
payload: { conversations: this.stateManager.getConversations() },
|
||||
payload: { conversations: this.getConversations() },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
if (this.stateManager.getActiveConversation()) {
|
||||
await this.fetchMessages(this.stateManager.getActiveConversation()!);
|
||||
if (useMessageStore.getState().currentConversationId) {
|
||||
await this.fetchMessages(useMessageStore.getState().currentConversationId!);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MessageManager] 初始化失败:', error);
|
||||
this.wsHandler.setBootstrapping(false);
|
||||
this.stateManager.notifySubscribers({
|
||||
subscriptionManager.notifySubscribers({
|
||||
type: 'error',
|
||||
payload: { error, context: 'initialize' },
|
||||
timestamp: Date.now(),
|
||||
@@ -225,7 +193,8 @@ class MessageManager {
|
||||
|
||||
destroy(): void {
|
||||
this.wsHandler.disconnect();
|
||||
this.stateManager.reset();
|
||||
useMessageStore.getState().reset();
|
||||
subscriptionManager.clear();
|
||||
this.syncService.restartSources();
|
||||
this.readReceiptManager.reset();
|
||||
this.deduplication.reset();
|
||||
@@ -310,61 +279,61 @@ class MessageManager {
|
||||
return this.conversationOps.decrementSystemUnreadCount(count);
|
||||
}
|
||||
|
||||
// ==================== 状态查询方法(代理到 stateManager)====================
|
||||
// ==================== 状态查询方法(直接使用 store)====================
|
||||
|
||||
getConversations(): ConversationResponse[] {
|
||||
return this.stateManager.getConversations();
|
||||
return useMessageStore.getState().getConversations();
|
||||
}
|
||||
|
||||
getConversation(conversationId: string): ConversationResponse | null {
|
||||
return this.stateManager.getConversation(conversationId);
|
||||
return useMessageStore.getState().getConversation(conversationId);
|
||||
}
|
||||
|
||||
getMessages(conversationId: string): MessageResponse[] {
|
||||
return this.stateManager.getMessages(conversationId);
|
||||
return useMessageStore.getState().getMessages(conversationId);
|
||||
}
|
||||
|
||||
getUnreadCount(): { total: number; system: number } {
|
||||
return this.stateManager.getUnreadCount();
|
||||
return useMessageStore.getState().getUnreadCount();
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.stateManager.isConnected();
|
||||
return useMessageStore.getState().isConnected();
|
||||
}
|
||||
|
||||
isLoading(): boolean {
|
||||
return this.stateManager.isLoading();
|
||||
return useMessageStore.getState().isLoading();
|
||||
}
|
||||
|
||||
isLoadingMessages(conversationId: string): boolean {
|
||||
return this.stateManager.isLoadingMessages(conversationId);
|
||||
return useMessageStore.getState().isLoadingMessages(conversationId);
|
||||
}
|
||||
|
||||
getTypingUsers(groupId: string): string[] {
|
||||
return this.stateManager.getTypingUsers(groupId);
|
||||
return useMessageStore.getState().getTypingUsers(groupId);
|
||||
}
|
||||
|
||||
isMuted(groupId: string): boolean {
|
||||
return this.stateManager.isMuted(groupId);
|
||||
return useMessageStore.getState().isMuted(groupId);
|
||||
}
|
||||
|
||||
setMutedStatus(groupId: string, isMuted: boolean): void {
|
||||
return this.stateManager.setMutedStatus(groupId, isMuted);
|
||||
return useMessageStore.getState().setMutedStatus(groupId, isMuted);
|
||||
}
|
||||
|
||||
// ==================== 活动会话管理 ====================
|
||||
|
||||
setActiveConversation(conversationId: string | null): void {
|
||||
const normalizedId = conversationId ? this.normalizeConversationId(conversationId) : null;
|
||||
this.stateManager.setCurrentConversation(normalizedId);
|
||||
const normalizedId = conversationId ? normalizeConversationId(conversationId) : null;
|
||||
useMessageStore.getState().setCurrentConversation(normalizedId);
|
||||
}
|
||||
|
||||
getActiveConversation(): string | null {
|
||||
return this.stateManager.getActiveConversation();
|
||||
return useMessageStore.getState().currentConversationId;
|
||||
}
|
||||
|
||||
async activateConversation(conversationId: string, options?: { forceSync?: boolean }): Promise<void> {
|
||||
const normalizedId = this.normalizeConversationId(conversationId);
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
if (!normalizedId) return;
|
||||
|
||||
await this.initialize();
|
||||
@@ -372,7 +341,7 @@ class MessageManager {
|
||||
|
||||
// 先下发当前内存快照
|
||||
const snapshot = this.getMessages(normalizedId);
|
||||
this.stateManager.notifySubscribers({
|
||||
subscriptionManager.notifySubscribers({
|
||||
type: 'messages_updated',
|
||||
payload: {
|
||||
conversationId: normalizedId,
|
||||
@@ -405,16 +374,16 @@ class MessageManager {
|
||||
// ==================== 订阅机制 ====================
|
||||
|
||||
subscribe(subscriber: import('./types').MessageSubscriber): () => void {
|
||||
const unsubscribe = this.stateManager.subscribe(subscriber);
|
||||
const unsubscribe = subscriptionManager.subscribe(subscriber);
|
||||
|
||||
// 立即发送当前状态给新订阅者(与原始版本保持一致)
|
||||
subscriber({
|
||||
type: 'conversations_updated',
|
||||
payload: { conversations: this.stateManager.getConversations() },
|
||||
payload: { conversations: this.getConversations() },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const unreadCount = this.stateManager.getUnreadCount();
|
||||
const unreadCount = this.getUnreadCount();
|
||||
subscriber({
|
||||
type: 'unread_count_updated',
|
||||
payload: {
|
||||
@@ -426,14 +395,14 @@ class MessageManager {
|
||||
|
||||
subscriber({
|
||||
type: 'connection_changed',
|
||||
payload: { connected: this.stateManager.isConnected() },
|
||||
payload: { connected: this.isConnected() },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// 如果当前有活动会话,立即发送该会话的消息给新订阅者
|
||||
const activeConversationId = this.stateManager.getActiveConversation();
|
||||
const activeConversationId = this.getActiveConversation();
|
||||
if (activeConversationId) {
|
||||
const currentMessages = this.stateManager.getMessages(activeConversationId);
|
||||
const currentMessages = this.getMessages(activeConversationId);
|
||||
if (currentMessages.length > 0) {
|
||||
subscriber({
|
||||
type: 'messages_updated',
|
||||
@@ -451,7 +420,7 @@ class MessageManager {
|
||||
}
|
||||
|
||||
subscribeToMessages(conversationId: string, callback: (messages: MessageResponse[]) => void): () => void {
|
||||
return this.stateManager.subscribe((event) => {
|
||||
return subscriptionManager.subscribe((event) => {
|
||||
if (event.type === 'messages_updated' && event.payload.conversationId === conversationId) {
|
||||
callback(event.payload.messages);
|
||||
}
|
||||
@@ -484,7 +453,7 @@ class MessageManager {
|
||||
}
|
||||
|
||||
private shouldDeferConversationRefresh(source: string, forceRefresh: boolean): boolean {
|
||||
if (!this.stateManager.getActiveConversation()) return false;
|
||||
if (!this.getActiveConversation()) return false;
|
||||
if (!forceRefresh) return false;
|
||||
return source === 'sse-reconnect' || source === 'prefetch' || source === 'hooks-initial-refresh';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user