纳入范围: - 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
13 KiB
10.1 寸中控屏对接设计文档
状态:讨论定稿(待开发) 日期:2026-09-18 范围:中控屏(家庭内常驻大屏)与现有 IoT SaaS 后端的对接方案,涵盖认证、接口、实时推送、安全。
1. 背景与目标
现有系统(后端 iot-device 9081)已具备完整的 家庭/房间/设备/涂鸦/动态 能力,手机 APP 通过会员账号(JWT userType=member)访问,家庭权限靠 homeMember 表校验。
现在要对接一块 10.1 寸中控屏,功能与手机 APP 类似:家庭/房间管理、设备管理/控制、场景、近期动态。目标是让中控屏以安全、隔离、实时的方式复用现有业务能力,同时不破坏手机端现有逻辑。
2. 决策摘要(已确认方案)
| 维度 | 决策 | 说明 |
|---|---|---|
| 身份模型 | 专属设备凭证(非会员账号登录) | 中控屏是家庭设备,不存个人密码;出厂 SN + 激活码,APP 扫码绑定家庭后签发 panel JWT |
| 接口组织 | 独立命名空间 /api/v1/panel |
与手机接口清晰隔离,便于限流、审计、差异化(聚合接口) |
| 实时推送 | WebSocket 推送 | 新增 WSS 端点,按 homeId 分组推送状态/场景结果,手机后续也可复用 |
| 家庭归属 | 单一固定家庭 | 激活后绑定一个家庭,所有接口强制限定该 homeId |
3. 整体架构
┌────────────┐ HTTPS/WSS ┌──────────────────────────────┐
│ 中控屏 │ ──────────────▶ │ iot-device (9081) │
│ (10.1"屏) │ │ /api/v1/panel/* 控制器 │
│ │ ◀──── WSS 推送 ── │ /panel/ws WebSocket 端点 │
└────────────┘ │ │ │
│ 激活(扫码) │ ▼ │
▼ │ PanelAuth 拦截器(deviceType+ │
┌────────────┐ │ homeId+session 校验) │
│ 手机 APP │ ── 扫码绑定 ───▶ │ │ │
└────────────┘ │ ▼ │
│ 复用现有 Service 层 │
│ (Home/Room/Device/Tuya/ │
│ Activity Service) │
│ │ │
│ ▼ │
│ DeviceEventPublisher ──▶ RabbitMQ
│ (设备数据变化) │ │
│ ▼ │
│ WS 广播(按homeId)
└──────────────────────────────┘
核心思路:数据模型与业务 Service 完全复用,新增的只是「设备身份鉴权层」+「panel 专属控制器(含聚合接口)」+「WebSocket 推送通道」。
4. 认证与安全设计
4.1 设备激活流程(出厂 SN + 激活码 + APP 扫码绑定)
- 出厂阶段:每台中控屏烧录唯一
SN,并预置一次性激活码(服务端可校验的凭证,哈希存储)。 - 首次开机:中控屏展示 二维码(含 SN + 激活码),提示"用手机 APP 扫码绑定家庭"。
- 手机 APP 扫码 → 调用后端绑定接口,选择一个家庭(homeId)→ 后端:
- 校验激活码有效且未使用;
- 创建/更新
panel_device记录,写入home_id、bound_member_id、status=已绑定; - 标记激活码已消费(一次性)。
- 中控屏用
SN + 激活码调用/panel/auth/token换取 panel JWT(access + refresh)。 - 后续所有请求携带 panel JWT;access 过期用 refresh 静默续期(与手机机制一致)。
安全优势:中控屏从不接触任何个人会员密码,只持有设备凭证 + 短期 JWT,即使设备被物理获取,也只能访问已绑定家庭的有限接口。
4.2 Panel Token 设计
在现有 com.iot.common.util.JwtUtil 基础上扩展 panel token 生成,claims 增加:
| claim | 值 | 用途 |
|---|---|---|
sub |
panel_device.id | 设备主键 |
deviceType |
panel |
与 userType=member/admin 区分 |
homeId |
绑定家庭 ID | 强制 home 隔离 |
scope |
home:read,home:control |
权限最小化 |
sessionId |
本次会话 ID | 支持远程吊销 |
userType |
panel(兼容现有解析) |
— |
access 有效期建议 30 分钟,refresh 滚动续期;refresh 接口校验 deviceType=panel 且 session 仍有效。
4.3 鉴权隔离
新增 PanelAuth 拦截器(或在现有 JwtAuthFilter 扩展分支):
- 校验
deviceType=panel; - 从 token 取出
homeId,所有/panel接口请求参数中的 homeId 必须以 token 中的 homeId 为准,禁止越权访问其它家庭; - 校验
sessionId未被吊销(panel_device.status非禁用); - 禁止 panel token 访问
/auth/member、/admin、/users、/roles等会员/后台接口(路径白名单隔离)。
4.4 安全清单 / 风险缓解
- 传输安全:全链路 HTTPS;中控屏出厂内置服务端证书指纹(证书固定 / pinning),防局域网中间人。
- 凭证安全:激活码一次性、可吊销、限尝试次数;中控屏仅加密存储 refresh token,不存任何密码。
- 权限最小化:panel 仅
home-scoped,不能访问会员/管理员接口;homeId 由 token 锁定。 - 设备吊销:手机 APP「中控屏管理」可解绑/禁用某 SN →
panel_device.status=disabled且 session 失效,已签发 JWT 在下一次校验被拒。 - 限流:激活、换 token、控制等敏感操作独立限流(如 5 次/分钟/IP)。
- 审计:所有中控屏控制写入
device_activity_log,source=central,并关联panel_id,可追溯。 - 防重放(可选二期):敏感写操作要求
nonce + timestamp签名。 - 远程禁用:管理员可在后台按 SN 拉黑设备。
5. 接口设计(/api/v1/panel)
5.1 认证类
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/panel/auth/activate |
提交 SN + 激活码,校验有效性,返回待绑定态 |
| POST | /api/v1/panel/auth/token |
SN + 激活码 换取 access/refresh(绑定完成后) |
| POST | /api/v1/panel/auth/refresh |
刷新 token(滚动续期,校验 session) |
| POST | /api/v1/panel/auth/unbind |
解绑/注销(使 session 失效) |
手机 APP 侧的「扫码绑定家庭」接口:复用现有家庭接口,新增一个绑定确认端点(如
/api/v1/homes/{homeId}/bind-panel),仅 owner/admin 可操作。
5.2 家庭 / 房间 / 设备(复用现有 Service)
| 方法 | 路径 | 复用 |
|---|---|---|
| GET | /api/v1/panel/homes/{homeId}/summary |
新增聚合接口:首页一屏拉取(房间、设备+状态、场景、近期动态) |
| GET | /api/v1/panel/homes/{homeId}/rooms |
RoomService |
| GET | /api/v1/panel/homes/{homeId}/devices |
DeviceService(含在线状态) |
| GET | /api/v1/panel/homes/{homeId}/devices/{deviceId} |
DeviceService.getDeviceInfo |
| POST | /api/v1/panel/devices/{deviceId}/commands |
DeviceService.sendCommand / TuyaCloudService.sendCommand(与 DeviceController.sendCommand 同源逻辑) |
| GET | /api/v1/panel/homes/{homeId}/activities |
DeviceActivityService(近期动态) |
注:现有控制接口在
DeviceController.sendCommand(/api/v1/devices/{deviceId}/commands)与TuyaController.sendCommand(/api/v1/tuya/devices/{deviceId}/commands)。panel 控制器内部复用同一DeviceService/TuyaCloudService,仅替换鉴权维度为 panel+homeId。
5.3 场景(当前无 SceneController,需新建,与首页场景对齐)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/panel/homes/{homeId}/scenes |
场景列表(推荐场景) |
| POST | /api/v1/panel/homes/{homeId}/scenes/{sceneId}/execute |
执行场景 |
待确认:首页「推荐场景 / 灯光 / 空调 / 安防」当前后端落地程度(是否已有场景实体与执行链路),需在开发前与首页场景功能一并规划。
5.4 近期动态
复用 DeviceActivityController(/api/v1/devices/activities),panel 下限定 homeId 过滤;首页聚合接口 summary 直接内联最近 N 条动态,点击进入查看完整列表(复用 activities 列表 + 详情页)。
6. WebSocket 实时推送设计
6.1 端点与鉴权
- 端点:
/api/v1/panel/ws?token={panelJWT}(WS 握手无法自定义 Header,token 放 query;配合 WSS 加密)。 - 握手时校验 panel JWT(deviceType + homeId + session),失败立即关闭。
- 连接成功后,将该 Session 加入内存
homeId → Set<Session>路由表。
6.2 推送消息结构
{
"type": "device.status | device.online | device.offline | scene.executed | home.updated",
"homeId": 123,
"payload": { "deviceId": "...", "dps": {...}, "ts": 1234567890 }
}
6.3 与现有事件机制衔接
现有 DeviceEventPublisher 已通过 RabbitMQ(EXCHANGE + ROUTING_DEVICE_DATA_CHANGE)发布设备数据变化事件。
- 新增一个 WS 广播监听器:消费设备数据变化事件 → 根据设备归属 homeId → 推送给该 home 所有在线 panel(及后续手机)WS 连接。
- 控制成功(
DeviceService.sendCommand)后同样publishDeviceDataChange→ 同 home 所有客户端实时刷新。 - 上下线(涂鸦云推送
TuyaMessageSubscribeService)也纳入广播。
这样中控屏与手机控制/状态天然一致,无需各自轮询。
6.4 客户端职责
心跳保活 + 断线指数退避重连;重连后增量拉取(调用 summary/devices)补齐离线期间变更。
7. 数据模型变更
新增表 panel_device(MySQL):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint PK | 设备主键 |
| sn | varchar(64) UNIQUE | 出厂序列号 |
| activation_code_hash | varchar(128) | 激活码哈希(一次性) |
| home_id | bigint | 绑定家庭(单一固定) |
| bound_member_id | bigint | 操作绑定的会员(审计/解绑) |
| name | varchar(64) | 中控屏昵称 |
| status | tinyint | 0 未激活 / 1 已绑定 / 2 已禁用 |
| session_id | varchar(64) | 当前会话(用于吊销) |
| last_active_at | datetime | 最后活跃 |
| create_time / update_time | datetime | — |
现有
device_activity_log已有source字段(app/panel/voice/scene/cloud_push),新增central取值并加panel_id列用于审计关联。
8. 与手机端一致性 & 不破坏现有
- 不改动现有
/api/v1/homes、/rooms、/devices、/tuya、/devices/activities及其鉴权(member 维度)。 - 新增
/panel命名空间与 PanelAuth 拦截器,是叠加而非替换。 - 业务 Service 层复用,避免逻辑分叉。
- WebSocket 推送对手机端是可选增强:手机后续可接同一 WS 通道,移除轮询,逐步收敛到统一实时模型。
9. 分阶段实施计划
- P0 基础:
panel_device表 + 激活/换 token + PanelAuth 拦截器 + 证书固定准备。 - P1 核心接口:
/panel家庭/房间/设备/控制/动态(复用 Service),中控屏可完成基础操控。 - P2 实时:WebSocket 端点 + RabbitMQ 事件 → WS 广播,首页聚合
summary实时刷新。 - P3 场景:场景列表/执行接口(与首页场景功能对齐落地)。
- P4 安全加固 & 管理:限流、审计、远程禁用;手机 APP「中控屏管理」页面(绑定/解绑/禁用)。
10. 待确认 / 开放问题
- 激活码的生成与下发方式(出厂预置固件 vs 后台按 SN 生成)。
- 首页「推荐场景 / 灯光 / 空调 / 安防」后端当前落地程度 —— 需先与首页场景功能统一规划(P3 依赖)。
- 是否允许同一家庭绑定多台中控屏(建议允许 N 台,均 home-scoped)。
- 中控屏是否要做「局域网内直连网关/设备」低延迟控制(本期先走云端,局域网优化留二期)。
- 设备重置(恢复出厂)后激活码失效策略:需后台支持重新生成激活码。
附录:现有可复用代码锚点
- 控制:
DeviceController.sendCommand(/api/v1/devices/{deviceId}/commands)、TuyaController.sendCommand(/api/v1/tuya/devices/{deviceId}/commands) - 事件:
DeviceEventPublisher.publishDeviceDataChange(RabbitMQ 设备数据变化) - 鉴权:
com.iot.common.util.JwtUtil(现有 JWT,已含userTypeclaim,可扩展deviceType/homeId/sessionId) - 动态:
DeviceActivityController+device_activity_log(含source) - 家庭权限:
HomeService/RoomServiceImpl的homeMember校验逻辑(panel 改为 homeId 锁定)