refactor(message): implement atomic message merging and patching to prevent race conditions
Introduce `mergeMessages` and `patchMessages` to the message store to handle updates atomically within Zustand's `set` callback. This replaces manual read-modify-write patterns in services, preventing message loss during concurrent WebSocket updates and synchronization processes. - Add `mergeMessages` for atomic merging of new messages into existing lists - Add `patchMessages` for efficient field updates (e.g., sender info, status) - Update `MessageSendService`, `MessageSyncService`, and `WSMessageHandler` to use these new atomic operations - Remove reliance on external `mergeMessagesById` in service layers to ensure state consistency
This commit is contained in:
@@ -84,6 +84,8 @@ export interface MessageActions {
|
||||
updateConversation: (conversation: ConversationResponse) => void;
|
||||
removeConversation: (conversationId: string) => void;
|
||||
setMessages: (conversationId: string, messages: MessageResponse[]) => void;
|
||||
mergeMessages: (conversationId: string, incoming: MessageResponse[]) => void;
|
||||
patchMessages: (conversationId: string, patches: Map<string, Partial<MessageResponse>>) => void;
|
||||
setUnreadCount: (total: number, system: number) => void;
|
||||
setLastSystemMessageAt: (time: string | null) => void;
|
||||
setSSEConnected: (connected: boolean) => void;
|
||||
@@ -331,6 +333,48 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 原子性合并消息到 messagesMap
|
||||
* 在 zustand set 回调内完成 read-merge-write,消除并发竞态
|
||||
*/
|
||||
mergeMessages: (conversationId: string, incoming: MessageResponse[]) => {
|
||||
if (incoming.length === 0) return;
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
set(state => {
|
||||
const existing = state.messagesMap.get(normalizedId) || [];
|
||||
const merged = mergeMessagesById(existing, incoming);
|
||||
const newMessagesMap = new Map(state.messagesMap);
|
||||
newMessagesMap.set(normalizedId, merged);
|
||||
return { messagesMap: newMessagesMap };
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 原子性 patch:按消息 ID 更新指定字段(如 sender、status)
|
||||
* 不影响其他消息,不丢失并发写入
|
||||
*/
|
||||
patchMessages: (conversationId: string, patches: Map<string, Partial<MessageResponse>>) => {
|
||||
if (patches.size === 0) return;
|
||||
const normalizedId = normalizeConversationId(conversationId);
|
||||
set(state => {
|
||||
const existing = state.messagesMap.get(normalizedId);
|
||||
if (!existing) return state;
|
||||
let changed = false;
|
||||
const updated = existing.map(m => {
|
||||
const patch = patches.get(String(m.id));
|
||||
if (patch) {
|
||||
changed = true;
|
||||
return { ...m, ...patch };
|
||||
}
|
||||
return m;
|
||||
});
|
||||
if (!changed) return state;
|
||||
const newMessagesMap = new Map(state.messagesMap);
|
||||
newMessagesMap.set(normalizedId, updated);
|
||||
return { messagesMap: newMessagesMap };
|
||||
});
|
||||
},
|
||||
|
||||
setUnreadCount: (total: number, system: number) => {
|
||||
set({ totalUnreadCount: total, systemUnreadCount: system });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user