实时订阅
先查后订,靠序号续传,不丢不重;连接是要占计算分钟的,所以只给有人在看的界面。

本课目标
读完这一课,你将能够
- 说出订阅解决什么问题,什么时候不该订阅
- 用「先查后订」和序号续传做到不丢不重
- 用
live()做一个实时视图,用watch()只收变化
为什么要订阅
看板要跟着数据走。有两种笨办法:每隔几秒重新查一次,大多数时候什么都没变,白白占着资源;或者干脆不更新,用户看到的是旧数据。订阅反过来:连接一直开着,数据一变,服务端就把变化推给你。
订阅有几件事在任何系统里都一样。你订的是一个对象集:对象加入、属性变化、被删除,你都会收到;服务端给不出增量时,会叫你重新加载整个集合;连接会断,所以客户端必须能重连。
官方 OSDK 通过 v2 WebSocket 订阅。AIDC 浏览器 SDK 仍用 SSE,也就是服务端推送的 HTTP 连接。本节的 live() 与 watch() 是兼容写法。SSE 用递增序号续传:断线后带上最后收到的序号,服务端从那里接着推。
- 01先查取一页当前值,同时拿到序号
seq - 02再订从
seq开始,中间的变化不会漏 - 03推送每次变化推来一条:哪个对象、什么操作、新值
- 04断线记着最后收到的序号
- 05续传带序号回来,不丢不重
live() 与 watch()
语义 SDK 和数据 SDK 各给一个入口。live() 给你一个一直保持最新的对象集,watch() 更底层,只给变化本身。
| live():一直最新的全集 | watch():只要变化 | |
|---|---|---|
| 你得到什么 | 先是全量,之后每次变化都给你最新的全集 | 只有变化:一条一条,带序号 |
| 适合 | 看板、列表、大屏:拿到就渲染 | 自己维护状态、写日志、触发别的动作 |
| 断线与落后 | 自动按序号续上,连接状态由 onStatus 告诉你 | 同样按序号续传;落后太多时收到 onReset,要重新查询 |
import { semantic, data } from "/developer/sdk/v1/aidc.js";
// 车间大屏:先是全量,之后每次变化回调一次最新的全集
const view = semantic.objects("workOrder").where({ status: "running" }).sort("-dueAt").live({
onUpdate(objects, change) { render(objects); },
onStatus({ connection }) { badge(connection); }, // connecting / live / reconnecting / paused / closed
});
// 页面离开时:view.close();
// 只要变化:先查,再从查到的序号订阅
const page = await data.table("workOrder").list({ where: { status: "running" }, limit: 50 });
const sub = data.watch({ types: ["workOrder"], after: page.seq }, {
onChange(c) { /* { seq, type, pk, op: "upsert" | "delete", props, origin: "sync" | "action" | "edit" | "import", at } */ },
onReset(seq) { /* 落后太多:重新查询 */ },
});
每条变化带着 origin:sync 是数据源同步,action 是 Action,edit 是直接修改,import 是批量导入。看板想突出「刚被人改过」的对象,看它就行。应用只会收到它看得见、并且订阅了的类型。
订阅不是备份。落后太多时你会收到 onReset,这时不要指望逐条补齐,重新查询一次就好。
连接是要占计算分钟的:什么时候该用
一条挂着的连接不是没有成本的:页面隐藏超过 1 分钟,连接自动断开(状态变成 paused),切回来带着序号续上;挂着的时间按时长计入应用的计算分钟,缺省每月 2000。所以订阅只给有人正在看的界面。
| 有人盯着屏幕 | 没人看也要反应 | |
|---|---|---|
| 用什么 | live() 或 watch() | 工作流的 change 触发器 |
| 连接 | 页面开着才有,隐藏超过 1 分钟自动断开 | 没有长连接:数据一变才触发,每条变化只触发一次 |
| 示例工厂 | 车间大屏:某条产线正在运行的工单 | 检验结果为 fail:建缺陷、暂停工单、通知质量负责人 |
隔一段时间才要做的事,比如每天早上提醒可能延期的订单,用工作流的 schedule 触发器,不用订阅。设计时先问一句:是不是数据一变才需要做?是,就用 change;只有人在看的时候才需要,才用订阅。
- 先订后查会漏掉中间的变化:永远先查,再从查到的序号订阅。
op: "delete"不是错误:它表示这个对象没了,界面上把它去掉。- 忘了
close():页面卸载后连接还挂着,一直占计算分钟。 - 收到
onReset就重新查询,不要硬接后面的变化。
要点
- 订阅是连接不断、数据一变就推送;断线按序号续传,不丢不重。
- 永远先查、再从查到的序号订阅;落后太多(
onReset)就重新查询。 live()给一个一直最新的对象集,watch()只给变化,每条带origin。- 连接挂着按时长计入计算分钟,页面隐藏超过 1 分钟自动断开;订阅只给有人在看的界面。
- 没人看也要反应,用工作流的
change触发器;隔一段时间才做,用schedule。
练一练
为车间大屏设计订阅
用示例工厂的工单练习,前两项写在纸上,第三项需要一个终端。
写出大屏的订阅顺序:先做什么,序号从哪里来,断线之后怎么办,收到 onReset 怎么办。
下面四件事各用 live()、watch()、change 触发器还是 schedule?① 大屏显示运行中的工单;② 设备振动超阈值就建维保工单;③ 每天早上提醒可能延期的订单;④ 把每次工单变化追加到审计文件。
开两个终端:一个运行 aidc data watch --types workOrder,另一个执行一次 Action,看变化里的 origin 是不是 action;再用最后收到的序号加 --after 重连。
小测
选一个答案,马上看解析。
Q1为什么要「先查、再从查到的序号订阅」?
查询返回的序号标出「到这里为止已经看到了」,订阅从它开始,中间的变化就不会漏。
Q2设备振动超阈值要自动建维保工单,半夜没人看任何页面。该怎么做?
没人看也要反应,用 change:数据一变才触发,没有长连接。schedule 最短也要 5 分钟,每分钟是拒收的。
Q3一个订阅的连接对应用有什么成本?
连接是按挂着的时长计入计算分钟的,所以别让它无人看时还挂着。