2025-08-06 14:45:07 +08:00

385 lines
13 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
import os
import shutil
from datetime import datetime
from pathlib import Path
from core.database import get_db
from models.device import Device
from services.video_processor import video_processor
router = APIRouter()
@router.post("/", summary="创建设备")
async def create_device(
device_data: Dict[str, Any],
db: Session = Depends(get_db)
):
"""创建新的设备"""
db_device = Device(**device_data)
db.add(db_device)
db.commit()
db.refresh(db_device)
return {
"id": db_device.id,
"name": db_device.name,
"device_type": db_device.device_type,
"ip_address": db_device.ip_address,
"port": db_device.port,
"username": db_device.username,
"password": db_device.password,
"location": db_device.location,
"status": db_device.status,
"is_enabled": db_device.is_enabled,
"description": db_device.description,
"created_at": db_device.created_at,
"updated_at": db_device.updated_at
}
@router.get("/", summary="获取设备列表")
async def get_devices(
skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(10, ge=1, le=100, description="返回记录数"),
name: Optional[str] = Query(None, description="设备名称"),
device_type: Optional[str] = Query(None, description="设备类型"),
status: Optional[str] = Query(None, description="设备状态"),
location: Optional[str] = Query(None, description="设备位置"),
is_enabled: Optional[bool] = Query(None, description="是否启用"),
db: Session = Depends(get_db)
):
"""获取设备列表,支持分页和筛选"""
query = db.query(Device)
if name:
query = query.filter(Device.name.contains(name))
if device_type:
query = query.filter(Device.device_type == device_type)
if status:
query = query.filter(Device.status == status)
if location:
query = query.filter(Device.location.contains(location))
if is_enabled is not None:
query = query.filter(Device.is_enabled == is_enabled)
total = query.count()
devices = query.offset(skip).limit(limit).all()
device_list = []
for device in devices:
device_list.append({
"id": device.id,
"name": device.name,
"device_type": device.device_type,
"ip_address": device.ip_address,
"port": device.port,
"username": device.username,
"password": device.password,
"location": device.location,
"status": device.status,
"is_enabled": device.is_enabled,
"description": device.description,
"demo_video_path": device.demo_video_path,
"processed_video_path": device.processed_video_path,
"processing_status": device.processing_status,
"last_processed_at": device.last_processed_at.isoformat() if device.last_processed_at else None,
"created_at": device.created_at,
"updated_at": device.updated_at
})
return {
"devices": device_list,
"total": total,
"page": skip // limit + 1,
"size": limit
}
@router.get("/{device_id}", summary="获取设备详情")
async def get_device(
device_id: int,
db: Session = Depends(get_db)
):
"""根据ID获取设备详情"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
return {
"id": device.id,
"name": device.name,
"device_type": device.device_type,
"ip_address": device.ip_address,
"port": device.port,
"username": device.username,
"password": device.password,
"location": device.location,
"status": device.status,
"is_enabled": device.is_enabled,
"description": device.description,
"demo_video_path": device.demo_video_path,
"processed_video_path": device.processed_video_path,
"processing_status": device.processing_status,
"processing_result": device.processing_result,
"last_processed_at": device.last_processed_at.isoformat() if device.last_processed_at else None,
"created_at": device.created_at,
"updated_at": device.updated_at
}
@router.put("/{device_id}", summary="更新设备")
async def update_device(
device_id: int,
device_data: Dict[str, Any],
db: Session = Depends(get_db)
):
"""更新设备信息"""
db_device = db.query(Device).filter(Device.id == device_id).first()
if not db_device:
raise HTTPException(status_code=404, detail="设备不存在")
for field, value in device_data.items():
if hasattr(db_device, field):
setattr(db_device, field, value)
db.commit()
db.refresh(db_device)
return {
"id": db_device.id,
"name": db_device.name,
"device_type": db_device.device_type,
"ip_address": db_device.ip_address,
"port": db_device.port,
"username": db_device.username,
"password": db_device.password,
"location": db_device.location,
"status": db_device.status,
"is_enabled": db_device.is_enabled,
"description": db_device.description,
"created_at": db_device.created_at,
"updated_at": db_device.updated_at
}
@router.delete("/{device_id}", summary="删除设备")
async def delete_device(
device_id: int,
db: Session = Depends(get_db)
):
"""删除设备"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
db.delete(device)
db.commit()
return {"message": "设备删除成功"}
@router.patch("/{device_id}/status", summary="更新设备状态")
async def update_device_status(
device_id: int,
status: str = Query(..., description="新状态"),
db: Session = Depends(get_db)
):
"""更新设备状态"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
device.status = status
db.commit()
db.refresh(device)
return {
"id": device.id,
"name": device.name,
"device_type": device.device_type,
"ip_address": device.ip_address,
"port": device.port,
"username": device.username,
"password": device.password,
"location": device.location,
"status": device.status,
"is_enabled": device.is_enabled,
"description": device.description,
"created_at": device.created_at,
"updated_at": device.updated_at
}
@router.patch("/{device_id}/enable", summary="启用/禁用设备")
async def toggle_device_enabled(
device_id: int,
enabled: bool = Query(..., description="是否启用"),
db: Session = Depends(get_db)
):
"""启用或禁用设备"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
device.is_enabled = enabled
db.commit()
db.refresh(device)
return {
"id": device.id,
"name": device.name,
"device_type": device.device_type,
"ip_address": device.ip_address,
"port": device.port,
"username": device.username,
"password": device.password,
"location": device.location,
"status": device.status,
"is_enabled": device.is_enabled,
"description": device.description,
"created_at": device.created_at,
"updated_at": device.updated_at
}
@router.post("/{device_id}/upload-demo-video", summary="上传演示视频")
async def upload_demo_video(
device_id: int,
video_file: UploadFile = File(...),
background_tasks: BackgroundTasks = None,
db: Session = Depends(get_db)
):
"""上传设备的演示视频并开始处理"""
# 检查设备是否存在
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
# 检查设备是否关联了算法
if not device.algorithm_id:
raise HTTPException(status_code=400, detail="设备未关联算法,无法处理视频")
# 检查文件类型
if not video_file.filename.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')):
raise HTTPException(status_code=400, detail="只支持视频文件格式")
# 创建上传目录
uploads_dir = Path(__file__).parent.parent / "uploads" / "videos"
uploads_dir.mkdir(parents=True, exist_ok=True)
# 生成文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"demo_{device_id}_{timestamp}_{video_file.filename}"
file_path = uploads_dir / filename
try:
# 保存上传的文件
with open(file_path, "wb") as buffer:
shutil.copyfileobj(video_file.file, buffer)
# 更新设备记录
device.demo_video_path = str(file_path)
device.processing_status = "idle"
device.processed_video_path = None
device.processing_result = None
db.commit()
# 后台处理视频
if background_tasks:
background_tasks.add_task(
video_processor.process_device_video,
device_id,
str(file_path)
)
return {
"success": True,
"message": "演示视频上传成功,开始处理",
"device_id": device_id,
"demo_video_path": str(file_path),
"processing_status": "processing"
}
except Exception as e:
# 清理已上传的文件
if file_path.exists():
file_path.unlink()
raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}")
@router.get("/{device_id}/processing-status", summary="获取视频处理状态")
async def get_video_processing_status(
device_id: int,
db: Session = Depends(get_db)
):
"""获取设备的视频处理状态"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
return {
"device_id": device_id,
"processing_status": device.processing_status,
"demo_video_path": device.demo_video_path,
"processed_video_path": device.processed_video_path,
"last_processed_at": device.last_processed_at.isoformat() if device.last_processed_at else None,
"processing_result": device.processing_result
}
@router.post("/{device_id}/process-video", summary="手动触发视频处理")
async def process_device_video(
device_id: int,
background_tasks: BackgroundTasks = None,
db: Session = Depends(get_db)
):
"""手动触发设备的视频处理"""
device = db.query(Device).filter(Device.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
if not device.demo_video_path:
raise HTTPException(status_code=400, detail="设备没有上传演示视频")
if not device.algorithm_id:
raise HTTPException(status_code=400, detail="设备未关联算法")
# 检查演示视频文件是否存在
if not os.path.exists(device.demo_video_path):
raise HTTPException(status_code=400, detail="演示视频文件不存在")
# 后台处理视频
if background_tasks:
background_tasks.add_task(
video_processor.process_device_video,
device_id,
device.demo_video_path
)
return {
"success": True,
"message": "视频处理已开始",
"device_id": device_id,
"processing_status": "processing"
}
@router.get("/types/list", summary="获取设备类型列表")
async def get_device_types():
"""获取所有设备类型"""
return {
"types": [
{"value": "camera", "label": "摄像头"},
{"value": "sensor", "label": "传感器"},
{"value": "gate", "label": "门禁"},
{"value": "alarm", "label": "报警器"},
{"value": "other", "label": "其他"}
]
}
@router.get("/status/stats", summary="获取设备状态统计")
async def get_device_status_stats(db: Session = Depends(get_db)):
"""获取设备状态统计信息"""
total = db.query(Device).count()
online = db.query(Device).filter(Device.status == "online").count()
offline = db.query(Device).filter(Device.status == "offline").count()
error = db.query(Device).filter(Device.status == "error").count()
return {
"total": total,
"online": online,
"offline": offline,
"error": error,
"online_rate": round(online / total * 100, 2) if total > 0 else 0
}