完善牧安云哨-后端

This commit is contained in:
BBIT-Kai
2025-12-29 16:30:36 +08:00
parent cd7aa35960
commit b9b8d30ebf
23 changed files with 1074 additions and 41 deletions
@@ -0,0 +1,8 @@
from pydantic import BaseModel
class IotDeviceCommandRequest(BaseModel):
id: str | None = None
command: str | None = None
project: str | None = None
device_type: str | None = None
+107
View File
@@ -0,0 +1,107 @@
from typing import Optional
class MqttTopic:
"""
封装 MQTT topic,根据规则:
project/domain/deviceType/deviceId/resource
"""
LEVELS = 5
def __init__(self, topic: str):
self.raw = str(topic)
parts = self.raw.split("/")
# 不足的层级用 None 补齐,避免属性缺失
parts += [None] * (self.LEVELS - len(parts))
self.project: Optional[str] = parts[0]
self.domain: Optional[str] = parts[1]
self.device_type: Optional[str] = parts[2]
self.device_id: Optional[str] = parts[3]
self.resource: Optional[str] = parts[4]
@classmethod
def from_parts(
cls,
project: Optional[str] = None,
domain: Optional[str] = None,
device_type: Optional[str] = None,
device_id: Optional[str] = None,
resource: Optional[str] = None,
) -> "MqttTopic":
"""
通过结构化参数构造 topic
None -> '+'
"""
def _v(v: Optional[str]) -> str:
return "+" if v is None else str(v)
topic = "/".join(
map(
_v,
[
project,
domain,
device_type,
device_id,
resource,
],
)
)
return cls(topic)
def to_topic(self) -> str:
"""
根据当前字段生成 topic(允许 '+')
"""
def _v(v: Optional[str]) -> str:
return "+" if v is None else v
return "/".join(
map(
_v,
[
self.project,
self.domain,
self.device_type,
self.device_id,
self.resource,
],
)
)
def build(self) -> str:
"""
生成严格 topic(不允许 None / '+'
用于 publish 场景
"""
parts = [
self.project,
self.domain,
self.device_type,
self.device_id,
self.resource,
]
if any(p in (None, "+") for p in parts):
raise ValueError(
f"Cannot build strict topic, wildcard exists: {self.to_topic()}"
)
return "/".join(parts)
def is_wildcard(self) -> bool:
return "+" in self.to_topic() or "#" in self.to_topic()
def __repr__(self):
return f"<MqttTopic {self.to_topic()}>"
def is_status(self) -> bool:
return self.domain == "status"
def is_cmd(self) -> bool:
return self.domain == "cmd"
@@ -0,0 +1,10 @@
from pydantic import BaseModel
class SentinelRecordRequest(BaseModel):
Id: str | None = None
DeviceId: str
LicensePlate: str | None = None
LicensePlateImage: str | None = None
VehicleType: str | None = None
VehicleImage: str | None = None