第3章:指令数据结构设计
好,咱们进入第三个话题。前面聊了指令的生命周期和通信协议,现在该聊聊指令本身长什么样了。
说白了,指令数据结构就是车和云端之间的「共同语言」。你发个"开空调",车得知道是开哪个空调、开多少度、开多久。这些信息怎么组织?这就是本章要解决的问题。
3.1 为什么需要JSON Schema
我记得刚入行那会儿,团队里指令格式全靠口头约定。今天你加个字段,明天他改个类型,后天就出线上事故了。后来我学乖了——必须用Schema来约束。
JSON Schema就像一张蓝图。它告诉你:
- 哪些字段是必须的
- 字段的值是什么类型
- 取值范围是多少
- 字段之间有没有依赖关系
举个例子,一个简单的车辆控制指令Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "VehicleControlCommand",
"type": "object",
"required": ["commandId", "commandType", "timestamp", "params"],
"properties": {
"commandId": {
"type": "string",
"description": "全局唯一指令ID,UUID格式",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"commandType": {
"type": "string",
"enum": ["DOOR_LOCK", "DOOR_UNLOCK", "AC_ON", "AC_OFF", "WINDOW_UP", "WINDOW_DOWN", "ENGINE_START", "ENGINE_STOP"]
},
"timestamp": {
"type": "integer",
"description": "Unix时间戳,毫秒级",
"minimum": 1600000000000
},
"params": {
"type": "object",
"description": "指令参数,根据commandType不同而变化"
}
}
}
我的小技巧: 实际项目中,我会把Schema放在Git仓库里,每次修改都要走PR评审。这样谁改了啥,一目了然。
3.2 指令类型枚举设计
指令类型不能随便写字符串。你想想看,有人写"lock_door",有人写"DoorLock",还有人写"锁门"...这不乱套了?
我建议用枚举来约束。Python里可以用Enum类:
from enum import Enum
class CommandType(Enum):
DOOR_LOCK = "DOOR_LOCK"
DOOR_UNLOCK = "DOOR_UNLOCK"
AC_ON = "AC_ON"
AC_OFF = "AC_OFF"
WINDOW_UP = "WINDOW_UP"
WINDOW_DOWN = "WINDOW_DOWN"
ENGINE_START = "ENGINE_START"
ENGINE_STOP = "ENGINE_STOP"
HORN_ON = "HORN_ON"
HORN_OFF = "HORN_OFF"
LIGHT_ON = "LIGHT_ON"
LIGHT_OFF = "LIGHT_OFF"
SUNROOF_OPEN = "SUNROOF_OPEN"
SUNROOF_CLOSE = "SUNROOF_CLOSE"
SEAT_HEAT_ON = "SEAT_HEAT_ON"
SEAT_HEAT_OFF = "SEAT_HEAT_OFF"
REMOTE_START = "REMOTE_START"
REMOTE_STOP = "REMOTE_STOP"
VEHICLE_LOCATE = "VEHICLE_LOCATE"
VEHICLE_ALARM = "VEHICLE_ALARM
为什么要用枚举?我在项目里吃过亏。有一次,一个同事把"DOOR_LOCK"写成了"DOOR_LOCk"(注意最后那个小写k),结果线上跑了三天才发现。从那以后,所有指令类型必须走枚举校验。
3.3 参数校验规则
参数校验是重中之重。你想想,如果用户发了个"温度调到500度"的指令,车机要是真执行了,那不得出大事?
我一般把参数校验分成三层:
| 校验层级 | 校验内容 | 示例 |
|---|---|---|
| 第一层:类型校验 | 参数类型是否正确 | temperature必须是number |
| 第二层:范围校验 | 参数值是否在合理范围内 | temperature必须在16-30之间 |
| 第三层:业务校验 | 参数组合是否合理 | AC_ON时temperature不能为空 |
来看一个具体的参数校验实现:
def validate_ac_params(params: dict) -> bool:
"""空调指令参数校验"""
# 第一层:类型校验
if not isinstance(params.get("temperature"), (int, float)):
return False
if not isinstance(params.get("mode"), str):
return False
if not isinstance(params.get("fan_speed"), int):
return False
# 第二层:范围校验
if not (16 <= params["temperature"] <= 30):
return False
if params["mode"] not in ["COOL", "HEAT", "VENT", "AUTO"]:
return False
if not (1 <= params["fan_speed"] <= 7):
return False
# 第三层:业务校验
# 制热模式下温度不能低于20度
if params["mode"] == "HEAT" and params["temperature"] < 20:
return False
# 自动模式下风速必须设为0(由系统自动控制)
if params["mode"] == "AUTO" and params["fan_speed"] != 0:
return False
return True
避坑指南: 我曾经遇到过一个问题——参数校验通过了,但车机执行时报错。后来发现是参数的单位搞错了。云端发的是摄氏度,车机内部用的是华氏度。所以,参数校验里一定要包含单位信息。
3.4 完整的指令数据结构
把上面这些整合起来,一个完整的指令数据结构大概长这样:
{
"commandId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"commandType": "AC_ON",
"timestamp": 1700000000000,
"source": "APP",
"userId": "user_12345",
"vin": "LSVAU2A38N2100001",
"params": {
"temperature": 24,
"mode": "COOL",
"fan_speed": 3,
"air_circulation": "OUTER",
"ac_switch": true
},
"priority": 5,
"ttl": 30,
"signature": "sha256_hash_here"
}
这里我特别想强调一下signature字段。嗯,这个很重要。指令在传输过程中可能被篡改,所以一定要加签名。我习惯用HMAC-SHA256,密钥存在HSM里。
3.5 实战中的注意事项
最后分享几个我在实战中踩过的坑:
- 字段命名要统一: 别一会儿用camelCase,一会儿用snake_case。我统一用snake_case,因为Python后端解析方便。
- 版本号不能少: 指令结构会迭代,加个version字段,方便兼容旧版本。
- 空值处理要明确: 有些字段允许为空,有些不行。在Schema里用nullable明确标注。
- 日志要记录原始报文: 出问题的时候,原始报文是最重要的排查依据。
核心要点:
- JSON Schema是指令结构的「宪法」,必须严格执行
- 指令类型用枚举约束,避免字符串拼写错误
- 参数校验分三层:类型、范围、业务逻辑
- 签名机制是安全底线,不能省略
好了,这一章就到这里。下一章咱们聊聊指令下发时的并发控制和幂等性设计——这可是个容易踩坑的地方。