feat(message): implement version-based incremental sync and WS compression
Implement a more efficient conversation synchronization mechanism using version numbers instead of sequence numbers to reduce bandwidth usage. This includes adding support for Gzip decompression on WebSocket messages to optimize data transfer. - Add `pako` for WebSocket message decompression - Implement `getSyncByVersion` in `MessageService` - Implement `syncByVersion` in `MessageSyncService` to handle incremental updates and conversation state changes - Update `WebSocketService` to support binary frames and Gzip inflation - Add `syncVersion` to `MessageStore` for tracking synchronization state
This commit is contained in:
@@ -466,6 +466,65 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于版本号的增量同步(替代 syncBySeq,减少带宽)
|
||||
* 需要本地存储 syncVersion,首次同步时 version=0 会触发 full_sync
|
||||
*/
|
||||
async syncByVersion(): Promise<boolean> {
|
||||
try {
|
||||
const store = useMessageStore.getState();
|
||||
const version = store.syncVersion ?? 0;
|
||||
|
||||
const result = await messageService.getSyncByVersion(version, 100);
|
||||
if (!result) return false;
|
||||
|
||||
// full_sync 表示需要全量刷新
|
||||
if (result.full_sync) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.changes.length === 0) {
|
||||
// 保存当前版本号,下次增量同步时使用
|
||||
useMessageStore.getState().setSyncVersion(result.current_version);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 按变更类型处理
|
||||
const staleIds: string[] = [];
|
||||
for (const change of result.changes) {
|
||||
const normId = normalizeConversationId(change.conversation_id);
|
||||
if (change.change_type === 'hide') {
|
||||
// 用户隐藏了会话,从本地移除
|
||||
store.removeConversation(normId);
|
||||
} else {
|
||||
// new_msg, read, pin, unpin, mute, unmute, group_update
|
||||
staleIds.push(normId);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新有变更的会话
|
||||
if (staleIds.length > 0) {
|
||||
const detailPromises = staleIds.map(id =>
|
||||
this.fetchConversationDetail(id).catch(() => {})
|
||||
);
|
||||
await Promise.all(detailPromises);
|
||||
}
|
||||
|
||||
// 保存版本号
|
||||
useMessageStore.getState().setSyncVersion(result.current_version);
|
||||
|
||||
// has_more 时继续拉取
|
||||
if (result.has_more) {
|
||||
return this.syncByVersion();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[MessageSyncService] syncByVersion 失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可加载更多会话
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,9 @@ export interface MessageState {
|
||||
|
||||
// 会话免打扰状态 - 按会话ID存储
|
||||
notificationMutedMap: Map<string, boolean>;
|
||||
|
||||
// 版本日志同步游标
|
||||
syncVersion: number | null;
|
||||
}
|
||||
|
||||
// ==================== Actions 接口 ====================
|
||||
@@ -90,6 +93,7 @@ export interface MessageActions {
|
||||
setTypingUsers: (groupId: string, users: string[]) => void;
|
||||
setMutedStatus: (groupId: string, isMuted: boolean) => void;
|
||||
setNotificationMuted: (conversationId: string, notificationMuted: boolean) => void;
|
||||
setSyncVersion: (version: number) => void;
|
||||
setInitialized: (initialized: boolean) => void;
|
||||
|
||||
// 原子性更新(避免中间渲染状态)
|
||||
@@ -121,6 +125,7 @@ const initialState: MessageState = {
|
||||
typingUsersMap: new Map(),
|
||||
mutedStatusMap: new Map(),
|
||||
notificationMutedMap: new Map(),
|
||||
syncVersion: null,
|
||||
};
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
@@ -189,6 +194,7 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
typingUsersMap: state.typingUsersMap,
|
||||
mutedStatusMap: state.mutedStatusMap,
|
||||
notificationMutedMap: state.notificationMutedMap,
|
||||
syncVersion: state.syncVersion,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -387,6 +393,10 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
setSyncVersion: (version: number) => {
|
||||
set({ syncVersion: version });
|
||||
},
|
||||
|
||||
setInitialized: (initialized: boolean) => {
|
||||
set({ isInitialized: initialized });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user