第五部分补充:关键系统设计细节

1. 领域边界

领域实体/聚合负责不负责
产品Product、ThingModelVersion产品能力定义和版本设备当前值
设备Device、Credential、DeviceShadow身份、归属、运行态用户会话
接入Connection、Telemetry、DeviceEvent协议适配、校验、标准化业务页面
控制Command、CommandAck指令生命周期、超时、重试直接修改历史遥测
身份User、Role、Tenant、Binding认证、授权、归属MQTT 会话
运维Alert、AuditLog、WorkOrder告警、审计、履约产品物模型定义

MVP 为便于运行将这些领域放在同一 Node.js 进程中,但 API 和数据结构保持可拆分边界。

2. 设备生命周期

planned → registered → activated → online/offline → disabled → retired
              │            │
              └─ unbound ──┴─ bound → transfer_pending → bound
  • registered:平台已经生成设备身份,但设备可能从未联网。
  • activated:设备首次通过身份验证;该动作不能与用户绑定混为一谈。
  • online/offline:瞬时连接状态,应结合 Broker 连接事件和心跳 TTL 判断。
  • bound:设备拥有终端用户归属;后台运维权限不等于所有权。
  • disabled/retired:禁止新连接或指令,但历史数据和审计保留。

当前 MVP 用 status + ownerId + lastSeenAt 表达核心子集,生产库应拆成生命周期状态、连接状态和绑定状态三个字段。

3. 设备身份与绑定码

设备密钥和绑定码用途不同:设备密钥证明“这台设备是谁”,绑定码授权“一次用户归属操作”。生产实现要求:

  1. 设备密钥由安全随机数生成,烧录或产线注入,密文/哈希保存,支持轮换和吊销。
  2. MQTT 鉴权至少校验 tenant/product/device,主题 ACL 限制设备只能访问自身 Topic。
  3. 绑定码不复用设备密钥;可一次性、可过期、可由管理员重置,并对连续失败限流。
  4. 绑定事务以设备记录加锁或条件更新:owner_id IS NULL → owner_id = current_user;受影响行数为零则返回冲突。
  5. 用户侧读取设备时始终追加 owner_id = current_user,对越权对象返回统一的不存在响应。

4. MQTT 主题和消息信封

上行属性: iot/v1/{tenantId}/{productKey}/{deviceId}/property/post
上行事件: iot/v1/{tenantId}/{productKey}/{deviceId}/event/{eventKey}/post
下行指令: iot/v1/{tenantId}/{productKey}/{deviceId}/command/get
指令回执: iot/v1/{tenantId}/{productKey}/{deviceId}/command/reply

统一信封:

{
  "messageId": "01J...",
  "deviceId": "d-123",
  "timestamp": 1789142400000,
  "version": "1.0",
  "data": { "power": true }
}

服务端以 (device_id, message_id) 去重;时间戳只用于事件时间,不用于认证的唯一依据;无效物模型字段进入死信/错误流,不直接污染设备影子。

5. 指令状态机和一致性

pending ──publish──> sent ──ACK(success)──> succeeded
   │                   ├──ACK(error)─────> failed
   │                   └──deadline───────> timeout
   └──cancel─────────────────────────────> canceled

写库和发 MQTT 采用 Transactional Outbox:在同一数据库事务写入 command 与 outbox_event;发布器发送成功后标记 Outbox;重复发布由设备/服务端按 commandId 幂等。只有合法 ACK 可以更新最终状态,且状态转换通过条件更新防止迟到 ACK 覆盖取消/超时状态。

MVP backend/server.js:addCommand 对在线设备立即生成 succeeded、离线设备生成 pending。这是联调替身,不是生产 ACK 实现。

6. 设备影子合并规则

影子至少包含:

{
  "desired": { "power": true },
  "reported": { "power": false },
  "delta": { "power": true },
  "desiredVersion": 12,
  "reportedVersion": 11,
  "updatedAt": "2026-09-12T12:00:00+08:00"
}
  • 用户控制先写 desired 和新版本;设备 ACK/属性上报更新 reported。
  • delta 是 desired 与 reported 的差异,不作为独立事实源。
  • 遥测历史只追加,不随影子覆盖;影子更新必须校验版本,防止乱序消息回退状态。

7. 授权规则

接口类型身份附加约束
管理员 APIadmin/operator产品创建和审计仅 admin
小程序设备 APIuserdevice.ownerId === user.id
设备遥测 APIdeviceX-Device-Id + X-Device-Secret,生产迁移 mTLS/MQTT 凭证
企业 APItenant member/service accounttenant_id 强制过滤 + scope

前端菜单隐藏只改善体验,后端仍对每个接口授权。审计记录操作主体、动作、对象、结果、来源 IP/客户端和关联 ID;MVP 已实现主体、动作、对象、详情和时间。

8. JSON MVP 到生产数据库的映射

MVP 集合生产表/存储
usersiam_user, tenant_member, role_binding
productsiot_product, thing_model_version
devicesiot_device, device_credential, device_binding, device_shadow
telemetryTimescaleDB hypertable telemetry_point
commandsdevice_command, command_attempt, outbox_event
alertsalert, alert_transition
audits只追加 audit_log 或日志存储

迁移时先冻结 JSON 写入,导入主数据,校验数量/唯一键/绑定关系,再切换连接配置;不直接把 JSON 文件作为数据库备份格式长期维护。