diff --git a/app/routers/yudao_aiot_alarm.py b/app/routers/yudao_aiot_alarm.py new file mode 100644 index 0000000..4cf5769 --- /dev/null +++ b/app/routers/yudao_aiot_alarm.py @@ -0,0 +1,224 @@ +""" +AIoT 告警路由 - 芋道规范 + +统一到 /admin-api/aiot/alarm 命名空间,与 aiot 平台架构对齐。 + +API 路径规范: + - /admin-api/aiot/alarm/alert/page - 分页查询 + - /admin-api/aiot/alarm/alert/get - 获取详情 + - /admin-api/aiot/alarm/alert/handle - 处理告警 + - /admin-api/aiot/alarm/alert/delete - 删除告警 + - /admin-api/aiot/alarm/alert/statistics - 获取统计 + - /admin-api/aiot/alarm/camera-summary/page - 摄像头汇总 +""" + +from fastapi import APIRouter, Query, Depends, HTTPException +from typing import Optional +from datetime import datetime + +from app.yudao_compat import YudaoResponse, get_current_user +from app.services.alert_service import get_alert_service, AlertService +from app.schemas import AlertHandleRequest + +router = APIRouter(prefix="/admin-api/aiot/alarm", tags=["AIoT-告警"]) + + +# ==================== 告警管理 ==================== + +@router.get("/alert/page") +async def get_alert_page( + pageNo: int = Query(1, ge=1, description="页码"), + pageSize: int = Query(20, ge=1, le=100, description="每页大小"), + cameraId: Optional[str] = Query(None, description="摄像头ID"), + deviceId: Optional[str] = Query(None, description="设备ID"), + alertType: Optional[str] = Query(None, description="告警类型"), + status: Optional[str] = Query(None, description="状态: pending/confirmed/ignored/resolved/dispatched"), + level: Optional[str] = Query(None, description="级别: low/medium/high/critical"), + startTime: Optional[datetime] = Query(None, description="开始时间"), + endTime: Optional[datetime] = Query(None, description="结束时间"), + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """分页查询告警列表""" + alerts, total = service.get_alerts( + camera_id=cameraId, + device_id=deviceId, + alert_type=alertType, + status=status, + level=level, + start_time=startTime, + end_time=endTime, + page=pageNo, + page_size=pageSize, + ) + + alert_list = [] + for alert in alerts: + alert_dict = alert.to_dict() + alert_list.append({ + "id": alert_dict.get("id"), + "alertNo": alert_dict.get("alert_no"), + "cameraId": alert_dict.get("camera_id"), + "cameraName": alert_dict.get("camera_id"), + "roiId": alert_dict.get("roi_id"), + "bindId": alert_dict.get("bind_id"), + "deviceId": alert_dict.get("device_id"), + "alertType": alert_dict.get("alert_type"), + "alertTypeName": _get_alert_type_name(alert_dict.get("alert_type")), + "algorithm": alert_dict.get("algorithm"), + "confidence": alert_dict.get("confidence"), + "durationMinutes": alert_dict.get("duration_minutes"), + "triggerTime": alert_dict.get("trigger_time"), + "message": alert_dict.get("message"), + "bbox": alert_dict.get("bbox"), + "snapshotUrl": alert_dict.get("snapshot_url"), + "ossUrl": alert_dict.get("oss_url"), + "status": alert_dict.get("status"), + "level": alert_dict.get("level"), + "handleRemark": alert_dict.get("handle_remark"), + "handledBy": alert_dict.get("handled_by"), + "handledAt": alert_dict.get("handled_at"), + "workOrderId": alert_dict.get("work_order_id"), + "aiAnalysis": alert_dict.get("ai_analysis"), + "createdAt": alert_dict.get("created_at"), + "updatedAt": alert_dict.get("updated_at"), + }) + + return YudaoResponse.page( + list_data=alert_list, + total=total, + page_no=pageNo, + page_size=pageSize + ) + + +@router.get("/alert/get") +async def get_alert( + id: int = Query(..., description="告警ID"), + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """获取告警详情""" + alert = service.get_alert(id) + if not alert: + raise HTTPException(status_code=404, detail="告警不存在") + + alert_dict = alert.to_dict() + return YudaoResponse.success({ + "id": alert_dict.get("id"), + "alertNo": alert_dict.get("alert_no"), + "cameraId": alert_dict.get("camera_id"), + "cameraName": alert_dict.get("camera_id"), + "roiId": alert_dict.get("roi_id"), + "bindId": alert_dict.get("bind_id"), + "deviceId": alert_dict.get("device_id"), + "alertType": alert_dict.get("alert_type"), + "alertTypeName": _get_alert_type_name(alert_dict.get("alert_type")), + "algorithm": alert_dict.get("algorithm"), + "confidence": alert_dict.get("confidence"), + "durationMinutes": alert_dict.get("duration_minutes"), + "triggerTime": alert_dict.get("trigger_time"), + "message": alert_dict.get("message"), + "bbox": alert_dict.get("bbox"), + "snapshotUrl": alert_dict.get("snapshot_url"), + "ossUrl": alert_dict.get("oss_url"), + "status": alert_dict.get("status"), + "level": alert_dict.get("level"), + "handleRemark": alert_dict.get("handle_remark"), + "handledBy": alert_dict.get("handled_by"), + "handledAt": alert_dict.get("handled_at"), + "workOrderId": alert_dict.get("work_order_id"), + "aiAnalysis": alert_dict.get("ai_analysis"), + "createdAt": alert_dict.get("created_at"), + "updatedAt": alert_dict.get("updated_at"), + }) + + +@router.put("/alert/handle") +async def handle_alert( + id: int = Query(..., description="告警ID"), + status: str = Query(..., description="处理状态: confirmed/ignored/resolved"), + remark: Optional[str] = Query(None, description="处理备注"), + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """处理告警""" + handle_data = AlertHandleRequest(status=status, remark=remark) + handled_by = current_user.get("username", "admin") + + alert = service.handle_alert(id, handle_data, handled_by) + if not alert: + raise HTTPException(status_code=404, detail="告警不存在") + + return YudaoResponse.success(True) + + +@router.delete("/alert/delete") +async def delete_alert( + id: int = Query(..., description="告警ID"), + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """删除告警""" + success = service.delete_alert(id) + if not success: + raise HTTPException(status_code=404, detail="告警不存在") + + return YudaoResponse.success(True) + + +@router.get("/alert/statistics") +async def get_statistics( + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """获取告警统计""" + stats = service.get_statistics() + + return YudaoResponse.success({ + "total": stats.get("total", 0), + "pending": stats.get("pending", 0), + "confirmed": stats.get("confirmed", 0), + "ignored": stats.get("ignored", 0), + "resolved": stats.get("resolved", 0), + "dispatched": stats.get("dispatched", 0), + "byType": stats.get("by_type", {}), + "byLevel": stats.get("by_level", {}), + }) + + +# ==================== 摄像头告警汇总 ==================== + +@router.get("/camera-summary/page") +async def get_camera_summary_page( + pageNo: int = Query(1, ge=1, description="页码"), + pageSize: int = Query(20, ge=1, le=100, description="每页大小"), + service: AlertService = Depends(get_alert_service), + current_user: dict = Depends(get_current_user) +): + """获取摄像头告警汇总(分页)""" + result = service.get_camera_alert_summary(page=pageNo, page_size=pageSize) + + return YudaoResponse.page( + list_data=result.get("list", []), + total=result.get("total", 0), + page_no=pageNo, + page_size=pageSize + ) + + +# ==================== 辅助函数 ==================== + +def _get_alert_type_name(alert_type: Optional[str]) -> str: + """获取告警类型名称""" + type_names = { + "leave_post": "离岗检测", + "intrusion": "周界入侵", + "crowd": "人群聚集", + "fire": "火焰检测", + "smoke": "烟雾检测", + "fall": "跌倒检测", + "helmet": "安全帽检测", + "unknown": "未知类型", + } + return type_names.get(alert_type, alert_type or "未知类型") diff --git a/app/routers/yudao_aiot_edge.py b/app/routers/yudao_aiot_edge.py new file mode 100644 index 0000000..96b11ca --- /dev/null +++ b/app/routers/yudao_aiot_edge.py @@ -0,0 +1,119 @@ +""" +AIoT 边缘设备路由 - 芋道规范 + +统一到 /admin-api/aiot/edge 命名空间,与 aiot 平台架构对齐。 + +API 路径规范: + - /admin-api/aiot/edge/device/page - 分页查询设备 + - /admin-api/aiot/edge/device/get - 获取设备详情 + - /admin-api/aiot/edge/device/statistics - 设备统计 +""" + +from fastapi import APIRouter, Query, Depends, HTTPException +from typing import Optional + +from app.yudao_compat import YudaoResponse, get_current_user +from app.services.device_service import get_device_service, DeviceService + +router = APIRouter(prefix="/admin-api/aiot/edge", tags=["AIoT-边缘设备"]) + + +@router.get("/device/page") +async def get_device_page( + pageNo: int = Query(1, ge=1, description="页码"), + pageSize: int = Query(20, ge=1, le=100, description="每页大小"), + status: Optional[str] = Query(None, description="设备状态: online/offline/error"), + service: DeviceService = Depends(get_device_service), + current_user: dict = Depends(get_current_user) +): + """分页查询边缘设备列表""" + devices, total = service.get_devices( + status=status, + page=pageNo, + page_size=pageSize, + ) + + device_list = [] + for device in devices: + device_dict = device.to_dict() + device_list.append({ + "id": device_dict.get("id"), + "deviceId": device_dict.get("device_id"), + "deviceName": device_dict.get("device_name"), + "status": device_dict.get("status"), + "statusName": _get_status_name(device_dict.get("status")), + "lastHeartbeat": device_dict.get("last_heartbeat"), + "uptimeSeconds": device_dict.get("uptime_seconds"), + "framesProcessed": device_dict.get("frames_processed"), + "alertsGenerated": device_dict.get("alerts_generated"), + "ipAddress": device_dict.get("ip_address"), + "streamCount": device_dict.get("stream_count"), + "configVersion": device_dict.get("config_version"), + "extraInfo": device_dict.get("extra_info"), + "updatedAt": device_dict.get("updated_at"), + }) + + return YudaoResponse.page( + list_data=device_list, + total=total, + page_no=pageNo, + page_size=pageSize + ) + + +@router.get("/device/get") +async def get_device( + id: str = Query(..., description="设备ID"), + service: DeviceService = Depends(get_device_service), + current_user: dict = Depends(get_current_user) +): + """获取设备详情""" + device = service.get_device(id) + if not device: + raise HTTPException(status_code=404, detail="设备不存在") + + device_dict = device.to_dict() + return YudaoResponse.success({ + "id": device_dict.get("id"), + "deviceId": device_dict.get("device_id"), + "deviceName": device_dict.get("device_name"), + "status": device_dict.get("status"), + "statusName": _get_status_name(device_dict.get("status")), + "lastHeartbeat": device_dict.get("last_heartbeat"), + "uptimeSeconds": device_dict.get("uptime_seconds"), + "framesProcessed": device_dict.get("frames_processed"), + "alertsGenerated": device_dict.get("alerts_generated"), + "ipAddress": device_dict.get("ip_address"), + "streamCount": device_dict.get("stream_count"), + "configVersion": device_dict.get("config_version"), + "extraInfo": device_dict.get("extra_info"), + "updatedAt": device_dict.get("updated_at"), + }) + + +@router.get("/device/statistics") +async def get_device_statistics( + service: DeviceService = Depends(get_device_service), + current_user: dict = Depends(get_current_user) +): + """获取设备统计""" + stats = service.get_statistics() + + return YudaoResponse.success({ + "total": stats.get("total", 0), + "online": stats.get("online", 0), + "offline": stats.get("offline", 0), + "error": stats.get("error", 0), + }) + + +# ==================== 辅助函数 ==================== + +def _get_status_name(status: Optional[str]) -> str: + """获取设备状态名称""" + status_names = { + "online": "在线", + "offline": "离线", + "error": "异常", + } + return status_names.get(status, status or "未知")