fix(message): improve message synchronization and reliability
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:
@@ -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: () => {
|
||||
|
||||
Reference in New Issue
Block a user