提交 4e31fa9b authored 作者: 陈泽健's avatar 陈泽健

fix(device-sim): 修复创建设备 metadata 字段错误

- service 层:data.metadata → data.extra_attrs
- schema:添加 field_validator 过滤 SQLAlchemy MetaData
- schema:恢复 topic_params 字段和主题模板 Schema
- 前端类型:SimulatorUpdate.metadata → extraAttrs
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 46a566f2
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
from datetime import datetime from datetime import datetime
from typing import Optional, List, Dict from typing import Optional, List, Dict
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict, field_validator
def to_camel(string: str) -> str: def to_camel(string: str) -> str:
...@@ -99,6 +99,7 @@ class SimulatorBase(BaseModel): ...@@ -99,6 +99,7 @@ class SimulatorBase(BaseModel):
device_id: str = Field(..., min_length=1, max_length=200, description="模拟设备 ID") device_id: str = Field(..., min_length=1, max_length=200, description="模拟设备 ID")
auto_reconnect: bool = Field(default=True, description="是否自动重连") auto_reconnect: bool = Field(default=True, description="是否自动重连")
extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性") extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性")
topic_params: Optional[Dict[str, str]] = Field(default=None, description="主题动态参数")
model_config = ConfigDict( model_config = ConfigDict(
alias_generator=to_camel, alias_generator=to_camel,
...@@ -117,6 +118,7 @@ class SimulatorUpdate(BaseModel): ...@@ -117,6 +118,7 @@ class SimulatorUpdate(BaseModel):
device_name: Optional[str] = Field(default=None, min_length=1, max_length=200, description="模拟设备名称") device_name: Optional[str] = Field(default=None, min_length=1, max_length=200, description="模拟设备名称")
auto_reconnect: Optional[bool] = Field(default=None, description="是否自动重连") auto_reconnect: Optional[bool] = Field(default=None, description="是否自动重连")
extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性") extra_attrs: Optional[dict] = Field(default=None, description="设备自定义属性")
topic_params: Optional[Dict[str, str]] = Field(default=None, description="主题动态参数")
model_config = ConfigDict( model_config = ConfigDict(
alias_generator=to_camel, alias_generator=to_camel,
...@@ -134,6 +136,17 @@ class SimulatorResponse(SimulatorBase): ...@@ -134,6 +136,17 @@ class SimulatorResponse(SimulatorBase):
created_at: datetime = Field(..., description="创建时间") created_at: datetime = Field(..., description="创建时间")
updated_at: datetime = Field(..., description="更新时间") updated_at: datetime = Field(..., description="更新时间")
# 显式声明 metadata 字段,避免 Pydantic 尝试序列化 SQLAlchemy 的 MetaData 对象
metadata: Optional[dict] = Field(default=None, exclude=True)
@field_validator('metadata', mode='before')
@classmethod
def ignore_sqlalchemy_metadata(cls, v):
"""忽略 SQLAlchemy 的 MetaData 对象,只接受 dict 类型"""
if not isinstance(v, dict):
return None
return v
model_config = ConfigDict( model_config = ConfigDict(
from_attributes=True, from_attributes=True,
alias_generator=to_camel, alias_generator=to_camel,
...@@ -196,4 +209,35 @@ class TestConnectionResponse(BaseModel): ...@@ -196,4 +209,35 @@ class TestConnectionResponse(BaseModel):
model_config = ConfigDict( model_config = ConfigDict(
populate_by_name=True, populate_by_name=True,
) )
\ No newline at end of file
# ==================== 主题模板 Schema ====================
class TopicTemplateResponse(BaseModel):
"""主题模板响应"""
key: str = Field(..., description="模板唯一标识")
template: str = Field(..., description="主题路径模板")
label: str = Field(..., description="中文标签")
params: List[str] = Field(default=[], description="需要填写的动态参数名列表")
param_labels: Dict[str, str] = Field(default={}, description="参数名 -> 中文标签映射")
param_defaults: Dict[str, str] = Field(default={}, description="参数名 -> 默认值映射")
direction: str = Field(..., description="方向:publish(上报) / subscribe(订阅)")
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
)
class TopicTemplateListResponse(BaseModel):
"""主题模板列表响应"""
device_type: str = Field(..., description="设备类型")
templates: List[TopicTemplateResponse] = Field(default=[], description="主题模板列表")
has_real_topics: bool = Field(default=False, description="是否有真实主题模板")
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
)
...@@ -386,7 +386,8 @@ class DeviceSimService: ...@@ -386,7 +386,8 @@ class DeviceSimService:
device_name=data.device_name, device_name=data.device_name,
device_id=data.device_id, device_id=data.device_id,
auto_reconnect=data.auto_reconnect, auto_reconnect=data.auto_reconnect,
extra_attrs=data.metadata or {}, extra_attrs=data.extra_attrs or {},
topic_params=data.topic_params or {},
status="stopped", status="stopped",
) )
...@@ -507,6 +508,7 @@ class DeviceSimService: ...@@ -507,6 +508,7 @@ class DeviceSimService:
env_config_id=config.id, env_config_id=config.id,
mqtt_manager=mqtt_manager, mqtt_manager=mqtt_manager,
topics=topics, topics=topics,
topic_params=simulator.topic_params,
) )
if not sim: if not sim:
raise ValueError(f"不支持的设备类型: {simulator.device_type}") raise ValueError(f"不支持的设备类型: {simulator.device_type}")
......
...@@ -78,6 +78,7 @@ export interface Simulator { ...@@ -78,6 +78,7 @@ export interface Simulator {
status: string status: string
autoReconnect: boolean autoReconnect: boolean
extraAttrs: Record<string, any> | null extraAttrs: Record<string, any> | null
topicParams: Record<string, string> | null
lastReportedAt: string | null lastReportedAt: string | null
totalReports: number totalReports: number
createdAt: string createdAt: string
...@@ -92,13 +93,15 @@ export interface SimulatorCreate { ...@@ -92,13 +93,15 @@ export interface SimulatorCreate {
deviceId: string deviceId: string
autoReconnect?: boolean autoReconnect?: boolean
extraAttrs?: Record<string, any> | null extraAttrs?: Record<string, any> | null
topicParams?: Record<string, string> | null
} }
/** 更新模拟设备请求 */ /** 更新模拟设备请求 */
export interface SimulatorUpdate { export interface SimulatorUpdate {
deviceName?: string deviceName?: string
autoReconnect?: boolean autoReconnect?: boolean
metadata?: Record<string, any> | null extraAttrs?: Record<string, any> | null
topicParams?: Record<string, string> | null
} }
/** 上报记录 */ /** 上报记录 */
...@@ -142,4 +145,22 @@ export interface SimulatorListResponse { ...@@ -142,4 +145,22 @@ export interface SimulatorListResponse {
export interface ReportLogListResponse { export interface ReportLogListResponse {
items: ReportLog[] items: ReportLog[]
total: number total: number
}
/** 主题模板 */
export interface TopicTemplate {
key: string
template: string
label: string
params: string[]
paramLabels: Record<string, string>
paramDefaults: Record<string, string>
direction: 'publish' | 'subscribe'
}
/** 主题模板列表响应 */
export interface TopicTemplateListResponse {
deviceType: string
templates: TopicTemplate[]
hasRealTopics: boolean
} }
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论