refactor(message): simplify state management and remove redundant service methods
Refactor the message module to streamline state management by removing the `MessageStateManager` and `SubscriptionManager` layers, relying instead on Zustand's built-in selector mechanism for reactive updates. - Remove redundant `setConversations`, `addMessage`, and `updateMessage` actions from the Zustand store to favor more specialized update methods. - Clean up `MessageService` by removing deprecated conversation and message sending convenience methods. - Update service interfaces to support new synchronization capabilities including `syncBySeq` and `restartSources`. - Simplify documentation and comments across the message store and its associated services.
This commit is contained in:
@@ -94,13 +94,6 @@ class MessageService {
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshConversationsFromServer(
|
||||
page = 1,
|
||||
pageSize = 20
|
||||
): Promise<ConversationListResponse> {
|
||||
return this.requestOffsetConversationsPage(page, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 远端会话列表单页(offset / cursor 统一入口)
|
||||
* 两种模式均先写 SQLite 再返回,行为与 NetworkRemoteConversationListPagedSource 配套。
|
||||
@@ -175,50 +168,6 @@ class MessageService {
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 会话相关 ====================
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* GET /api/v1/conversations/list
|
||||
*/
|
||||
async getConversations(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
forceRefresh = false
|
||||
): Promise<ConversationListResponse> {
|
||||
// 如果强制刷新,直接从服务器获取,不使用缓存
|
||||
if (!forceRefresh) {
|
||||
const cachedList = await conversationRepository.getListCache();
|
||||
if (cachedList.length > 0) {
|
||||
// 后台刷新数据,但不阻塞当前返回
|
||||
this.refreshConversationsFromServer(page, pageSize).catch(error => {
|
||||
console.error('后台刷新会话列表失败:', error);
|
||||
});
|
||||
return {
|
||||
list: cachedList,
|
||||
total: cachedList.length,
|
||||
page: 1,
|
||||
page_size: cachedList.length,
|
||||
total_pages: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 强制刷新或没有缓存时,直接从服务器获取
|
||||
try {
|
||||
return await this.refreshConversationsFromServer(page, pageSize);
|
||||
} catch (error) {
|
||||
console.error('获取会话列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
total_pages: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建私聊会话
|
||||
* POST /api/v1/conversations
|
||||
@@ -428,98 +377,6 @@ class MessageService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本消息(便捷方法)
|
||||
*/
|
||||
async sendTextMessage(
|
||||
conversationId: string,
|
||||
content: string,
|
||||
detailType: 'private' | 'group' = 'private'
|
||||
): Promise<MessageResponse> {
|
||||
return this.sendTextMessageByAction(detailType, conversationId, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 HTTP POST 发送文本消息
|
||||
* @param detailType 消息类型: 'private' 私聊, 'group' 群聊
|
||||
* @param conversationId 会话ID
|
||||
* @param text 文本内容
|
||||
* @returns 消息响应
|
||||
*/
|
||||
async sendTextMessageByAction(
|
||||
detailType: 'private' | 'group',
|
||||
conversationId: string,
|
||||
text: string
|
||||
): Promise<MessageResponse> {
|
||||
const message: MessageSegment[] = [
|
||||
{
|
||||
type: 'text',
|
||||
data: {
|
||||
text: text,
|
||||
},
|
||||
},
|
||||
];
|
||||
return this.sendMessageByAction(detailType, conversationId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送图片消息(便捷方法)
|
||||
*/
|
||||
async sendImageMessage(
|
||||
conversationId: string,
|
||||
mediaUrl: string,
|
||||
content?: string,
|
||||
detailType: 'private' | 'group' = 'private'
|
||||
): Promise<MessageResponse> {
|
||||
const message: MessageSegment[] = [
|
||||
{
|
||||
type: 'image',
|
||||
data: {
|
||||
url: mediaUrl,
|
||||
},
|
||||
},
|
||||
];
|
||||
// 如果有文本说明,添加一个文本段
|
||||
if (content) {
|
||||
message.unshift({
|
||||
type: 'text',
|
||||
data: { text: content },
|
||||
});
|
||||
}
|
||||
return this.sendMessageByAction(detailType, conversationId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 segments 格式消息(新版统一格式)
|
||||
* @param conversationId 会话ID
|
||||
* @param segments 消息链数组
|
||||
* @param detailType 消息类型
|
||||
*/
|
||||
async sendSegmentsMessage(
|
||||
conversationId: string,
|
||||
segments: MessageSegment[],
|
||||
detailType: 'private' | 'group' = 'private'
|
||||
): Promise<MessageResponse> {
|
||||
return this.sendMessageByAction(detailType, conversationId, segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带回复的 segments 消息
|
||||
* @param conversationId 会话ID
|
||||
* @param segments 消息链数组
|
||||
* @param replyToId 被回复消息的ID
|
||||
* @param detailType 消息类型
|
||||
*/
|
||||
async sendReplySegmentsMessage(
|
||||
conversationId: string,
|
||||
segments: MessageSegment[],
|
||||
replyToId: string,
|
||||
detailType: 'private' | 'group' = 'private'
|
||||
): Promise<MessageResponse> {
|
||||
// 使用 reply_to_id 参数发送回复消息
|
||||
return this.sendMessageByAction(detailType, conversationId, segments, replyToId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回/删除消息
|
||||
* 优先使用WebSocket,失败时降级到HTTP
|
||||
@@ -619,68 +476,6 @@ class MessageService {
|
||||
return response.data?.conversations || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个会话未读数
|
||||
* GET /api/v1/conversations/unread/count?conversation_id=xxx
|
||||
*/
|
||||
async getConversationUnreadCount(
|
||||
conversationId: string
|
||||
): Promise<ConversationUnreadCountResponse> {
|
||||
try {
|
||||
const response = await api.get<ConversationUnreadCountResponse>(
|
||||
'/conversations/unread/count',
|
||||
{ conversation_id: conversationId }
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取会话未读数失败:', error);
|
||||
return {
|
||||
conversation_id: conversationId,
|
||||
unread_count: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 系统消息相关 ====================
|
||||
|
||||
/**
|
||||
* 获取系统消息列表
|
||||
* GET /api/v1/messages/system
|
||||
* @param limit 返回数量限制
|
||||
* @param beforeId 获取此ID之前的消息(用于分页)
|
||||
*/
|
||||
async getSystemMessages(
|
||||
pageSize: number = 20,
|
||||
page: number = 1
|
||||
): Promise<SystemMessageListResponse> {
|
||||
try {
|
||||
const response = await api.get<{
|
||||
list: any[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}>('/messages/system', {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
return {
|
||||
messages: data.list || [],
|
||||
total: data.total || 0,
|
||||
has_more: data.page < data.total_pages,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取系统消息列表失败:', error);
|
||||
return {
|
||||
messages: [],
|
||||
total: 0,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统消息未读数
|
||||
* GET /api/v1/messages/system/unread-count
|
||||
@@ -714,41 +509,6 @@ class MessageService {
|
||||
await api.put('/messages/system/read-all');
|
||||
}
|
||||
|
||||
// ==================== 游标分页方法 ====================
|
||||
|
||||
/**
|
||||
* 获取消息列表(游标分页)
|
||||
* GET /api/v1/conversations/:id/messages/cursor
|
||||
* @param conversationId 会话ID
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getMessagesCursor(
|
||||
conversationId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<MessageResponse>> {
|
||||
try {
|
||||
const response = await api.get<any>(
|
||||
`/conversations/${encodeURIComponent(conversationId)}/messages/cursor`,
|
||||
params
|
||||
);
|
||||
const raw = response.data;
|
||||
return {
|
||||
list: Array.isArray(raw?.list) ? raw.list : [],
|
||||
next_cursor: raw?.next_cursor ?? null,
|
||||
prev_cursor: raw?.prev_cursor ?? null,
|
||||
has_more: raw?.has_more ?? false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取消息列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统消息列表(游标分页)
|
||||
* GET /api/v1/messages/system/cursor
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
/**
|
||||
* MessageManager - 消息管理核心模块(重构版)
|
||||
*
|
||||
* MessageManager - 消息管理核心模块
|
||||
*
|
||||
* 作为轻量级协调者,整合各个专注的服务模块
|
||||
* 直接使用 zustand store 进行状态管理
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 MessageStateManager 包装层
|
||||
* - 直接使用 zustand store
|
||||
* - 服务层移至 services/ 目录
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||
*/
|
||||
|
||||
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* 消息模块 React Hooks
|
||||
*
|
||||
*
|
||||
* 提供React组件与消息状态管理的集成
|
||||
* 所有hooks都基于zustand store的selector机制
|
||||
* 确保组件能实时获取状态更新
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector
|
||||
* - Zustand 会自动处理依赖追踪和组件重渲染
|
||||
* - 不需要手动管理订阅/取消订阅
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
/**
|
||||
* 消息模块统一导出
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 MessageStateManager 包装层
|
||||
* - 直接使用 zustand store 进行状态管理
|
||||
* - 服务层移至 services/ 目录
|
||||
* - 新增 hooks.ts 封装常用逻辑
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||
*/
|
||||
|
||||
// ==================== 主管理器 ====================
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/**
|
||||
* 会话操作服务
|
||||
* 处理会话的 CRUD 操作
|
||||
*
|
||||
* 重构说明:
|
||||
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||
*/
|
||||
|
||||
import type { ConversationResponse } from '../../../types/dto';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/**
|
||||
* 消息发送服务
|
||||
* 处理消息发送相关逻辑
|
||||
*
|
||||
* 重构说明:
|
||||
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||
*/
|
||||
|
||||
import type { MessageResponse, MessageSegment, ConversationResponse } from '../../../types/dto';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/**
|
||||
* 消息同步服务
|
||||
* 处理消息和会话的服务器同步逻辑
|
||||
*
|
||||
* 重构说明:
|
||||
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||
*/
|
||||
|
||||
import type { ConversationResponse, MessageResponse } from '../../../types/dto';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/**
|
||||
* 已读回执管理器
|
||||
* 管理消息已读状态的同步和保护
|
||||
*
|
||||
* 重构说明:
|
||||
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||
*/
|
||||
|
||||
import type { ConversationResponse } from '../../../types/dto';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/**
|
||||
* WebSocket 消息处理器
|
||||
* 处理所有来自 WebSocket 的消息事件
|
||||
*
|
||||
* 重构说明:
|
||||
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||
*/
|
||||
|
||||
import type {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* 消息服务层统一导出
|
||||
*
|
||||
* 重构说明:所有服务直接使用 zustand store,移除了 IMessageStateManager 接口依赖
|
||||
*/
|
||||
|
||||
// 消息去重服务
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* 消息状态 Zustand Store
|
||||
* 使用 zustand 进行状态管理,提供响应式状态更新
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 MessageStateManager 包装层
|
||||
* - 直接使用 zustand store 进行状态管理
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
@@ -78,7 +73,6 @@ export interface MessageActions {
|
||||
isNotificationMuted: (conversationId: string) => boolean;
|
||||
|
||||
// 设置状态
|
||||
setConversations: (conversations: Map<string, ConversationResponse>) => void;
|
||||
setConversationsWithUnread: (
|
||||
conversations: Map<string, ConversationResponse>,
|
||||
totalUnread: number,
|
||||
@@ -87,8 +81,6 @@ export interface MessageActions {
|
||||
updateConversation: (conversation: ConversationResponse) => void;
|
||||
removeConversation: (conversationId: string) => void;
|
||||
setMessages: (conversationId: string, messages: MessageResponse[]) => void;
|
||||
addMessage: (conversationId: string, message: MessageResponse) => void;
|
||||
updateMessage: (conversationId: string, messageId: string, updates: Partial<MessageResponse>) => void;
|
||||
setUnreadCount: (total: number, system: number) => void;
|
||||
setLastSystemMessageAt: (time: string | null) => void;
|
||||
setSSEConnected: (connected: boolean) => void;
|
||||
@@ -253,21 +245,6 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
|
||||
// ==================== 设置状态 ====================
|
||||
|
||||
setConversations: (conversations: Map<string, ConversationResponse>) => {
|
||||
const conversationList = sortConversationList(conversations);
|
||||
const notificationMutedMap = new Map<string, boolean>();
|
||||
conversations.forEach((conv, id) => {
|
||||
if (conv.notification_muted) {
|
||||
notificationMutedMap.set(id, true);
|
||||
}
|
||||
});
|
||||
set(state => ({
|
||||
conversations,
|
||||
conversationList,
|
||||
notificationMutedMap: new Map([...state.notificationMutedMap, ...notificationMutedMap]),
|
||||
}));
|
||||
},
|
||||
|
||||
setConversationsWithUnread: (
|
||||
conversations: Map<string, ConversationResponse>,
|
||||
totalUnread: number,
|
||||
@@ -348,39 +325,6 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
addMessage: (conversationId: string, message: MessageResponse) => {
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
set(state => {
|
||||
const existing = state.messagesMap.get(normalizedId) || [];
|
||||
const exists = existing.some(m => String(m.id) === String(message.id));
|
||||
|
||||
if (exists) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const updated = mergeMessagesById(existing, [message]);
|
||||
const newMessagesMap = new Map(state.messagesMap);
|
||||
newMessagesMap.set(normalizedId, updated);
|
||||
return { messagesMap: newMessagesMap };
|
||||
});
|
||||
},
|
||||
|
||||
updateMessage: (conversationId: string, messageId: string, updates: Partial<MessageResponse>) => {
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
set(state => {
|
||||
const messages = state.messagesMap.get(normalizedId);
|
||||
if (!messages) return state;
|
||||
|
||||
const updated = messages.map(m =>
|
||||
String(m.id) === String(messageId) ? { ...m, ...updates } : m
|
||||
);
|
||||
|
||||
const newMessagesMap = new Map(state.messagesMap);
|
||||
newMessagesMap.set(normalizedId, updated);
|
||||
return { messagesMap: newMessagesMap };
|
||||
});
|
||||
},
|
||||
|
||||
setUnreadCount: (total: number, system: number) => {
|
||||
set({ totalUnreadCount: total, systemUnreadCount: system });
|
||||
},
|
||||
|
||||
@@ -73,7 +73,9 @@ export interface IMessageSyncService {
|
||||
fetchMessages(conversationId: string, afterSeq?: number): Promise<void>;
|
||||
loadMoreMessages(conversationId: string, beforeSeq: number, limit?: number): Promise<MessageResponse[]>;
|
||||
fetchUnreadCount(): Promise<void>;
|
||||
syncBySeq(): Promise<boolean>;
|
||||
canLoadMoreConversations(): boolean;
|
||||
restartSources(): void;
|
||||
}
|
||||
|
||||
export interface IWSMessageHandler {
|
||||
|
||||
Reference in New Issue
Block a user