iotsaas/docs/10寸中控屏/05-评审回复与落地方案.md
2995840376 0ce191c448 chore: 初始化 iotsaas 版本库基线
纳入范围:
- backend/  Spring Boot 多模块后端(iot-common/device/rule/data/alarm/auth)
- frontend/admin/  Vue3 + Element Plus 管理后台
- Android/   涂鸦原生 APP (com.xhai.iot)
- backend/sql/  Flyway 风格迁移脚本(V2~V21)
- docs/       对接文档与设计文档

排除范围:
- 构建产物: node_modules/ target/ build/ dist/ .gradle/
- 签名密钥: *.jks *.keystore (严禁入库)
- 本机配置: Android/local.properties
- 运行日志: logs/ *.log
- AI 协作本地数据 .codebuddy/ 与第三方 demo tuya-demo/

.gitattributes 统一以 LF 存储, Windows 批处理保留 CRLF
2026-10-27 12:00:41 +08:00

270 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 中控屏直连 MumIot 协议 · 平台方评审回复与落地方案
| 项目 | 内容 |
| --- | --- |
| 对应文档 | `04-中控屏直连MumIot对接协议-评审稿.html`(v0.1,2026-10-16) |
| 本稿版本 | v1.0(平台方评审回复) |
| 日期 | 2026-10-22 |
| 评审范围 | 协议 13 章 / 24 个接口(21 个新增建议 + 3 个扩展建议) |
| 评审方 | MumIot 平台 |
---
## 一、总体结论
**采纳本协议作为中控屏接入的主体框架。**
协议提出的"屏 = 本地执行网关 + 命令单据化 + 三重版本号(归属/物模型/状态)"设计,与平台演进方向一致。落地后不只中控屏受益,平台现有手机端 / 网页端也会同步补上四项长期欠账:
| 现状欠账 | 落地后改善 |
| --- | --- |
| 设备命令纯同步、无记录、不可追溯 | 命令单据化 + 状态机,全程可查 |
| 设备状态无版本号、`/latest` 是空壳 | 属性级状态 + 序号,最新值可用 |
| 无面向客户端的事件通道 | 家庭增量事件 + 长轮询,多端一致 |
| OTA 只有任务级聚合、无单设备追踪 | 单设备升级尝试(attempt),进度可追踪 |
**但需分期落地,不建议一次全量实施。** 协议中有三块属全新子系统建设(命令状态机、事件通道、状态版本号),另有 OTA 模型需重构,一次性上线的风险与回归成本过高。
---
## 二、P0 前置事项结论
### 2.1 DDL 缺列排查 —— ✅ 已澄清,非阻塞
协议要求"本地设备挂载到屏(父网关)",依赖 `Device.parentDeviceId` 与 `Device.isGateway`。此前发现 `backend/sql/` 全部迁移脚本中均无这两列,疑为阻塞项。**已核查生产库,结论如下**:
```
parent_device_id varchar(64) YES NULL
is_gateway tinyint YES 0
```
**生产库 `device` 表实际已存在这两列,非生产阻塞项。**
> 遗留事项(降为 **P1**):`sql/` 目录缺少对应迁移脚本,导致「从脚本重建的新环境」会缺列。需补一个迁移脚本登记这两列,保证环境可重建。详见第五章 P1。
### 2.2 三项契约冲突 —— 我方裁决
协议第 2 章通用约定与平台现行约定存在三处冲突,我方裁决如下:
#### 冲突 1:HTTP 状态码
- **平台现状**:全局约定「业务异常 HTTP 200 + JSON 业务码」。例:`/api/v1/auth/token` 凭证无效返回 `200 + {"code":401}`。
- **协议要求**:错误使用对应非 2xx(含 202/409/410/429/503),且明确「屏不能仅凭 HTTP 200 判定设备控制成功」。
**裁决:分区处理,双方约定不冲突。**
- 协议新增的 `/api/v1/panels/**` 及声明 `X-MUM-Protocol: panel-v1` 的请求 → **采用标准 HTTP 语义**(202 已接受 / 4xx / 5xx)。
- 存量接口(手机 App、网页、涂鸦回调)→ **保持 200 + 业务码不动**,避免打断现有客户端。
- 我方提供《ResultCode ↔ HTTP 状态 + errorCode》映射表(见 2.3),屏端按映射表处理。
协议第 2 章已预留此口子:「平台既有错误码可映射保留,但同一含义应稳定一致」。
#### 冲突 2:成功响应的 `code` 值
- **平台现状**:成功 `code = 200`(非 0)。
- **协议示例**:`{"code": 0, "message": "success"}`。
**裁决:沿用平台 `code = 200`,并明确判定方式。**
屏端请**不要硬编码 `code === 0`**。建议统一按下述优先级判定:
1. HTTP 状态码(panel 接口);
2. 响应体 `success` 布尔字段(平台 `Result` 已带);
3. 业务 `code`(成功为 200,非 0/200 即异常)。
#### 冲突 3:幂等键与新增必填字段的强制范围
- **协议要求**:所有 POST 必带 `Idempotency-Key`;控制命令必带 `schemaRevision`。
- **平台现状**:现有 App / 网页 / 涂鸦回调均无这些字段。
**裁决:仅对声明 `X-MUM-Protocol: panel-v1` 的请求强制。** 存量客户端保持原契约,内部归入同一设备路由与命令记录,由平台补齐默认版本。
协议第 2 章已认可:「不能直接把新必填字段施加到所有旧客户端」。
### 2.3 错误码映射表(我方提供,供屏端实现)
| HTTP | 业务 code | errorCode | 屏端处理 |
| --- | --- | --- | --- |
| 400 | 400 | INVALID_ARGUMENT | 修正参数,不自动重试 |
| 400 | 400 | UNSUPPORTED_CAPABILITY | 刷新能力,关闭无效操作 |
| 401 | 401 | TOKEN_EXPIRED | 刷新后重试同一逻辑请求 |
| 401 | 401 | INVALID_CREDENTIAL | 停止循环,上报身份错误 |
| 403 | 403 | HOME_ACCESS_REVOKED | 停止该家庭云操作,清缓存 |
| 403 | 403 | DEVICE_OUT_OF_SCOPE | 不执行跨家庭/非本屏任务 |
| 409 | 409 | IDEMPOTENCY_CONFLICT | 不生成新键掩盖冲突 |
| 409 | 409 | SCHEMA_CHANGED | 重读物模型,重新确认目标 |
| 409 | 409 | ROUTE_CHANGED | 刷新归属,旧屏不执行 |
| 409 | 409 | DELIVERY_EXPIRED | 本次投递无效,不执行 |
| 409 | 409 | EXECUTION_ALREADY_STARTED | 查原命令,不重复操作 |
| 409 | 409 | DEVICE_OFFLINE | 显示设备/执行屏离线 |
| 409 | 409 | STALE_SESSION | 停止领取 |
| 410 | 410 | CURSOR_EXPIRED | 重拉完整快照 |
| 410 | 410 | SNAPSHOT_EXPIRED | 放弃分页,重新开始 |
| 410 | 410 | COMMAND_EXPIRED | 不执行 |
| 429 | 429 | RATE_LIMITED | 按 Retry-After 退避 |
| 503 | 503 | SERVICE_UNAVAILABLE | 读请求退避;控制先查原结果 |
> `errorCode` 为语义稳定字符串,屏端按 `errorCode` 分支;`code` 数值仅供日志。
### 2.4 时间参数默认值(我方确认按协议实现)
心跳 30s / 在线租期 90s / 长轮询 20s / 命令总超时 30s(允许 1–120s)/ 投递租期 10s / 快照缓存 120s / 事件游标保留 24h / 幂等记录保留 7 天。
**全部由会话响应下发,屏端以响应值为准,不硬编码。**
---
## 三、逐条评审回执
图例:**接受** = 按协议实现;**修改后接受** = 采纳思路但有调整;**暂缓** = 延后到后续期;**需澄清** = 待屏方确认后定。
### 第 3 章 · 中控身份与会话
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 1 | `POST /api/v1/panels/session` | 新增 | **接受** | 新增 `panel_terminal` 表(serialNumber 唯一 + BCrypt 凭证 + homeId/memberId/role/enabled);`JwtAuthenticationFilter` 增加 `panel → ROLE_PANEL` 分支;复用现有 `JwtUtil` 签发 |
| 2 | `POST /api/v1/panels/session/refresh` | 新增 | **接受** | 协议要求「轮换 + 120s 重放窗口 + 可撤销」。平台现有 refresh 为无状态 JWT、无法撤销,故**新增刷新令牌表**实现轮换与撤销(同时修复会员侧同一隐患) |
| 3 | `GET /api/v1/panels/{panelId}` | 新增 | **接受** | 按协议返回,panelId 强制为自身 |
| 4 | `POST /api/v1/panels/{panelId}/heartbeat` | 新增 | **接受** | 按协议;租期自收到有效心跳起算,其它调用不隐式替代心跳 |
### 第 4 章 · 家庭快照与事件同步
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 5 | `GET /api/v1/homes`<br>`GET /api/v1/homes/{homeId}/rooms` | 扩展 | **接受(关键安全项)** | 平台现有接口以 `memberId` 入参过滤。屏调用时**必须忽略入参、以会话绑定家庭为准**。我方新增 `PanelContextResolver` 强制覆盖,杜绝越权选择器 |
| 6 | `GET /api/v1/panels/{panelId}/snapshot` | 新增 | **修改后接受** | 一期**单页返回不分页**(家庭设备量级有限),二期再上 `pageToken` 分页与快照一致性游标。字段按协议(含 executionSource/executorPanelId/routeRevision/schemaRevision/stateVersion) |
| 7 | `GET /api/v1/panels/{panelId}/events` | 新增 | **修改后接受(P2)** | 采用协议的 **DB 事件表 + 长轮询**方案(平台无 SSE/WebSocket 端点基础设施,不另起炉灶)。新增 `panel_event` 表 + 不透明游标。一期先以快照 + 状态上报保证可用 |
### 第 5 章 · 设备登记与标准控制模型
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 8 | `POST /api/v1/panels/{panelId}/devices/register` | 新增 | **接受** | 映射良好:屏 = 网关设备(`isGateway`),本地设备挂 `parentDeviceId`;`platform` 已支持 local/matter。`(panelId, localDeviceId)` 唯一,重复登记返回同一映射 |
| 9 | `PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema` | 新增 | **接受** | 物模型现状与协议 `PropertySpec` 高度吻合(code/type/access/min/max/step/unit/enumList/dpId 均已具备)。需**新增** `semantic`、`channel`、`completionMode` 三字段,并引入**设备级** `schemaRevision` |
| 10 | `GET /api/v1/devices/{deviceId}/control-schema`<br>`GET /api/v1/products/{id}/spec` | 扩展 | **修改后接受** | 现 `control-schema` **只返回 properties,未用 ProductAction/ProductEvent**。按协议补齐 actions(如 stop)。版本号平台内部称 `specVersion`(产品级),**对外统一暴露为 `schemaRevision`(设备级)** |
### 第 6 章 · 控制命令与执行回执(核心)
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 11 | `POST /api/v1/devices/{deviceId}/commands` | 扩展 | **修改后接受(核心改造)** | 现为同步阻塞、无命令记录(`CommandStatus` 常量有定义但零引用)。按协议改造为「先落库生成 commandId → 202 → 异步执行」。<br>**双轨兼容:云设备(涂鸦等)同步执行后直落终态,旧客户端仍返回 200 + 原结构;仅 `executionSource=LOCAL_PANEL` 且声明 panel-v1 的请求走 202 异步** |
| 12 | `GET /api/v1/device-commands/{commandId}` | 新增 | **接受** | 新增 `device_command` 表承载 10 态状态机;仅本命令关联家庭的合法调用者可查 |
| 13 | `POST /api/v1/panels/{panelId}/commands/pull` | 新增 | **接受** | 按协议:bootId 必填、投递租期 10s、deliveryToken 一次性 |
| 14 | `POST /api/v1/panels/{panelId}/commands/{commandId}/start` | 新增 | **接受** | 原子校验状态/有效期/归属/token,成功转 EXECUTING 并返回 executionToken |
| 15 | `POST /api/v1/panels/{panelId}/commands/{commandId}/report` | 新增 | **接受** | `reportId` + 递增 `reportSequence` 去重排序;平台按证据判定 CONFIRMED,不凭旧缓存值 |
### 第 7 章 · 状态与活动上报
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 16 | `POST /api/v1/panels/{panelId}/reports` | 新增 | **修改后接受** | 现设备实时状态唯一来源是 `device.capabilities.dps`(JSON 列),`/data/latest` 为 TODO 空壳,**全项目无 stateVersion/sequence**。按协议新增 `device_state`(deviceId + 属性码 + sequence + stateEpoch + quality),按 (epoch, seq) 比较决定 `appliedToCurrentState`。**顺带补上 `/latest` 空壳**,手机端同样受益 |
### 第 8 章 · 多屏、场景与 AI
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 17 | `GET /api/v1/scene-runs/{sceneRunId}` | 新增 | **暂缓(P3)** | 一期云端场景经「场景 → 设备命令 → 执行屏」即可跑通;场景执行单据化延后,避免与命令状态机同期上线放大回归面 |
| 18 | `POST /api/v1/panels/{panelId}/scene-runs/report` | 新增 | **暂缓(P3)** | 同上。离线场景历史记录二期补 |
| 19 | `POST /api/v1/ai/command` | 扩展 | **暂缓(P3)** | 平台现有二次确认字段(targetType/targetId)可覆盖基本需求;`confirmationId` + `expiresAt` + `status` 随屏端语音排期再上 |
### 第 9 章 · 配置、内容、日志和 OTA
| # | 接口 | 标记 | 结论 | 说明 |
| --- | --- | --- | --- | --- |
| 20 | `GET /api/v1/panels/{panelId}/configuration` | 新增 | **暂缓(P2)** | 一期屏用本地默认配置即可启动;配置下发二期随运营需求上 |
| 21 | `POST /api/v1/panels/{panelId}/diagnostics` | 新增 | **暂缓(P3)** | 诊断事件延后;期间用服务端日志定位 |
| 22 | `GET /api/v1/panels/{panelId}/ota-tasks/next` | 新增 | **修改后接受(P2)** | 平台 OTA 现为「产品固件 + 任务级聚合计数」,缺 `packageType`/`targetModel`/`minVersion`、无单设备升级记录、重复上报会重复计数、非 running 或不在目标集合时**静默丢弃**上报(屏会误判成功)。按协议改造:新增 `ota_attempt` 单设备升级尝试,聚合计数改为由 attempt 统计 |
| 23 | `POST /api/v1/ota-tasks/{id}/report` | 扩展 | **修改后接受(P2)** | 现请求体仅 `deviceId/success/version` 三字段。按协议增加 `attemptId/reportId/reportSequence/stage/progress/errorCode/message`,并做幂等 |
### 第 2 章 · 通用约定
| # | 约定 | 结论 | 说明 |
| --- | --- | --- | --- |
| 24 | 幂等键 / `X-MUM-Protocol` / 错误响应 / 标识与时间 | **修改后接受** | 见 2.2 三项裁决。权限清单(panel.read、gateway.execute 等 7 项)我方**接受**,签发时写入 JWT `scopes`,用 `hasAuthority` 做细粒度校验(平台已有 scopes 机制) |
---
## 四、数据模型改造清单
### 新增表
| 表 | 用途 | 期次 |
| --- | --- | --- |
| `panel_terminal` | 屏终端身份(序列号、凭证哈希、家庭绑定、角色) | P1 |
| `panel_refresh_token` | 刷新令牌轮换与撤销(兼修会员侧隐患) | P1 |
| `device_command` | 命令单据 + 10 态状态机(**核心**) | P1 |
| `device_state` | 设备属性级状态 + 序号(补 `/latest` 空壳) | P1 |
| `idempotency_record` | 幂等记录(保留 7 天) | P1 |
| `panel_event` | 家庭增量事件 + 游标 | P2 |
| `ota_attempt` | 单设备升级尝试(attempt/stage/progress) | P2 |
| `panel_configuration` | 屏配置下发 | P2 |
| `scene_run` | 场景执行单据 | P3 |
### 字段扩展
- **`device` 表新增**:`executor_panel_id`、`route_revision`、`schema_revision`、`local_device_id`、`protocol`、`execution_source`
- **`product_property` 新增**:`semantic`、`channel`、`completionMode`
- **`firmware` 新增**:`package_type`、`target_model`、`min_version`
### 迁移脚本补齐(P1)
`parent_device_id` / `is_gateway` 两列生产库已存在但 `sql/` 无迁移脚本,需补脚本登记,保证新环境可重建。
---
## 五、分期落地计划
### P0 · 前置(已完成 / 进行中)
- [x] 排查 `parent_device_id` / `is_gateway` 生产库列 → **已确认存在,阻塞解除**
- [x] 三项契约冲突裁决 → 见 2.2,待屏方确认映射表
### P1 · 一期:打通主链路
目标:**屏能注册、能看家、能执行云下发的命令**。完成即可覆盖约 90% 屏控场景。
1. 屏终端身份 + `ROLE_PANEL` + session / refresh(含刷新令牌表)
2. `PanelContextResolver`:家庭范围强制覆盖(安全关键)
3. `device` 执行归属字段 + `devices/register` + `schema` 更新
4. **`device_command` 状态机 + pull / start / report + 命令查询**(双轨兼容云同步)
5. `device_state` + `reports` 批量上报
6. `snapshot`(一期单页)
7. 补 `parent_device_id` / `is_gateway` 迁移脚本
### P2 · 二期:实时性与完整性
8. `panel_event` + `events` 长轮询
9. `control-schema` 补 actions / events + 设备级 `schemaRevision`
10. OTA 改造:`ota_attempt` + `Firmware` 扩展 + `ota-tasks/next`
11. `snapshot` 分页与一致性游标
12. `configuration` 下发
### P3 · 三期:体验与运营
13. `scene_run` / `scene-runs/report`
14. AI `confirmationId` + `status`
15. `diagnostics` 诊断事件
16. 多屏归属交接流程(`routeRevision` 迁移)
---
## 六、需屏方确认的问题清单
对应贵方第 12 章提出的 5 点,我方反馈如下并补充需确认项:
1. **推送方式**:我方一期采用协议建议的 **DB 事件表 + 长轮询**(平台无 SSE/WebSocket 端点基础设施)。请确认屏端「长轮询 20s / 心跳 30s / 租期 90s」三路并发调度互不阻塞。
2. **一期范围**:`scene-runs`、`AI confirmationId`、`diagnostics` 我方建议**延后**。请确认屏端一期是否强依赖其中任何一项。
3. **CONFIRMED 的证据来源**:屏端能否提供「写后读回」校验?若否,命令只能到达 `ACCEPTED` / `UNKNOWN`,界面须如实显示「已接受 / 结果未知」,不得显示为已确认(此点协议亦强调)。
4. **多屏归属交接**:一期是否仅单屏?多屏场景下 `routeRevision` 交接由谁触发(管理员后台操作 / 平台自动)?
5. **配网**:确认屏端 Matter/Zigbee/BLE 配网完成后走设备登记,**平台不下发任何协议密钥**(与协议一致)。
6. **OTA 下载**:我方提供平台自有下载域名,**无需屏端 Bearer Token 转发第三方**(与协议一致)。
7. **错误码映射表**(见 2.3):请确认按 `errorCode` 字符串分支、`code` 数值仅用于日志,且不硬编码 `code === 0`。
---
## 七、下一步
1. 双方就本回复的「修改后接受 / 暂缓」项达成一致,输出**定稿版协议**(路径与字段维护在同一份文档中)。
2. 我方按第五章 P1 启动一期开发;屏端同步适配。
3. 定稿前请勿按评审稿开发——评审稿中标注「新增建议」的路径与字段,以定稿为准。