refactor(message): implement atomic message merging and patching to prevent race conditions
Some checks failed
Frontend CI / ota-ios (push) Successful in 1m11s
Frontend CI / ota-android (push) Successful in 2m20s
Frontend CI / build-and-push-web (push) Successful in 3m55s
Frontend CI / build-android-apk (push) Failing after 9m29s

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:
2026-05-25 01:44:23 +08:00
parent 7a17323e8d
commit f4db4eb1ed
4 changed files with 86 additions and 89 deletions

View File

@@ -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 });
},