fix(message): improve message synchronization and reliability
Some checks failed
Frontend CI / ota-android (push) Successful in 1m47s
Frontend CI / ota-ios (push) Successful in 1m49s
Frontend CI / build-and-push-web (push) Failing after 13m31s
Frontend CI / build-android-apk (push) Successful in 29m15s

Refactor the messaging subsystem to enhance data consistency, prevent race conditions during WebSocket reconnection, and ensure accurate unread counts.

- **WebSocket Reliability**: Implement `client_msg_id` for precise message ACK matching, preventing incorrect pending message resolution in high-frequency scenarios.
- **Sync Logic**: Update `WSMessageHandler` and `MessageManager` to use a bootstrapping state, ensuring buffered SSE events are flushed only after the initial synchronization is complete.
- **State Consistency**:
    - Introduce atomic unread count increments to prevent lost updates.
    - Implement conditional rollback for optimistic read receipts, ensuring that failed API calls do not overwrite newer, valid read states.
- **Resource Management**: Add reference counting to `useMessages` hook to prevent premature clearing of loading states during rapid conversation switching or React StrictMode double-invocations.
- **Data Integrity**: Update `UserCacheService` to re-read the latest message list before applying sender info enrichment, ensuring new messages arriving during the async process are correctly processed.
This commit is contained in:
2026-06-06 13:10:08 +08:00
parent 1f7e25349f
commit 5c81795d39
8 changed files with 195 additions and 47 deletions

View File

@@ -88,6 +88,10 @@ export interface MessageActions {
removeMessage: (conversationId: string, messageId: string) => void;
patchMessages: (conversationId: string, patches: Map<string, Partial<MessageResponse>>) => void;
setUnreadCount: (total: number, system: number) => void;
/**
* 原子递增系统未读数:避免外部 RMW 造成丢失
*/
incrementSystemUnreadAtomic: () => void;
setLastSystemMessageAt: (time: string | null) => void;
setSSEConnected: (connected: boolean) => void;
setCurrentConversation: (conversationId: string | null) => void;
@@ -107,6 +111,16 @@ export interface MessageActions {
) => void;
incrementUnreadAtomic: (conversationId: string) => void;
/**
* 原子回滚乐观已读:仅在当前会话的 my_last_read_seq 仍等于 expectedReadSeq 时才回滚
* 避免覆盖后续并发 markAsRead 已更新的更大值
*/
rollbackReadIfUnchanged: (
conversationId: string,
expectedReadSeq: number,
prevUnreadCount: number,
) => void;
// 重置
reset: () => void;
}
@@ -393,6 +407,13 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
set({ totalUnreadCount: total, systemUnreadCount: system });
},
incrementSystemUnreadAtomic: () => {
set(state => ({
totalUnreadCount: state.totalUnreadCount + 1,
systemUnreadCount: state.systemUnreadCount + 1,
}));
},
setLastSystemMessageAt: (time: string | null) => {
set({ lastSystemMessageAt: time });
if (time) {
@@ -506,6 +527,36 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
});
},
rollbackReadIfUnchanged: (
conversationId: string,
expectedReadSeq: number,
prevUnreadCount: number,
) => {
const normalizedId = normalizeConversationId(conversationId);
set(state => {
const conversation = state.conversations.get(normalizedId);
if (!conversation) return state;
// 仅当本地乐观已读游标仍是本次写入的值时,才回滚未读
// 否则说明已被更新的 markAsRead 覆盖,不应回滚
if (Number(conversation.my_last_read_seq || 0) !== expectedReadSeq) {
return state;
}
const newConversations = new Map(state.conversations);
newConversations.set(normalizedId, {
...conversation,
unread_count: prevUnreadCount,
});
const conversationList = sortConversationList(newConversations);
// 同步回滚 totalUnreadCount加回本次扣减的未读数
const unreadDelta = prevUnreadCount - (conversation.unread_count || 0);
return {
conversations: newConversations,
conversationList,
totalUnreadCount: state.totalUnreadCount + unreadDelta,
};
});
},
// ==================== 重置 ====================
reset: () => {