# 基线卡片 04:报警/工单实时同步(工单状态增量帧 + 设备帧 eventId 透传) > 按决策票 [05 真实联调基线与收口证据门槛](../../issues/05-integration-baseline-and-evidence.md) 的固定骨架产出,对应执行票 [13 报警/工单实时同步](../../issues/13-backend-realtime-sync.md)。**状态判定:③ 真实数据联调完成**(2026-10-09 部署后复验 6 项全通过,含真人触发状态变化的端到端帧捕获;④ 模式整体收口仍待真人验收与前端接线)。 ## 一、基线 | 项 | 值 | | --- | --- | | 记录时间 | 2026-10-09(本地时钟) | | 记录角色 | 后端执行代理(实施 + 编译 + 契约测试 + 部署后复验) | | 联调环境 baseUrl | `http://192.168.110.96:8301`(= 本机,IDE 内运行的 Spring Boot 进程) | | 后端仓库 / 分支 / 提交 | `http://192.168.110.30:3000/shaoy/pipe-ner-server` / `master` / **`d8a6b03`**(已推送 `4ef2159..d8a6b03`) | | 运行实例 | PID 20240,启动于 2026-10-09 13:40:58(**已加载新构建**) | | 访问控制 | 驾驶舱只读 REST/WS 免鉴权(ADR 0005);本票不改鉴权范围 | | 只读边界 | 未触碰工单创建/派单/接单/处置/验收/延期/取消逻辑(决策 01 非目标) | ## 二、收口条件五行 | # | 条件 | 状态 | 证据 | 未完成项 | | --- | --- | --- | --- | --- | | ① | 后端提交 / 编译 / 契约测试 | **已完成** | 编译通过;针对性契约测试 **21/21**;全量 8 模块 **422/422**;`d8a6b03` 已推送 | 无 | | ② | REST 快照复用 | **已完成(无新端点)** | 票 11/12 交付的读模型即快照源:`/events/current`、`/equipments/{code}/work-orders`、`/events/{source}/{code}/work-orders`、`/work-orders/{code}/timeline` | 无 | | ③ | WS 增量帧 | **已完成(部署后实测)** | 真人操作 284 工单(3→4)→ **40 秒内 `cockpitWorkOrderStateDelta` 帧到达**,字段全对;无变化/不改状态的操作均正确不推送 | 设备帧 `eventId` 线上携带值待外部发布事件(契约已锁定) | | ④ | 诚实降级 | **已完成(契约层 + 实测)** | 帧带 `scanIntervalMs` 标注延迟上限(实测延迟 40s < 60s 上限);无订阅不查库;断线重订、失效会话清理有测试 | 前端消费侧呈现归票 16 | | ⑤ | 真实设备报警闭环 | **不归本票** | 按决策 01 由真人团队执行 | 阶段总收口(票 18 上位阶段) | ## 三、本票交付内容 ### 新增:`cockpitWorkOrderStateDelta` 工单状态增量帧 | 项 | 决策 | 依据 | | --- | --- | --- | | 产生机制 | 定时扫描 `work_order.order_status` **逐单比对**(默认 60s,`websocket.cockpit-work-order-delta.scan-interval-ms` 可配) | 状态流转不写 `update_time`(`transition()` 只 set orderStatus、无 FieldFill),靠时间过滤会漏掉全部流转——诊断实测核实 | | 推送对象 | 驾驶舱全量订阅会话(免鉴权 scope=all),**不做按人可见性过滤** | 与管理端 `workOrderReminderPush`(需鉴权+按人过滤)明确区分:内容是**状态增量**,不是提醒清单 | | 首扫语义 | **只建基线不推送** | REST 才是初始快照(与 `cockpitSubscribe` 既有语义一致);WS 只收后续变化 | | 变化判定 | orderStatus 与上次扫描不同,或工单新出现 | 新工单也是状态变化(0-待派单入场) | | 帧形状 | `type/scanIntervalMs/serverUpdatedAt/data{items[{workOrderCode,deviceCode,stateCode,stateName}],total}` | 对外键 `workOrderCode`(order_no),**不泄漏数据库主键 orderId**(与 REST 读模型同口径) | | 诚实边界 | 帧不带工单 `updateTime`(流转不更新该列,携带会误导);变化时刻=帧级 `serverUpdatedAt`(扫描时间);脏状态码原样上报、`stateName` 显式 null | 不给消费方看起来可信实则不更新的时间;不编造名称 | | 状态字典 | `stateCode/stateName` 出自 `CockpitWorkOrderStateDictionary`(0–7 单源,票 11) | 与 REST 读模型完全一致 | | 降级与重连 | 无全量订阅会话时**不查库**;发送失败的失效会话被清出订阅集合;错过的增量由消费方 REST 重取兜底 | 先看会话再扫表(同 `WorkOrderWebSocketPushTask` 纪律);断线后重订即可 | ### 修改:`cockpitDeviceStatusDelta` 补 `eventId` 透传 Redis 消息(`device:status:change`)携带 `eventId` 时透传进驾驶舱帧,缺省**显式 null**(WriteMapNullValue 序列化,不静默丢)——前端收到 `status=ALARM` 增量后可精确重取该报警详情,而非盲刷列表。是否携带由发布方(zk-api-service)决定,后端不臆造。 ### 明确的语义隔离(决策 01 红线) `cockpitDeviceStatusDelta`(设备在线/报警状态)与 `cockpitWorkOrderStateDelta`(工单状态)**帧类型不同、语义不同**,文档明言不得互为冒充;REST 快照与 WS 增量的分工(快照=REST、增量=WS)在两帧上一致。 ## 四、实测记录(可复制命令与结果) ```powershell cd E:\pipe-ner-server mvn -o -pl pipe-network-service/zksy-admin -am test "-Dtest=CockpitWorkOrderDeltaPushTaskTest,DeviceStatusCockpitWebSocketHandlerTest" "-DfailIfNoTests=false" mvn -o -pl pipe-network-service/zksy-admin -am test # 全量 ``` 结果:针对性 **21/21**(新增 8 例);全量 8 模块 **422/422**(基线 414 + 8)。 新增契约覆盖(`CockpitWorkOrderDeltaPushTaskTest` 7 例 + 监听器 1 例): - `firstScanEstablishesBaselineWithoutPush` —— REST 才是快照,首扫零推送 - `stateChangePushesDeltaWithDictionaryStateNames` —— 变化推送 + 字典名称 + `scanIntervalMs` + 不泄漏 orderId - `newlyAppearingOrderIsPushedAsDelta` —— 新工单入场即增量;无设备关联时 `deviceCode` 显式 null - `unchangedOrdersProduceNoSecondPush` —— 无变化不推 - `unknownStateCodeKeepsRawCodeWithExplicitNullName` —— 脏码原样、名称 null - `noCockpitSessionMeansNoDatabaseScan` —— 无订阅不查库(降级纪律) - `staleAllScopeSessionIsPrunedWhenSendFails` —— 失效会话清理(重连语义) - `cockpitDeltaCarriesEventIdWhenPresentAndExplicitNullWhenAbsent` —— eventId 透传与显式 null ### 4.2 部署后真实环境复验(2026-10-09,实例 PID 20240 @ 13:41:14 加载 `d8a6b03`) ```powershell # WS 直订(System.Net.WebSockets.ClientWebSocket,PowerShell) $ws.ConnectAsync([Uri]'ws://192.168.110.96:8301/ws/deviceStatus', …) → {"type":"cockpitSubscribe","scope":"all"} ← {"type":"cockpitSubscribed",…} # 保持订阅监听帧;同时用只读 JDBC 探针快照 work_order.order_status 对照 ``` 结果(6 项全通过): | 复验项 | 实测 | | --- | --- | | 新构建加载 | PID 20240 启动 13:41:14 > 提交;日志无调度任务异常 | | WS 直订回执 | `cockpitSubscribed`(13:49:38 与 13:52:23 两次) | | 无变化不推送 | 订阅 75 秒零增量帧;复查库确认 7 张工单零变化(13:48:56 vs 13:50:53) | | 不改状态的操作不触发 | 13:55:47 真人「处置」284(仅 update_time 变、状态码仍 3)→ **无帧**(检测由 orderStatus 驱动的正面证明) | | **真人状态变化 → 端到端帧** | 13:57:54 提交验收(3→4)→ **13:58:34 帧到达**(延迟 40s < scanIntervalMs=60000):
`{"type":"cockpitWorkOrderStateDelta","scanIntervalMs":60000,"serverUpdatedAt":"2026-10-09T13:58:34+08:00","data":{"total":1,"items":[{"workOrderCode":"WO202610091014453765","stateCode":4,"stateName":"待验收","deviceCode":"TEST-CBYL5432168"}]}}`
帧内无 `orderId`/`updateTime`;items 只含变化那一张 | | 设备帧 `eventId` | 契约与显式 null 已由测试锁定;线上携带值待外部发布事件(窗口内无设备状态发布) | ## 五、真人验收 **闭环演示未执行**(归决策 01 的真人团队阶段总收口)。但本票复验包含一次**真人真实操作**:维护者于 13:57:54 在管理端对工单 284 提交验收(状态 3→4),驾驶舱侧 40 秒内收到增量帧——这是真实业务路径的端到端证据,非模拟。 ## 六、未完成项与补齐条件 1. ~~部署后复验~~ —— **已完成**(2026-10-09,6 项全通过,见 §4.2;记录同步至 [票 17](../../issues/17-postdeploy-live-verification.md))。 2. **前端消费侧**(归 [票 16](../../issues/16-frontend-alarm-order-consumption.md)):消费 `cockpitWorkOrderStateDelta`(按 `workOrderCode` 合并、`scanIntervalMs` 标注延迟);`cockpitDeviceStatusDelta.eventId` 用于精确重取报警。前端对未知帧静默忽略,**不升级前端也不会崩**。 3. `eventId` 是否真的由 zk-api-service 发布**未证实**——透传是防御性的;复验窗口内无设备状态发布事件,线上携带值待外部发布时观察(契约与显式 null 已由测试锁定)。 4. **决策 01 真人闭环演示**(报警产生→关联生成工单→推进→驾驶舱呈现全链路):归阶段总收口,本票已交付其驾驶舱侧全部依赖。 ## 七、状态判定 **当前状态:③ 真实数据联调完成(2026-10-09 达成)。**(④ 模式整体收口仍待真人验收与前端接线。) 依据:编译 + 422/422 全量测试 + 契约测试通过,且**部署后真实环境复验 6 项全通过**——含端到端正面证据(真人操作 3→4 后 40 秒帧到达、字段全对)与两项负向证据(无变化不推送、不改状态码的操作不触发,证明增量由 `orderStatus` 驱动)。 **不进入 ④ 的原因**:④ 要求模式整体收口(真人验收 + 前端接线 + 阶段联调)——本卡片只覆盖后端切片 3;前端 real 生命周期接线(票 14–16)与真人设备报警闭环(决策 01)尚未发生。 ## 八、关联 - 本图执行票:[13 报警/工单实时同步](../../issues/13-backend-realtime-sync.md) - 复验承载:[17 部署后真实环境复验(后端切片 11–13)](../../issues/17-postdeploy-live-verification.md) - 上游决策:[01 报警—工单闭环读模型与 Gogs 票拆分边界](../../issues/01-backend-slice-ticket-boundary.md)、[04 real 生命周期与 mock 同构启动契约](../../issues/04-real-lifecycle-contract.md) - 规格:[09 real 生命周期规格](../../issues/09-real-lifecycle-spec.md) - 下游:前端执行票 [16 报警–工单闭环消费](../../issues/16-frontend-alarm-order-consumption.md) - 上位台账:[票 25 后端契约缺口待办](../../../cockpit-shell-panel-layout/issues/25-backend-contract-gaps.md)、Gogs #63