4. 设备模型与物模型设计
在IoT多租户SaaS平台中,设备接入只是第一步。真正决定平台能否规模化复用的,是物模型(Thing Model)——它把物理设备抽象成平台可理解、可存储、可编排的数据契约。没有物模型,每个设备都只能靠硬编码解析;有了物模型,平台才能自动完成数据解析、指令下发、告警触发和可视化渲染。本节围绕三个核心问题展开:物模型如何抽象、版本如何管理、多租户下如何隔离与复用。
一、物模型的三个抽象维度:属性、事件、服务
物模型用三类元素描述一个设备“能提供什么、会报告什么、可被要求做什么”。这三类元素不是随意划分,而是对应了IoT数据流的三种方向与语义。
属性(Property)描述设备的状态量,是平台可读、可写、可缓存的数据点。例如智能温控器的“当前温度”“目标温度”“运行模式”。属性有明确的数据类型(int32、float、bool、enum、text、date)、取值范围和单位。属性又分读写权限:只读属性(如当前温度)由设备上报,平台只订阅;读写属性(如目标温度)平台可下发期望值,设备执行后回传实际值,形成“期望值—实际值”的双值模型,这是处理设备离线、指令延迟的关键设计。
事件(Event)描述设备主动上报的离散通知,通常带有时间戳和事件参数。事件分两类:信息类(如“门磁被打开”“滤芯寿命剩余10%”)和告警类(如“高温告警”“通信中断”)。事件与属性的区别在于:属性是持续存在的状态,事件是某一时刻发生的动作或异常。平台对事件的处理通常是“记录+触发规则”,而不是缓存最新值。
服务(Service)描述设备可被调用的能力,即平台向设备下发的异步或同步指令。服务有输入参数和输出参数,例如“设置温度(目标值)→返回(执行结果)”“重启设备→返回(重启耗时)”。服务调用是请求—响应模型,平台需要处理超时、重试和幂等。
三者的关系可以用一张结构图清晰表达。下图展示了一个“智能温控器”物模型的完整抽象,以及属性、事件、服务在数据流中的位置。
二、物模型版本管理:为什么必须做,怎么做
物模型不是一成不变的。产品迭代时,可能新增属性、修改取值范围、废弃旧事件、调整服务参数。如果直接修改线上模型,已接入的旧固件设备会因字段不匹配而解析失败,新设备又可能使用新字段,导致平台数据混乱。因此,物模型必须版本化。
版本管理的核心原则是:模型版本与产品绑定,设备在注册时声明自己遵循的模型版本。平台按设备声明的版本加载对应模型进行解析。具体做法如下:
- 版本号规则:采用“主版本.次版本”两级,如 v1.0、v1.1、v2.0。主版本变更表示不兼容(删除属性、修改类型、改变语义);次版本变更表示兼容扩展(新增可选属性、新增事件、放宽取值范围)。
- 兼容性策略:次版本升级时,平台同时加载新旧两个版本的模型定义,旧设备按旧版本解析,新设备按新版本解析,互不影响。主版本升级时,旧版本进入“维护期”,平台仍可解析但不再接受新设备注册到旧版本。
- 灰度与回滚:新版本模型先在小批量设备上验证,确认解析成功率、指令响应率正常后,再逐步扩大。若发现异常,将产品默认版本回滚到上一稳定版本,已升级设备可继续使用新版本或强制回退。
一个常见的教学示例:某温控器产品 v1.0 只有“当前温度”和“目标温度”两个属性。v1.1 新增“湿度”只读属性,并新增“滤芯寿命低”事件。此时 v1.0 设备上报的数据中不含湿度字段,平台按 v1.0 模型解析,湿度字段缺失不报错;v1.1 设备上报湿度时,平台按 v1.1 模型解析并存储。规则引擎中若引用了“湿度”属性,需判断设备模型版本,避免对 v1.0 设备误触发。
三、多租户下的模型隔离与复用
多租户SaaS平台的核心矛盾是:既要让不同租户的数据和配置严格隔离,又要让公共的设备类型模型能够复用,避免每个租户重复定义。解决思路是分层:平台级公共模型库 + 租户级私有模型 + 租户对公共模型的引用与扩展。
平台级公共模型库由平台运营方维护,收录通用设备类型,如“标准电表”“通用温湿度传感器”“标准PLC网关”。这些模型对所有租户可见、可引用,但租户不能直接修改公共模型,只能“引用”或“派生”。
租户级私有模型是租户基于自身业务定义的模型,仅本租户可见。租户可以:直接创建全新模型;从公共模型派生,继承其属性、事件、服务,再追加租户特有字段;或者引用公共模型,不修改定义,仅在规则和可视化中使用。
隔离机制体现在三个层面:数据隔离——设备数据按租户ID分区存储,查询强制带租户过滤;模型隔离——租户只能看到自己的私有模型和公共模型,不能看到其他租户的私有模型;版本隔离——同一公共模型的不同版本,租户可锁定使用某一版本,避免平台升级公共模型时影响租户业务。
tenant_id 字段,公共模型 tenant_id = 0,租户私有模型 tenant_id = 具体租户ID。查询时使用 WHERE tenant_id IN (0, :currentTenantId),即可同时获取公共模型和本租户私有模型,天然实现隔离与复用。
四、一个可执行的物模型定义示例
下面给出一个温控器物模型的 JSON 结构示例(教学简化版),展示属性、事件、服务如何在同一份定义中组织,以及版本字段和租户字段的位置。
{
"productKey": "thermostat_001",
"modelVersion": "1.1",
"tenantId": 0,
"properties": [
{ "id": "current_temp", "name": "当前温度", "type": "float", "unit": "℃", "access": "r" },
{ "id": "target_temp", "name": "目标温度", "type": "float", "unit": "℃", "access": "rw", "min": 5, "max": 35 },
{ "id": "mode", "name": "运行模式", "type": "enum", "values": ["cool","heat","auto"], "access": "rw" },
{ "id": "humidity", "name": "湿度", "type": "float", "unit": "%", "access": "r" }
],
"events": [
{ "id": "high_temp_alarm", "name": "高温告警", "type": "alert", "params": ["current_temp"] },
{ "id": "filter_low", "name": "滤芯寿命低", "type": "info", "params": ["remain_percent"] }
],
"services": [
{ "id": "set_temp", "name": "设置温度", "input": ["target_temp"], "output": ["result_code"] },
{ "id": "reboot", "name": "重启设备", "input": [], "output": ["cost_ms"] }
]
}
平台收到设备上报的 {"current_temp": 26.5, "humidity": 48} 时,根据 productKey 和 modelVersion 找到对应模型,逐字段校验类型和范围,写入时序数据库。若收到 high_temp_alarm 事件,则触发规则引擎中配置的告警动作。若平台下发 set_temp 服务,设备执行后返回 result_code=0,平台更新“目标温度”的期望值状态。
五、设计检查清单
在实际项目中,物模型设计完成后,建议用以下清单快速自检:
| 检查项 | 判断标准 | 常见问题 |
|---|---|---|
| 属性是否区分读写 | 只读属性设备上报,读写属性支持期望值/实际值 | 全部设为读写,导致平台误下发 |
| 事件是否带时间戳和参数 | 每个事件有明确参数列表和类型 | 事件无参数,规则引擎无法判断 |
| 服务是否有超时和幂等设计 | 服务定义包含超时时间和重试策略 | 重复下发导致设备执行两次 |
| 版本号是否与产品绑定 | 设备注册时声明模型版本 | 平台只维护一份模型,旧设备解析失败 |
| 租户隔离字段是否完整 | 模型表、设备表、数据表均含 tenant_id | 查询漏加租户条件,数据越权 |
| 公共模型是否可派生 | 租户可继承公共模型并追加字段 | 每个租户重复定义相同模型 |