feat: 信标设备绑定 + 蓝牙模式管理 + 系统管理增强 + 数据导出
- 新增 DeviceBeaconBinding 模型,信标-设备多对多绑定 CRUD - 蓝牙打卡模式批量配置/恢复正常模式 API - 反向同步: 查询设备 BTMACSET 配置更新数据库绑定 (独立 session 解决事务隔离) - 设备列表快捷操作弹窗修复 (fire-and-forget IIFE 替代阻塞轮询) - 保存按钮防抖: 围栏/信标绑定保存点击后 disabled + 转圈防重复提交 - 审计日志中间件 + 系统配置/备份/固件 API - 设备分组管理 + 告警规则配置 - 5个数据导出 API (CSV UTF-8 BOM) - 位置热力图 + 告警条件删除 + 位置清理 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
145
app/schemas.py
145
app/schemas.py
@@ -59,6 +59,7 @@ class DeviceUpdate(BaseModel):
|
||||
imsi: str | None = Field(None, max_length=20)
|
||||
timezone: str | None = Field(None, max_length=30)
|
||||
language: str | None = Field(None, max_length=10)
|
||||
group_id: int | None = None
|
||||
|
||||
|
||||
class DeviceResponse(DeviceBase):
|
||||
@@ -72,6 +73,7 @@ class DeviceResponse(DeviceBase):
|
||||
last_login: datetime | None = None
|
||||
iccid: str | None = None
|
||||
imsi: str | None = None
|
||||
group_id: int | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime | None = None
|
||||
|
||||
@@ -374,6 +376,23 @@ class BeaconConfigResponse(BaseModel):
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Device-Beacon Binding schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class DeviceBeaconBindRequest(BaseModel):
|
||||
device_ids: list[int] = Field(..., min_length=1, max_length=100, description="设备ID列表")
|
||||
|
||||
|
||||
class BeaconDeviceDetail(BaseModel):
|
||||
"""Binding detail with device info."""
|
||||
binding_id: int
|
||||
device_id: int
|
||||
device_name: str | None = None
|
||||
imei: str | None = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fence Config schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -588,6 +607,132 @@ class CommandListResponse(APIResponse[PaginatedList[CommandResponse]]):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Device Group schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class DeviceGroupCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100, description="分组名称")
|
||||
description: str | None = Field(None, max_length=500, description="描述")
|
||||
color: str = Field(default="#3b82f6", max_length=20, description="颜色")
|
||||
|
||||
|
||||
class DeviceGroupUpdate(BaseModel):
|
||||
name: str | None = Field(None, max_length=100)
|
||||
description: str | None = None
|
||||
color: str | None = Field(None, max_length=20)
|
||||
|
||||
|
||||
class DeviceGroupResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
description: str | None = None
|
||||
color: str
|
||||
created_at: datetime
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
class DeviceGroupWithCount(DeviceGroupResponse):
|
||||
device_count: int = 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Alert Rule schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AlertRuleCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100, description="规则名称")
|
||||
rule_type: Literal["low_battery", "no_heartbeat", "fence_stay", "speed_limit", "offline_duration"] = Field(
|
||||
..., description="规则类型"
|
||||
)
|
||||
conditions: dict = Field(..., description="条件参数, 如 {\"threshold\": 20}")
|
||||
is_active: bool = Field(default=True)
|
||||
device_ids: str | None = Field(None, description="适用设备ID (逗号分隔), null=全部")
|
||||
group_id: int | None = Field(None, description="适用分组ID")
|
||||
description: str | None = Field(None, max_length=500)
|
||||
|
||||
|
||||
class AlertRuleUpdate(BaseModel):
|
||||
name: str | None = Field(None, max_length=100)
|
||||
rule_type: Literal["low_battery", "no_heartbeat", "fence_stay", "speed_limit", "offline_duration"] | None = None
|
||||
conditions: dict | None = None
|
||||
is_active: bool | None = None
|
||||
device_ids: str | None = None
|
||||
group_id: int | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class AlertRuleResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
rule_type: str
|
||||
conditions: dict
|
||||
is_active: bool
|
||||
device_ids: str | None = None
|
||||
group_id: int | None = None
|
||||
description: str | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Audit Log schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AuditLogResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
method: str
|
||||
path: str
|
||||
status_code: int
|
||||
operator: str | None = None
|
||||
client_ip: str | None = None
|
||||
request_body: dict[str, Any] | None = None
|
||||
response_summary: str | None = None
|
||||
duration_ms: int | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System Config schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class SystemConfigResponse(BaseModel):
|
||||
"""Current runtime configuration (read-only and writable fields)."""
|
||||
data_retention_days: int
|
||||
data_cleanup_interval_hours: int
|
||||
tcp_idle_timeout: int
|
||||
fence_check_enabled: bool
|
||||
fence_lbs_tolerance_meters: int
|
||||
fence_wifi_tolerance_meters: int
|
||||
fence_min_inside_seconds: int
|
||||
rate_limit_default: str
|
||||
rate_limit_write: str
|
||||
track_max_points: int
|
||||
geocoding_cache_size: int
|
||||
|
||||
|
||||
class SystemConfigUpdate(BaseModel):
|
||||
"""Fields that can be updated at runtime."""
|
||||
data_retention_days: int | None = Field(None, ge=1, le=3650)
|
||||
data_cleanup_interval_hours: int | None = Field(None, ge=1, le=720)
|
||||
tcp_idle_timeout: int | None = Field(None, ge=0, le=86400)
|
||||
fence_check_enabled: bool | None = None
|
||||
fence_lbs_tolerance_meters: int | None = Field(None, ge=0, le=10000)
|
||||
fence_wifi_tolerance_meters: int | None = Field(None, ge=0, le=10000)
|
||||
fence_min_inside_seconds: int | None = Field(None, ge=0, le=3600)
|
||||
track_max_points: int | None = Field(None, ge=100, le=100000)
|
||||
|
||||
|
||||
class ApiKeyCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100, description="Key name / 名称")
|
||||
permissions: Literal["read", "write", "admin"] = Field(default="read", description="Permission level")
|
||||
|
||||
Reference in New Issue
Block a user