deviceId 持久化与排障
多设备游标的前提是“同一安装稳定、跨安装不同”的 deviceId。它由各端 SDK 内部生成与持久化,不是新的公开 API(IMLib 仍 102 个语义 op,契约无 deviceId 操作);业务无需传参。本页列出各端实现、重置方式与排障。
各端实现
| 端 | 生成 | 持久化位置 | 说明 |
|---|---|---|---|
| Web | `crypto.randomUUID()`(不可用时随机十六进制) | localStorage["imkit:deviceId"] | 同 origin 共享;隐私模式/只读存储降级为内存值 |
| Electron | `randomUUID()` | <dbPath>/im-electron/device.json(含 deviceId/createdAt) | 主进程持有;与硬件无关 |
| Android | `android-<32hex>` | ImStore KV:rev25.device_id | 超长/空白视为损坏并重生成 |
| Flutter | `flutter-<32hex>` | DeviceIdStorage:device_id:<appkey> | 按 appkey 隔离;读写失败不阻断建连 |
四端共同规则:trim 后 ≤128 字符;断线重连/重新 init 复用;disconnect() 不清除;只有清缓存/卸载/换设备才会生成新值。
公开 API 边界
| 项 | 结论 |
|---|---|
| IMLib 契约 | 无新增 op:仍是 102 个语义 op;无 getDeviceId/setDeviceId 契约方法 |
| 业务传参 | 不需要传 deviceId;SDK 自动生成并追加到 WS query |
| Electron 诊断辅助 | 主进程客户端/工厂额外提供只读 getDeviceId()(排障用,不属于契约 API) |
| Web/Android/Flutter | 不对外暴露 getter;排障看本地存储键 |
| 服务端 | 只把 deviceId 当游标维度;不提供设备列表/下线接口 |
何时会变化
- 清除浏览器站点数据 / 换浏览器 profile / 无痕窗口(Web 每次新会话都可能新 deviceId)。
- 卸载重装、清除应用数据、迁移到新设备(Android/Flutter/Electron)。
- 本地存储损坏/超长(>128)被 SDK 重新生成。
- Electron 更换
dbPath(数据库目录迁移会被视为新设备)。
变化的影响
deviceId 变化 = 新设备:游标从 0 开始,首次连接会补收全部未 ack 消息(每会话 ≤500 条)。这不是消息重复,而是新设备缺少确认记录。
排障
| 现象 | 排查 | 处理 |
|---|---|---|
| 每次启动都收到全量补收 | 检查本地存储键是否存在/被清 | 保留站点数据/应用数据;Electron 确认 dbPath 稳定 |
| 握手 400(deviceId 超长) | 自定义连接是否手工传了超长值 | 交给 SDK 生成;或截断到 ≤128 |
| hello.deviceId 与服务端日志不一致 | SDK 回显校验告警 | 确认 SDK/服务端均为 rev25;不一致不影响连接但游标可能错位 |
| 同一账号两端共享游标 | 两端是否复制了同一存储(镜像/克隆 profile) | 确保每安装独立存储;不要手工指定 deviceId |
| 多端未读不同步 | 未读是用户级(已知限制) | 以会话列表/未读接口为准,不要期望端级未读 |
| Web 无痕模式每次都补收 | localStorage 不持久 | 预期行为;提示用户或改用普通窗口 |