多设备补收与排障

离线补收是“hello 之后、sync_done 之前”的一段:服务端按当前连接设备的游标补发未 ack 消息。多设备下最常见的问题不是“没补”,而是“补错设备/重复补/漏补”。本页给出时序、判定方法与排障表。

补收时序​

  1. 建立连接

    SDK 在 WS query 带 deviceId → 服务端校验 → 握手成功。

  2. hello

    收到 hello:含 deviceId(回显)与 syncState(该设备每个会话的 deliveredSeq)。SDK 应以此判断“从哪里继续”。

  3. 补发消息

    服务端按 seq > deliveredSeq 逐条推送(排除本人发送、无可见性、已清理消息),客户端持久化后回 ack。

  4. sync_done

    补发结束信号;此时才可宣布“同步完成”。UI 在 CONNECTED 后、sync_done 前应显示“同步中”。

  5. 增量阶段

    sync_done 后进入实时投递;ack 继续推进当前设备游标。

游标判定速查​

问题看哪里判定
这台设备补收到哪里?hello.syncState.conversations[].deliveredSeq每个会话一个值;设备首次连接为 0
为什么 A ack 后 B 还收到?A/B 是两个 deviceId预期行为:A ack 只推进 A 的设备游标
为什么重连后重复收到?是否复用同一 deviceId、是否 ack 成功新 deviceId = 新设备(全量);同 deviceId 重复 = ack 未达/乱序跳跃
为什么只补了 500 条?offlineMaxPerConversation每会话上限 500,其余用历史分页拉取
旧客户端为何行为不变?未携带 deviceId走 legacy 用户级游标(升级前语义)
read 后为何不补了?read 推进设备游标到会话最新 seq已读即视为已同步;其他设备仍按各自游标

常见故障与处理​

现象可能原因处理
握手 400 / 1001deviceId 超过 128 字符不要手工传超长值;SDK 已截断/校验
hello 没有 deviceId该连接未带 deviceId(旧客户端/自定义连接)或服务端未升级升级服务端/SDK;旧端继续 legacy 语义
A 端已读,B 端未读变 0未读是用户级(已知限制)B 端以会话列表刷新为准;不要依赖端级未读
B 端重复收到同一批消息B 未 ack 或 ack 失败;或 B 的 deviceId 变化检查 ack 日志;确认 deviceId 持久化未被清除
B 端漏收部分消息超过每会话 500 条补收上限;或乱序 ack 跳跃历史分页补齐;按 seq 顺序 ack
新设备一次性收到大量消息新 deviceId 游标=0预期行为;首屏先拉会话列表/历史,或分批渲染
重连后 sync_done 迟迟不到补发量大/网络慢以 sync_done 为完成信号;可延长“同步中”提示

验证脚本(Web SDK 视角)​

JavaScript
import { createClient, Events } from '@ni-paas/im-web';

const a = createClient({ appkey, serverUrl });
const b = createClient({ appkey, serverUrl }); // 同一浏览器会复用同一 deviceId,需在独立 profile/设备验证

// 观察补收完成:CONNECTED 只代表连接建立,sync_done 才代表补收结束
a.addEventListener(Events.MESSAGES, ({ messages }) => {
  for (const m of messages) console.log("[A]", m.messageId, m.seq);
});

// ack 由 SDK 在消息持久化后自动发送;如需手动 ack/排障可查看 SDK 调试日志

相关链接​