多设备同步必须由服务端统一裁决状态,客户端仅作状态投影;需用snowflake或数据库自增生成全局唯一messageid和严格递增sequence,按sequence插入、messageid去重,未读数和服务端servertimestamp为准,断网重连依赖心跳+ack+离线拉取。

多设备同步不是靠客户端“推数据”,而是靠服务端统一裁决状态。uni-app 本身不提供跨设备状态收敛能力,所有“同步失效”问题(比如手机已读、平板仍红点)都源于把每个 App 实例当成独立终端来处理。
服务端必须生成全局唯一 messageId 和严格递增 sequence
客户端用 Date.now() 或 Math.random() 生成 ID 是同步崩塌的起点——不同设备时间不同步、随机碰撞概率随消息量上升,服务端无法排序,前端渲染必然错乱。
- 必须用 Snowflake 算法或数据库自增 + 业务前缀(如
"msg_123456789")生成messageId - 每条消息带一个服务端生成的
sequence字段,类型为整型,同一会话内严格递增 - 前端收到消息后,不能直接
push到列表;要先按sequence插入,再用messageId去重(查本地数据库是否存在该 ID) - 排序最终以服务端返回的
serverTimestamp为准,而非客户端createdAt
unreadCount 必须每次进入会话页时从服务端拉取
Vuex 或 localStorage 里存一个累加的 unreadCount 变量,在多端场景下毫无意义。用户在另一台设备点开聊天页的瞬间,这个数字就已经过期了。
- 进入会话页前,必须调用
GET /api/conversation/:id/unread获取真实未读数 - 标记已读操作不能只调
uniIm.conversation.read(),必须紧接着发POST /api/conversation/:id/read,带上当前最新messageId - 服务端收到后更新
last_read_message_id字段,并广播read_ack事件,各端监听后刷新本地角标 -
uni.setTabBarBadge前务必查一次服务端汇总未读数,别信本地缓存
断网重连后消息乱序?关键在心跳 + ACK + 离线兜底
iOS 后台几分钟内 WebSocket 就会被系统 kill,Android 也不稳定。单纯依赖连接在线是不可靠的,必须主动管理消息生命周期。
- 前端每 30 秒发一次
{"type":"ping"},服务端回{"type":"pong"} - 每条消息发出后启动定时器等待服务端
ACK;超时未收则重发,最多 2 次 - App 从后台切回前台时,立即调用
GET /api/messages/since?last_seq=xxx拉取断线期间消息 - 收消息必须统一走
uniIm.on('message', handler),不要混用原生socket.on('message'),否则绕过内置去重逻辑
真正难的不是让消息“传过去”,而是让所有设备共享同一个「会话意识」。服务端必须是唯一真相源,前端只是状态投影。任何绕过服务端做本地状态计算的逻辑,都会在多端场景下崩塌——这不是 Bug,是架构误判。











