refactor(service): 删除MQTT旧代码 + 修复边缘节点重复显示问题
**删除MQTT旧代码:** - 删除 mqtt_service.py(已废弃的空壳) - 从 config.py 删除 MQTTConfig 类和相关配置 - 从 schemas.py 删除 mqtt 字段 - 从 alert_service.py 删除 create_alert_from_mqtt 方法 - 告警上报已改为 HTTP + COS 方案,MQTT机制完全废弃 **修复边缘节点重复显示(方案A):** - 清理 edge_devices 表历史数据(删除 edge_device_001、edge_inference_device) - 禁用 DeviceService 的 handle_heartbeat 自动创建设备功能 - 边缘端未实现心跳机制,告警数从 alarm_event 表统计 - 运行时长、处理帧数字段设为 null(无心跳机制,不可用) - 添加 count_alarms_by_edge_node 方法统计边缘节点告警数 **影响范围:** - /admin-api/aiot/edge/device/page 接口返回数据调整 - /admin-api/aiot/edge/device/get 接口返回数据调整 - 确保不破坏现有功能(告警上报已改为HTTP)
This commit is contained in:
@@ -43,20 +43,6 @@ class AIModelConfig:
|
|||||||
api_key: str = ""
|
api_key: str = ""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MQTTConfig:
|
|
||||||
"""MQTT 配置 (已废弃 - 告警上报已改为 HTTP + COS)"""
|
|
||||||
broker_host: str = "localhost"
|
|
||||||
broker_port: int = 1883
|
|
||||||
client_id: str = "alert_platform"
|
|
||||||
username: str = ""
|
|
||||||
password: str = ""
|
|
||||||
alert_topic: str = "edge/alert/#"
|
|
||||||
heartbeat_topic: str = "edge/alert/heartbeat/#"
|
|
||||||
qos: int = 1
|
|
||||||
enabled: bool = False # 默认禁用
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RedisConfig:
|
class RedisConfig:
|
||||||
"""Redis 配置"""
|
"""Redis 配置"""
|
||||||
@@ -103,7 +89,6 @@ class Settings(BaseModel):
|
|||||||
cos: COSConfig = COSConfig()
|
cos: COSConfig = COSConfig()
|
||||||
app: AppConfig = AppConfig()
|
app: AppConfig = AppConfig()
|
||||||
ai_model: AIModelConfig = AIModelConfig()
|
ai_model: AIModelConfig = AIModelConfig()
|
||||||
mqtt: MQTTConfig = MQTTConfig()
|
|
||||||
redis: RedisConfig = RedisConfig()
|
redis: RedisConfig = RedisConfig()
|
||||||
camera_name: CameraNameConfig = CameraNameConfig()
|
camera_name: CameraNameConfig = CameraNameConfig()
|
||||||
|
|
||||||
@@ -137,17 +122,6 @@ def load_settings() -> Settings:
|
|||||||
endpoint=os.getenv("AI_MODEL_ENDPOINT", ""),
|
endpoint=os.getenv("AI_MODEL_ENDPOINT", ""),
|
||||||
api_key=os.getenv("AI_MODEL_API_KEY", ""),
|
api_key=os.getenv("AI_MODEL_API_KEY", ""),
|
||||||
),
|
),
|
||||||
mqtt=MQTTConfig(
|
|
||||||
broker_host=os.getenv("MQTT_BROKER_HOST", "localhost"),
|
|
||||||
broker_port=int(os.getenv("MQTT_BROKER_PORT", "1883")),
|
|
||||||
client_id=os.getenv("MQTT_CLIENT_ID", "alert_platform"),
|
|
||||||
username=os.getenv("MQTT_USERNAME", ""),
|
|
||||||
password=os.getenv("MQTT_PASSWORD", ""),
|
|
||||||
alert_topic=os.getenv("MQTT_ALERT_TOPIC", "edge/alert/#"),
|
|
||||||
heartbeat_topic=os.getenv("MQTT_HEARTBEAT_TOPIC", "edge/alert/heartbeat/#"),
|
|
||||||
qos=int(os.getenv("MQTT_QOS", "1")),
|
|
||||||
enabled=os.getenv("MQTT_ENABLED", "false").lower() == "true",
|
|
||||||
),
|
|
||||||
redis=RedisConfig(
|
redis=RedisConfig(
|
||||||
host=os.getenv("REDIS_HOST", "localhost"),
|
host=os.getenv("REDIS_HOST", "localhost"),
|
||||||
port=int(os.getenv("REDIS_PORT", "6379")),
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ API 路径规范:
|
|||||||
- /admin-api/aiot/edge/device/page - 分页查询设备
|
- /admin-api/aiot/edge/device/page - 分页查询设备
|
||||||
- /admin-api/aiot/edge/device/get - 获取设备详情
|
- /admin-api/aiot/edge/device/get - 获取设备详情
|
||||||
- /admin-api/aiot/edge/device/statistics - 设备统计
|
- /admin-api/aiot/edge/device/statistics - 设备统计
|
||||||
|
|
||||||
|
注意:边缘端未实现心跳机制,因此运行时长/处理帧数等实时指标不可用。
|
||||||
|
告警数统计从 alarm_event 表中提取 edge_node_id 字段。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Query, Depends, HTTPException
|
from fastapi import APIRouter, Query, Depends, HTTPException
|
||||||
@@ -14,6 +17,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from app.yudao_compat import YudaoResponse, get_current_user
|
from app.yudao_compat import YudaoResponse, get_current_user
|
||||||
from app.services.device_service import get_device_service, DeviceService
|
from app.services.device_service import get_device_service, DeviceService
|
||||||
|
from app.services.alarm_event_service import get_alarm_event_service, AlarmEventService
|
||||||
|
|
||||||
router = APIRouter(prefix="/admin-api/aiot/edge", tags=["AIoT-边缘设备"])
|
router = APIRouter(prefix="/admin-api/aiot/edge", tags=["AIoT-边缘设备"])
|
||||||
|
|
||||||
@@ -24,9 +28,10 @@ async def get_device_page(
|
|||||||
pageSize: int = Query(20, ge=1, le=100, description="每页大小"),
|
pageSize: int = Query(20, ge=1, le=100, description="每页大小"),
|
||||||
status: Optional[str] = Query(None, description="设备状态: online/offline/error"),
|
status: Optional[str] = Query(None, description="设备状态: online/offline/error"),
|
||||||
service: DeviceService = Depends(get_device_service),
|
service: DeviceService = Depends(get_device_service),
|
||||||
|
alarm_service: AlarmEventService = Depends(get_alarm_event_service),
|
||||||
current_user: dict = Depends(get_current_user)
|
current_user: dict = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""分页查询边缘设备列表"""
|
"""分页查询边缘设备列表(告警数从 alarm_event 表统计)"""
|
||||||
devices, total = service.get_devices(
|
devices, total = service.get_devices(
|
||||||
status=status,
|
status=status,
|
||||||
page=pageNo,
|
page=pageNo,
|
||||||
@@ -36,16 +41,21 @@ async def get_device_page(
|
|||||||
device_list = []
|
device_list = []
|
||||||
for device in devices:
|
for device in devices:
|
||||||
device_dict = device.to_dict()
|
device_dict = device.to_dict()
|
||||||
|
device_id = device_dict.get("device_id")
|
||||||
|
|
||||||
|
# 从 alarm_event 表统计告警数
|
||||||
|
alerts_count = alarm_service.count_alarms_by_edge_node(device_id)
|
||||||
|
|
||||||
device_list.append({
|
device_list.append({
|
||||||
"id": device_dict.get("id"),
|
"id": device_dict.get("id"),
|
||||||
"deviceId": device_dict.get("device_id"),
|
"deviceId": device_id,
|
||||||
"deviceName": device_dict.get("device_name"),
|
"deviceName": device_dict.get("device_name"),
|
||||||
"status": device_dict.get("status"),
|
"status": device_dict.get("status"),
|
||||||
"statusName": _get_status_name(device_dict.get("status")),
|
"statusName": _get_status_name(device_dict.get("status")),
|
||||||
"lastHeartbeat": device_dict.get("last_heartbeat"),
|
"lastHeartbeat": device_dict.get("last_heartbeat"),
|
||||||
"uptimeSeconds": device_dict.get("uptime_seconds"),
|
"uptimeSeconds": None, # 无心跳机制,不可用
|
||||||
"framesProcessed": device_dict.get("frames_processed"),
|
"framesProcessed": None, # 无心跳机制,不可用
|
||||||
"alertsGenerated": device_dict.get("alerts_generated"),
|
"alertsGenerated": alerts_count, # 从 alarm_event 表统计
|
||||||
"ipAddress": device_dict.get("ip_address"),
|
"ipAddress": device_dict.get("ip_address"),
|
||||||
"streamCount": device_dict.get("stream_count"),
|
"streamCount": device_dict.get("stream_count"),
|
||||||
"configVersion": device_dict.get("config_version"),
|
"configVersion": device_dict.get("config_version"),
|
||||||
@@ -65,24 +75,30 @@ async def get_device_page(
|
|||||||
async def get_device(
|
async def get_device(
|
||||||
id: str = Query(..., description="设备ID"),
|
id: str = Query(..., description="设备ID"),
|
||||||
service: DeviceService = Depends(get_device_service),
|
service: DeviceService = Depends(get_device_service),
|
||||||
|
alarm_service: AlarmEventService = Depends(get_alarm_event_service),
|
||||||
current_user: dict = Depends(get_current_user)
|
current_user: dict = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""获取设备详情"""
|
"""获取设备详情(告警数从 alarm_event 表统计)"""
|
||||||
device = service.get_device(id)
|
device = service.get_device(id)
|
||||||
if not device:
|
if not device:
|
||||||
raise HTTPException(status_code=404, detail="设备不存在")
|
raise HTTPException(status_code=404, detail="设备不存在")
|
||||||
|
|
||||||
device_dict = device.to_dict()
|
device_dict = device.to_dict()
|
||||||
|
device_id = device_dict.get("device_id")
|
||||||
|
|
||||||
|
# 从 alarm_event 表统计告警数
|
||||||
|
alerts_count = alarm_service.count_alarms_by_edge_node(device_id)
|
||||||
|
|
||||||
return YudaoResponse.success({
|
return YudaoResponse.success({
|
||||||
"id": device_dict.get("id"),
|
"id": device_dict.get("id"),
|
||||||
"deviceId": device_dict.get("device_id"),
|
"deviceId": device_id,
|
||||||
"deviceName": device_dict.get("device_name"),
|
"deviceName": device_dict.get("device_name"),
|
||||||
"status": device_dict.get("status"),
|
"status": device_dict.get("status"),
|
||||||
"statusName": _get_status_name(device_dict.get("status")),
|
"statusName": _get_status_name(device_dict.get("status")),
|
||||||
"lastHeartbeat": device_dict.get("last_heartbeat"),
|
"lastHeartbeat": device_dict.get("last_heartbeat"),
|
||||||
"uptimeSeconds": device_dict.get("uptime_seconds"),
|
"uptimeSeconds": None, # 无心跳机制,不可用
|
||||||
"framesProcessed": device_dict.get("frames_processed"),
|
"framesProcessed": None, # 无心跳机制,不可用
|
||||||
"alertsGenerated": device_dict.get("alerts_generated"),
|
"alertsGenerated": alerts_count, # 从 alarm_event 表统计
|
||||||
"ipAddress": device_dict.get("ip_address"),
|
"ipAddress": device_dict.get("ip_address"),
|
||||||
"streamCount": device_dict.get("stream_count"),
|
"streamCount": device_dict.get("stream_count"),
|
||||||
"configVersion": device_dict.get("config_version"),
|
"configVersion": device_dict.get("config_version"),
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ class DeviceStatisticsResponse(BaseModel):
|
|||||||
class HealthResponse(BaseModel):
|
class HealthResponse(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
database: str
|
database: str
|
||||||
mqtt: Optional[str] = None
|
|
||||||
websocket_connections: Optional[int] = None
|
websocket_connections: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -588,6 +588,20 @@ class AlarmEventService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
def count_alarms_by_edge_node(self, edge_node_id: str) -> int:
|
||||||
|
"""统计指定边缘节点的告警数量"""
|
||||||
|
db = get_session()
|
||||||
|
try:
|
||||||
|
count = db.query(AlarmEvent).filter(
|
||||||
|
AlarmEvent.edge_node_id == edge_node_id
|
||||||
|
).count()
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"统计边缘节点告警数失败: {e}")
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
def save_llm_analysis(
|
def save_llm_analysis(
|
||||||
self,
|
self,
|
||||||
alarm_id: str,
|
alarm_id: str,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
告警服务
|
告警服务
|
||||||
处理告警 CRUD 和 MQTT 消息
|
处理告警 CRUD(旧版告警系统,已迁移至 alarm_event_service)
|
||||||
"""
|
"""
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -63,64 +63,6 @@ class AlertService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
def create_alert_from_mqtt(self, mqtt_data: Dict[str, Any]) -> Optional[Alert]:
|
|
||||||
"""从 MQTT 消息创建告警"""
|
|
||||||
db = get_session()
|
|
||||||
try:
|
|
||||||
# 解析时间
|
|
||||||
trigger_time_str = mqtt_data.get("timestamp")
|
|
||||||
if trigger_time_str:
|
|
||||||
try:
|
|
||||||
trigger_time = datetime.fromisoformat(trigger_time_str.replace("Z", "+00:00"))
|
|
||||||
except ValueError:
|
|
||||||
trigger_time = datetime.now(timezone.utc)
|
|
||||||
else:
|
|
||||||
trigger_time = datetime.now(timezone.utc)
|
|
||||||
|
|
||||||
# 解析置信度(MQTT 可能是 0-1,需要转为 0-100)
|
|
||||||
confidence = mqtt_data.get("confidence")
|
|
||||||
if confidence is not None:
|
|
||||||
if isinstance(confidence, float) and confidence <= 1:
|
|
||||||
confidence = int(confidence * 100)
|
|
||||||
else:
|
|
||||||
confidence = int(confidence)
|
|
||||||
|
|
||||||
# 解析持续时长
|
|
||||||
duration_minutes = mqtt_data.get("duration_minutes")
|
|
||||||
if duration_minutes is not None:
|
|
||||||
duration_minutes = int(float(duration_minutes))
|
|
||||||
|
|
||||||
alert = Alert(
|
|
||||||
alert_no=self.generate_alert_no(),
|
|
||||||
camera_id=mqtt_data.get("camera_id", "unknown"),
|
|
||||||
roi_id=mqtt_data.get("roi_id"),
|
|
||||||
bind_id=mqtt_data.get("bind_id"),
|
|
||||||
device_id=mqtt_data.get("device_id"),
|
|
||||||
alert_type=mqtt_data.get("alert_type", "unknown"),
|
|
||||||
algorithm=mqtt_data.get("algorithm", "YOLO"),
|
|
||||||
confidence=confidence,
|
|
||||||
duration_minutes=duration_minutes,
|
|
||||||
trigger_time=trigger_time,
|
|
||||||
message=mqtt_data.get("message"),
|
|
||||||
bbox=mqtt_data.get("bbox"),
|
|
||||||
status=AlertStatus.PENDING,
|
|
||||||
level=self._determine_level(mqtt_data),
|
|
||||||
)
|
|
||||||
|
|
||||||
db.add(alert)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(alert)
|
|
||||||
|
|
||||||
logger.info(f"MQTT告警创建成功: {alert.alert_no}, type={alert.alert_type}")
|
|
||||||
return alert
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
|
||||||
logger.error(f"创建MQTT告警失败: {e}")
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def _determine_level(self, data: Dict[str, Any]) -> AlertLevel:
|
def _determine_level(self, data: Dict[str, Any]) -> AlertLevel:
|
||||||
"""根据告警数据确定告警级别"""
|
"""根据告警数据确定告警级别"""
|
||||||
alert_type = data.get("alert_type", "")
|
alert_type = data.get("alert_type", "")
|
||||||
|
|||||||
@@ -19,66 +19,15 @@ class DeviceService:
|
|||||||
self._devices: Dict[str, Dict[str, Any]] = {} # 内存缓存
|
self._devices: Dict[str, Dict[str, Any]] = {} # 内存缓存
|
||||||
|
|
||||||
def handle_heartbeat(self, heartbeat_data: Dict[str, Any]) -> Optional[EdgeDevice]:
|
def handle_heartbeat(self, heartbeat_data: Dict[str, Any]) -> Optional[EdgeDevice]:
|
||||||
"""处理心跳消息"""
|
"""
|
||||||
device_id = heartbeat_data.get("device_id")
|
处理心跳消息(已废弃 - 边缘端未实现心跳机制)
|
||||||
if not device_id:
|
|
||||||
logger.warning("心跳消息缺少 device_id")
|
保留此方法以兼容旧代码,实际上不会被调用。
|
||||||
|
边缘节点信息应通过手动创建或配置管理。
|
||||||
|
"""
|
||||||
|
logger.warning("handle_heartbeat 被调用,但心跳机制已废弃,忽略")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
status_info = heartbeat_data.get("status", {})
|
|
||||||
|
|
||||||
db = get_session()
|
|
||||||
try:
|
|
||||||
# 查找或创建设备
|
|
||||||
device = db.query(EdgeDevice).filter(
|
|
||||||
EdgeDevice.device_id == device_id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
|
||||||
|
|
||||||
if device is None:
|
|
||||||
# 新设备
|
|
||||||
device = EdgeDevice(
|
|
||||||
device_id=device_id,
|
|
||||||
device_name=f"边缘设备_{device_id[:8]}",
|
|
||||||
status=DeviceStatus.ONLINE,
|
|
||||||
last_heartbeat=now,
|
|
||||||
)
|
|
||||||
db.add(device)
|
|
||||||
logger.info(f"发现新设备: {device_id}")
|
|
||||||
else:
|
|
||||||
# 更新状态
|
|
||||||
old_status = device.status
|
|
||||||
device.status = DeviceStatus.ONLINE
|
|
||||||
device.last_heartbeat = now
|
|
||||||
|
|
||||||
if old_status != DeviceStatus.ONLINE:
|
|
||||||
logger.info(f"设备上线: {device_id}")
|
|
||||||
|
|
||||||
# 更新运行信息
|
|
||||||
if status_info:
|
|
||||||
device.uptime_seconds = status_info.get("uptime_seconds")
|
|
||||||
device.frames_processed = status_info.get("frames_processed")
|
|
||||||
device.alerts_generated = status_info.get("alerts_generated")
|
|
||||||
device.extra_info = status_info.get("stream_stats")
|
|
||||||
|
|
||||||
device.updated_at = now
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
db.refresh(device)
|
|
||||||
|
|
||||||
# 更新内存缓存
|
|
||||||
self._devices[device_id] = device.to_dict()
|
|
||||||
|
|
||||||
return device
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
|
||||||
logger.error(f"处理心跳失败: {e}")
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def check_offline_devices(self) -> List[EdgeDevice]:
|
def check_offline_devices(self) -> List[EdgeDevice]:
|
||||||
"""检查离线设备"""
|
"""检查离线设备"""
|
||||||
db = get_session()
|
db = get_session()
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
"""
|
|
||||||
MQTT 服务 - 已废弃
|
|
||||||
|
|
||||||
告警上报已改为 HTTP + COS 方案(边缘端直传)。
|
|
||||||
此文件保留为空壳,避免其他模块 import 报错。
|
|
||||||
后续版本将彻底删除此文件。
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class MQTTService:
|
|
||||||
"""MQTT 服务 (已废弃,保留空壳兼容旧代码)"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_connected(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def register_alert_handler(self, handler):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def register_heartbeat_handler(self, handler):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_statistics(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"messages_received": 0,
|
|
||||||
"alerts_received": 0,
|
|
||||||
"heartbeats_received": 0,
|
|
||||||
"errors": 0,
|
|
||||||
"connected": False,
|
|
||||||
"running": False,
|
|
||||||
"deprecated": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_mqtt_service: Optional[MQTTService] = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_mqtt_service() -> MQTTService:
|
|
||||||
"""获取 MQTT 服务单例 (已废弃)"""
|
|
||||||
global _mqtt_service
|
|
||||||
if _mqtt_service is None:
|
|
||||||
_mqtt_service = MQTTService()
|
|
||||||
return _mqtt_service
|
|
||||||
Reference in New Issue
Block a user