feat(message): implement version-based incremental sync and WS compression
Some checks failed
Frontend CI / ota-android (push) Successful in 1m58s
Frontend CI / ota-ios (push) Successful in 2m2s
Frontend CI / build-android-apk (push) Failing after 3m22s
Frontend CI / build-and-push-web (push) Successful in 4m19s

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:
2026-05-17 23:37:38 +08:00
parent 404b3fabe7
commit fb67fb6d5b
6 changed files with 132 additions and 4 deletions

View File

@@ -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;
}
}
/**
* 检查是否可加载更多会话
*/

View File

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