WebSocket 连接、订阅与推送
约 1235 字大约 4 分钟
2025-03-03
WebSocket 用于已登录客户端的实时通知。当前平台使用频道协议,连接认证、频道授权和消息分发是三个环节。旧文章的 up.sub、body.code/params 报文不适用于当前新客户端。
独立应用可以直接使用已有频道和 ur-kit 客户端,接入方法见应用 WS 与消息通知;本页侧重平台连接、频道与授权实现。
1. 建立连接
入口为 GET /api/v1/system/common/websocket/connect,完成 HTTP 认证后升级连接。反向代理须转发 Upgrade、认证上下文并设置合适的长连接超时。
浏览器原生 WebSocket 不能任意设置请求头。当前平台读取认证参数时也支持查询串,下面函数演示用户 token 连接;正式应用优先使用已有共享连接管理器,避免页面各开一条连接:
// 本文件演示在浏览器中创建平台用户 WebSocket。
/** 创建用户连接;参数来自已完成的登录和应用选择,不在日志中输出 URL。 */
function connectPlatform(base: string, token: string, appID: string, deviceID: string): WebSocket {
const url = new URL('/api/v1/system/common/websocket/connect', base);
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
url.searchParams.set('ithings-token', token);
url.searchParams.set('app-id', appID);
url.searchParams.set('device-id', deviceID);
return new WebSocket(url);
}生产环境使用 TLS,并在代理日志中隐藏查询串凭据。device-id 表示真实客户端实例,同一标识的新连接会替换旧连接;多个页面共享一条连接,不能不断抢连。
建立后接收:
{"type":"connected","ts":1792112400000,"data":{"connID":123}}connID 是连接标识,不能当作业务对象 ID 或授权凭证。
2. 订阅和取消
客户端发送:
{"id":1,"type":"subscribe","channel":"device.prop.DEMO.sensor1"}服务端以相同 id 返回 subscribe.ok 或 error。等确认成功后才把页面状态标记为已订阅,不能只以发送成功判断有权限。
{"id":2,"type":"unsubscribe","channel":"device.prop.DEMO.sensor1"}收到 unsubscribe.ok 后清理本地订阅。id 是用于关联响应的小整数,连接断开后旧请求应失败并清理,不能让重连后的响应误配给旧请求。
3. 当前频道
| 频道 | 业务内容 |
|---|---|
device.prop.<productID>.<deviceName> | 整包属性变化 |
device.prop.<productID>.<deviceName>.<identifier> | 单属性变化 |
device.event.<productID>.<deviceName>.<identifier> | 事件 |
device.action.<productID>.<deviceName>.<identifier> | 行为消息 |
device.conn.<productID>.<deviceName> | 上下线状态 |
device.ota.<productID>.<deviceName> | OTA 进度 |
device.raw.<productID>.<deviceName> | 原始消息 |
project.prop.<projectID> / project.conn.<projectID> | 项目聚合通知 |
area.prop.<projectID>.<areaID> | 区域属性聚合 |
user.notify.<userID> | 站内通知 |
sys.alarm.<tenantCode> | 企业告警 |
tenant.announce.<tenantCode> | 企业公告 |
频道构建应复用当前客户端/服务端辅助函数,避免手写时段数错误。解析限制长度和段数;通配符 * 匹配一段,末尾 > 匹配后续一段及以上。语法允许通配不代表用户有权订阅,授权提供者可以拒绝过宽订阅。
4. 接收推送与心跳
{
"type":"publish",
"channel":"device.prop.DEMO.sensor1",
"ts":1792112400123,
"data":{
"device":{"productID":"DEMO","deviceName":"sensor1"},
"timestamp":"1792112400000",
"receivedTime":1792112400123,
"params":{"temperature":25.5},
"quality":{"temperature":"GOOD"}
}
}外层 ts 是服务端消息时间,业务 timestamp 是采集时间,receivedTime 是平台接收时间。不能混用来画历史曲线。完整载荷见设备消息。
应用层 {"id":3,"type":"ping"} 对应 pong;另有 WebSocket 协议层 ping/pong,由运行库和浏览器处理。实现共享连接时统一管理心跳,页面卸载不留下 timer。
5. 平台扩展频道
新增命名空间时,为 Hub 注册 ChannelAuthProvider:声明 Name()、Namespaces() 和 Authorize(ctx, userCtx, channel),检查身份和目标资源;缺少授权不能以“频道名称难猜”替代。
业务发布通过统一 Publisher/Hub 链路,核对企业和用户范围。新增频道需同时约定 payload、取消订阅和失权行为,不能直接让前端订阅任意内部事件总线主题。
6. 重连与页面生命周期
- 断线后有上限地退避重连,恢复当前有效订阅。
- token 失效后先重新认证,不能无限高速重试。
- 企业/项目切换后清理旧订阅与缓存,忽略旧范围异步响应。
- 被同客户端新连接替换时延长重连间隔,避免互相踢下线。
- 重连后通过查询接口补齐快照/历史,WebSocket 本身不是完整历史存储。
验证与兼容
验证正常与过期认证、可读/只读/无权资源、越权通配、取消订阅、多标签页、断线重连、项目切换和慢消费者。以真实设备或本机模拟上报确认前端实际收到正确数据。
apilegacy 中可能仍有旧客户端协议适配,变更必须单独验证,不能把新频道报文直接替换进旧客户端而认为自动兼容。
更新日志
2026/10/16 20:08
查看所有更新日志
b1437-docs(应用开发): 补齐WS通知及平台能力接入导航于b3b4d-docs(平台开发): 恢复有效专题并更新当前实现示例于
