多设备补收与排障
离线补收是“hello 之后、sync_done 之前”的一段:服务端按当前连接设备的游标补发未 ack 消息。多设备下最常见的问题不是“没补”,而是“补错设备/重复补/漏补”。本页给出时序、判定方法与排障表。
补收时序
- 建立连接
SDK 在 WS query 带 deviceId → 服务端校验 → 握手成功。
- hello
收到 hello:含
deviceId(回显)与syncState(该设备每个会话的 deliveredSeq)。SDK 应以此判断“从哪里继续”。 - 补发消息
服务端按
seq > deliveredSeq逐条推送(排除本人发送、无可见性、已清理消息),客户端持久化后回 ack。 - sync_done
补发结束信号;此时才可宣布“同步完成”。UI 在 CONNECTED 后、sync_done 前应显示“同步中”。
- 增量阶段
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 / 1001 | deviceId 超过 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 调试日志