feat
This commit is contained in:
parent
6e7e065763
commit
eb8af4ba8b
40
server/app.py
Normal file
40
server/app.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from routers import algorithms, events, devices
|
||||||
|
from core.database import engine
|
||||||
|
from models.base import Base
|
||||||
|
|
||||||
|
# 创建数据库表
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="边检CV算法接口服务",
|
||||||
|
description="边检计算机视觉算法管理系统API",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # 生产环境中应该指定具体域名
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册路由
|
||||||
|
app.include_router(algorithms.router, prefix="/api/algorithms", tags=["算法管理"])
|
||||||
|
app.include_router(events.router, prefix="/api/events", tags=["事件管理"])
|
||||||
|
app.include_router(devices.router, prefix="/api/devices", tags=["设备管理"])
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "边检CV算法接口服务"}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy"}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000, reload=True, workers=10)
|
27
server/core/database.py
Normal file
27
server/core/database.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 数据库URL
|
||||||
|
SQLALCHEMY_DATABASE_URL = "sqlite:///./border_inspection.db"
|
||||||
|
|
||||||
|
# 创建数据库引擎
|
||||||
|
engine = create_engine(
|
||||||
|
SQLALCHEMY_DATABASE_URL,
|
||||||
|
connect_args={"check_same_thread": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建会话工厂
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
# 创建基础模型类
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
# 依赖注入函数
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
19
server/env.example
Normal file
19
server/env.example
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 数据库配置
|
||||||
|
DATABASE_URL=sqlite:///./border_inspection.db
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=8000
|
||||||
|
|
||||||
|
# 安全配置
|
||||||
|
SECRET_KEY=your-secret-key-here
|
||||||
|
ALGORITHM=HS256
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
|
||||||
|
# 文件上传配置
|
||||||
|
UPLOAD_DIR=./uploads
|
||||||
|
MAX_FILE_SIZE=10485760 # 10MB
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_FILE=./logs/app.log
|
219
server/init_data.py
Normal file
219
server/init_data.py
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
初始化示例数据脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from core.database import SessionLocal
|
||||||
|
from models.algorithm import Algorithm
|
||||||
|
from models.device import Device
|
||||||
|
from models.event import Event
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
|
||||||
|
def init_sample_data():
|
||||||
|
"""初始化示例数据"""
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建示例算法
|
||||||
|
algorithms = [
|
||||||
|
{
|
||||||
|
"name": "YOLOv11n人员检测",
|
||||||
|
"description": "基于YOLOv11n的人员检测算法,适用于边检场景",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"model_path": "/models/yolo11n.pt",
|
||||||
|
"config_path": "/configs/yolo11n.yaml",
|
||||||
|
"status": "active",
|
||||||
|
"accuracy": 0.95,
|
||||||
|
"detection_classes": json.dumps(["person"]),
|
||||||
|
"input_size": "640x640",
|
||||||
|
"inference_time": 15.5,
|
||||||
|
"is_enabled": True,
|
||||||
|
"creator": "admin",
|
||||||
|
"tags": json.dumps(["person", "detection", "yolo"])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "车辆检测算法",
|
||||||
|
"description": "专门用于车辆检测的深度学习算法",
|
||||||
|
"version": "2.1.0",
|
||||||
|
"model_path": "/models/vehicle_detection.pt",
|
||||||
|
"config_path": "/configs/vehicle.yaml",
|
||||||
|
"status": "active",
|
||||||
|
"accuracy": 0.92,
|
||||||
|
"detection_classes": json.dumps(["car", "truck", "bus", "motorcycle"]),
|
||||||
|
"input_size": "640x640",
|
||||||
|
"inference_time": 18.2,
|
||||||
|
"is_enabled": True,
|
||||||
|
"creator": "admin",
|
||||||
|
"tags": json.dumps(["vehicle", "detection"])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "人脸识别算法",
|
||||||
|
"description": "高精度人脸识别算法",
|
||||||
|
"version": "1.5.0",
|
||||||
|
"model_path": "/models/face_recognition.pt",
|
||||||
|
"config_path": "/configs/face.yaml",
|
||||||
|
"status": "inactive",
|
||||||
|
"accuracy": 0.98,
|
||||||
|
"detection_classes": json.dumps(["face"]),
|
||||||
|
"input_size": "512x512",
|
||||||
|
"inference_time": 25.0,
|
||||||
|
"is_enabled": False,
|
||||||
|
"creator": "admin",
|
||||||
|
"tags": json.dumps(["face", "recognition"])
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for alg_data in algorithms:
|
||||||
|
algorithm = Algorithm(**alg_data)
|
||||||
|
db.add(algorithm)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print("✅ 算法数据初始化完成")
|
||||||
|
|
||||||
|
# 创建示例设备
|
||||||
|
devices = [
|
||||||
|
{
|
||||||
|
"name": "港口A区主监控",
|
||||||
|
"device_type": "camera",
|
||||||
|
"location": "港口A区",
|
||||||
|
"ip_address": "192.168.1.100",
|
||||||
|
"port": 554,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123",
|
||||||
|
"rtsp_url": "rtsp://192.168.1.100:554/stream1",
|
||||||
|
"status": "online",
|
||||||
|
"resolution": "1920x1080",
|
||||||
|
"fps": 25,
|
||||||
|
"algorithm_id": 1,
|
||||||
|
"is_enabled": True,
|
||||||
|
"latitude": 22.3193,
|
||||||
|
"longitude": 114.1694,
|
||||||
|
"description": "港口A区主要监控摄像头",
|
||||||
|
"manufacturer": "Hikvision",
|
||||||
|
"model": "DS-2CD2T47G1-L",
|
||||||
|
"serial_number": "HK123456789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "港口B区监控",
|
||||||
|
"device_type": "camera",
|
||||||
|
"location": "港口B区",
|
||||||
|
"ip_address": "192.168.1.101",
|
||||||
|
"port": 554,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123",
|
||||||
|
"rtsp_url": "rtsp://192.168.1.101:554/stream1",
|
||||||
|
"status": "online",
|
||||||
|
"resolution": "1920x1080",
|
||||||
|
"fps": 25,
|
||||||
|
"algorithm_id": 2,
|
||||||
|
"is_enabled": True,
|
||||||
|
"latitude": 22.3195,
|
||||||
|
"longitude": 114.1696,
|
||||||
|
"description": "港口B区监控摄像头",
|
||||||
|
"manufacturer": "Dahua",
|
||||||
|
"model": "IPC-HFW4431R-ZE",
|
||||||
|
"serial_number": "DH987654321"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "边检站门禁",
|
||||||
|
"device_type": "gate",
|
||||||
|
"location": "边检站入口",
|
||||||
|
"ip_address": "192.168.1.102",
|
||||||
|
"port": 80,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123",
|
||||||
|
"status": "online",
|
||||||
|
"is_enabled": True,
|
||||||
|
"latitude": 22.3190,
|
||||||
|
"longitude": 114.1690,
|
||||||
|
"description": "边检站入口门禁系统",
|
||||||
|
"manufacturer": "Suprema",
|
||||||
|
"model": "BioStation 2",
|
||||||
|
"serial_number": "SP123456789"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for dev_data in devices:
|
||||||
|
device = Device(**dev_data)
|
||||||
|
db.add(device)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print("✅ 设备数据初始化完成")
|
||||||
|
|
||||||
|
# 创建示例事件
|
||||||
|
events = [
|
||||||
|
{
|
||||||
|
"event_type": "person_detection",
|
||||||
|
"device_id": 1,
|
||||||
|
"algorithm_id": 1,
|
||||||
|
"severity": "medium",
|
||||||
|
"status": "pending",
|
||||||
|
"confidence": 0.95,
|
||||||
|
"bbox": json.dumps([100, 150, 80, 160]),
|
||||||
|
"image_path": "/events/images/person_001.jpg",
|
||||||
|
"description": "检测到人员进入监控区域",
|
||||||
|
"location": "港口A区",
|
||||||
|
"detected_objects": json.dumps([{"type": "person", "confidence": 0.95}]),
|
||||||
|
"processing_time": 15.5,
|
||||||
|
"is_alert": True,
|
||||||
|
"alert_sent": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event_type": "vehicle_detection",
|
||||||
|
"device_id": 2,
|
||||||
|
"algorithm_id": 2,
|
||||||
|
"severity": "low",
|
||||||
|
"status": "resolved",
|
||||||
|
"confidence": 0.92,
|
||||||
|
"bbox": json.dumps([200, 100, 120, 80]),
|
||||||
|
"image_path": "/events/images/vehicle_001.jpg",
|
||||||
|
"description": "检测到车辆通过",
|
||||||
|
"location": "港口B区",
|
||||||
|
"detected_objects": json.dumps([{"type": "car", "confidence": 0.92}]),
|
||||||
|
"processing_time": 18.2,
|
||||||
|
"is_alert": False,
|
||||||
|
"alert_sent": False,
|
||||||
|
"operator_id": 1,
|
||||||
|
"resolution_notes": "正常车辆通行",
|
||||||
|
"resolved_at": datetime.utcnow() - timedelta(hours=2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event_type": "intrusion",
|
||||||
|
"device_id": 1,
|
||||||
|
"algorithm_id": 1,
|
||||||
|
"severity": "high",
|
||||||
|
"status": "processing",
|
||||||
|
"confidence": 0.88,
|
||||||
|
"bbox": json.dumps([300, 200, 60, 120]),
|
||||||
|
"image_path": "/events/images/intrusion_001.jpg",
|
||||||
|
"description": "检测到可疑人员入侵",
|
||||||
|
"location": "港口A区",
|
||||||
|
"detected_objects": json.dumps([{"type": "person", "confidence": 0.88}]),
|
||||||
|
"processing_time": 16.0,
|
||||||
|
"is_alert": True,
|
||||||
|
"alert_sent": True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for evt_data in events:
|
||||||
|
event = Event(**evt_data)
|
||||||
|
db.add(event)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print("✅ 事件数据初始化完成")
|
||||||
|
|
||||||
|
print("\n🎉 所有示例数据初始化完成!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 初始化数据时出错: {e}")
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
init_sample_data()
|
6
server/models/__init__.py
Normal file
6
server/models/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from .algorithm import Algorithm
|
||||||
|
from .event import Event
|
||||||
|
from .device import Device
|
||||||
|
from .base import Base
|
||||||
|
|
||||||
|
__all__ = ["Algorithm", "Event", "Device", "Base"]
|
19
server/models/algorithm.py
Normal file
19
server/models/algorithm.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from sqlalchemy import Column, String, Text, Boolean, Float, Integer
|
||||||
|
from .base import BaseModel
|
||||||
|
|
||||||
|
class Algorithm(BaseModel):
|
||||||
|
__tablename__ = "algorithms"
|
||||||
|
|
||||||
|
name = Column(String(100), nullable=False, comment="算法名称")
|
||||||
|
description = Column(Text, comment="算法描述")
|
||||||
|
version = Column(String(20), nullable=False, comment="算法版本")
|
||||||
|
model_path = Column(String(255), comment="模型文件路径")
|
||||||
|
config_path = Column(String(255), comment="配置文件路径")
|
||||||
|
status = Column(String(20), default="inactive", comment="算法状态: active, inactive, training")
|
||||||
|
accuracy = Column(Float, comment="算法准确率")
|
||||||
|
detection_classes = Column(Text, comment="检测类别,JSON格式")
|
||||||
|
input_size = Column(String(20), comment="输入尺寸,如: 640x640")
|
||||||
|
inference_time = Column(Float, comment="推理时间(ms)")
|
||||||
|
is_enabled = Column(Boolean, default=True, comment="是否启用")
|
||||||
|
creator = Column(String(50), comment="创建者")
|
||||||
|
tags = Column(Text, comment="标签,JSON格式")
|
12
server/models/base.py
Normal file
12
server/models/base.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy import Column, Integer, DateTime
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
class BaseModel(Base):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
26
server/models/device.py
Normal file
26
server/models/device.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from sqlalchemy import Column, String, Text, Boolean, Integer, Float, DateTime
|
||||||
|
from .base import BaseModel
|
||||||
|
|
||||||
|
class Device(BaseModel):
|
||||||
|
__tablename__ = "devices"
|
||||||
|
|
||||||
|
name = Column(String(100), nullable=False, comment="设备名称")
|
||||||
|
device_type = Column(String(50), nullable=False, comment="设备类型: camera, sensor, etc")
|
||||||
|
location = Column(String(200), comment="设备位置")
|
||||||
|
ip_address = Column(String(50), comment="IP地址")
|
||||||
|
port = Column(Integer, comment="端口号")
|
||||||
|
username = Column(String(50), comment="用户名")
|
||||||
|
password = Column(String(100), comment="密码")
|
||||||
|
rtsp_url = Column(String(500), comment="RTSP流地址")
|
||||||
|
status = Column(String(20), default="offline", comment="设备状态: online, offline, error")
|
||||||
|
resolution = Column(String(20), comment="分辨率,如: 1920x1080")
|
||||||
|
fps = Column(Integer, comment="帧率")
|
||||||
|
algorithm_id = Column(Integer, comment="关联的算法ID")
|
||||||
|
is_enabled = Column(Boolean, default=True, comment="是否启用")
|
||||||
|
last_heartbeat = Column(DateTime, comment="最后心跳时间")
|
||||||
|
latitude = Column(Float, comment="纬度")
|
||||||
|
longitude = Column(Float, comment="经度")
|
||||||
|
description = Column(Text, comment="设备描述")
|
||||||
|
manufacturer = Column(String(100), comment="制造商")
|
||||||
|
model = Column(String(100), comment="设备型号")
|
||||||
|
serial_number = Column(String(100), comment="序列号")
|
24
server/models/event.py
Normal file
24
server/models/event.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from sqlalchemy import Column, String, Text, Boolean, Integer, Float, DateTime, JSON
|
||||||
|
from .base import BaseModel
|
||||||
|
|
||||||
|
class Event(BaseModel):
|
||||||
|
__tablename__ = "events"
|
||||||
|
|
||||||
|
event_type = Column(String(50), nullable=False, comment="事件类型: person_detection, vehicle_detection, intrusion, etc")
|
||||||
|
device_id = Column(Integer, nullable=False, comment="关联设备ID")
|
||||||
|
algorithm_id = Column(Integer, comment="关联算法ID")
|
||||||
|
severity = Column(String(20), default="medium", comment="严重程度: low, medium, high, critical")
|
||||||
|
status = Column(String(20), default="pending", comment="事件状态: pending, processing, resolved, ignored")
|
||||||
|
confidence = Column(Float, comment="置信度")
|
||||||
|
bbox = Column(Text, comment="边界框坐标,JSON格式: [x, y, width, height]")
|
||||||
|
image_path = Column(String(500), comment="事件图片路径")
|
||||||
|
video_path = Column(String(500), comment="事件视频路径")
|
||||||
|
description = Column(Text, comment="事件描述")
|
||||||
|
location = Column(String(200), comment="事件发生位置")
|
||||||
|
detected_objects = Column(Text, comment="检测到的对象,JSON格式")
|
||||||
|
processing_time = Column(Float, comment="处理时间(ms)")
|
||||||
|
is_alert = Column(Boolean, default=False, comment="是否触发告警")
|
||||||
|
alert_sent = Column(Boolean, default=False, comment="是否已发送告警")
|
||||||
|
operator_id = Column(Integer, comment="处理人员ID")
|
||||||
|
resolution_notes = Column(Text, comment="处理备注")
|
||||||
|
resolved_at = Column(DateTime, comment="解决时间")
|
177
server/readme.md
Normal file
177
server/readme.md
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
# 边检CV算法接口服务
|
||||||
|
|
||||||
|
### 技术栈
|
||||||
|
- **后端框架**: FastAPI
|
||||||
|
- **数据库**: SQLite (SQLAlchemy ORM)
|
||||||
|
- **AI模型**: YOLOv11n
|
||||||
|
- **进程管理**: Supervisor
|
||||||
|
- **开发语言**: Python 3.8+
|
||||||
|
|
||||||
|
### 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── app.py # FastAPI主应用
|
||||||
|
├── start.py # 启动脚本
|
||||||
|
├── requirements.txt # Python依赖
|
||||||
|
├── env.example # 环境变量示例
|
||||||
|
├── init_data.py # 示例数据初始化
|
||||||
|
├── core/ # 核心配置
|
||||||
|
│ └── database.py # 数据库配置
|
||||||
|
├── models/ # 数据模型
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── base.py # 基础模型
|
||||||
|
│ ├── algorithm.py # 算法模型
|
||||||
|
│ ├── device.py # 设备模型
|
||||||
|
│ └── event.py # 事件模型
|
||||||
|
├── schemas/ # Pydantic模型
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── algorithm.py # 算法相关模型
|
||||||
|
│ ├── device.py # 设备相关模型
|
||||||
|
│ └── event.py # 事件相关模型
|
||||||
|
└── routers/ # API路由
|
||||||
|
├── __init__.py
|
||||||
|
├── algorithms.py # 算法管理接口
|
||||||
|
├── devices.py # 设备管理接口
|
||||||
|
└── events.py # 事件管理接口
|
||||||
|
```
|
||||||
|
|
||||||
|
### 功能模块
|
||||||
|
|
||||||
|
#### 1. 算法管理模块
|
||||||
|
- 算法的增删改查
|
||||||
|
- 算法状态管理(启用/禁用)
|
||||||
|
- 算法版本管理
|
||||||
|
- 算法性能指标(准确率、推理时间等)
|
||||||
|
|
||||||
|
#### 2. 设备管理模块
|
||||||
|
- 设备信息管理(摄像头、传感器等)
|
||||||
|
- 设备状态监控
|
||||||
|
- 设备类型管理
|
||||||
|
- 设备地理位置信息
|
||||||
|
|
||||||
|
#### 3. 事件管理模块
|
||||||
|
- 事件记录和查询
|
||||||
|
- 事件状态管理
|
||||||
|
- 事件统计分析
|
||||||
|
- 告警管理
|
||||||
|
|
||||||
|
### API接口
|
||||||
|
|
||||||
|
#### 算法管理接口
|
||||||
|
- `POST /api/algorithms/` - 创建算法
|
||||||
|
- `GET /api/algorithms/` - 获取算法列表
|
||||||
|
- `GET /api/algorithms/{id}` - 获取算法详情
|
||||||
|
- `PUT /api/algorithms/{id}` - 更新算法
|
||||||
|
- `DELETE /api/algorithms/{id}` - 删除算法
|
||||||
|
- `PATCH /api/algorithms/{id}/status` - 更新算法状态
|
||||||
|
- `PATCH /api/algorithms/{id}/enable` - 启用/禁用算法
|
||||||
|
|
||||||
|
#### 设备管理接口
|
||||||
|
- `POST /api/devices/` - 创建设备
|
||||||
|
- `GET /api/devices/` - 获取设备列表
|
||||||
|
- `GET /api/devices/{id}` - 获取设备详情
|
||||||
|
- `PUT /api/devices/{id}` - 更新设备
|
||||||
|
- `DELETE /api/devices/{id}` - 删除设备
|
||||||
|
- `PATCH /api/devices/{id}/status` - 更新设备状态
|
||||||
|
- `PATCH /api/devices/{id}/enable` - 启用/禁用设备
|
||||||
|
- `GET /api/devices/types/list` - 获取设备类型列表
|
||||||
|
- `GET /api/devices/status/stats` - 获取设备状态统计
|
||||||
|
|
||||||
|
#### 事件管理接口
|
||||||
|
- `POST /api/events/` - 创建事件
|
||||||
|
- `GET /api/events/` - 获取事件列表
|
||||||
|
- `GET /api/events/{id}` - 获取事件详情
|
||||||
|
- `PUT /api/events/{id}` - 更新事件
|
||||||
|
- `DELETE /api/events/{id}` - 删除事件
|
||||||
|
- `PATCH /api/events/{id}/status` - 更新事件状态
|
||||||
|
- `GET /api/events/types/list` - 获取事件类型列表
|
||||||
|
- `GET /api/events/stats/summary` - 获取事件统计摘要
|
||||||
|
- `GET /api/events/stats/by-type` - 按类型统计事件
|
||||||
|
|
||||||
|
### 安装和运行
|
||||||
|
|
||||||
|
#### 1. 环境准备
|
||||||
|
```bash
|
||||||
|
# 创建虚拟环境
|
||||||
|
conda create -n border_inspection python=3.8
|
||||||
|
conda activate border_inspection
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 初始化数据库
|
||||||
|
```bash
|
||||||
|
# 启动服务(会自动创建数据库表)
|
||||||
|
python start.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 初始化示例数据
|
||||||
|
```bash
|
||||||
|
python init_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 启动服务
|
||||||
|
```bash
|
||||||
|
python start.py
|
||||||
|
```
|
||||||
|
|
||||||
|
服务将在 `http://localhost:8000` 启动
|
||||||
|
|
||||||
|
### API文档
|
||||||
|
|
||||||
|
启动服务后,可以访问以下地址查看API文档:
|
||||||
|
- Swagger UI: `http://localhost:8000/docs`
|
||||||
|
- ReDoc: `http://localhost:8000/redoc`
|
||||||
|
|
||||||
|
### 数据库设计
|
||||||
|
|
||||||
|
#### 算法表 (algorithms)
|
||||||
|
- 基本信息:名称、描述、版本
|
||||||
|
- 模型信息:模型路径、配置文件路径
|
||||||
|
- 性能指标:准确率、推理时间、输入尺寸
|
||||||
|
- 状态管理:启用状态、算法状态
|
||||||
|
- 分类信息:检测类别、标签
|
||||||
|
|
||||||
|
#### 设备表 (devices)
|
||||||
|
- 基本信息:名称、类型、位置
|
||||||
|
- 连接信息:IP地址、端口、用户名、密码
|
||||||
|
- 视频流:RTSP地址、分辨率、帧率
|
||||||
|
- 状态信息:在线状态、最后心跳时间
|
||||||
|
- 地理位置:经纬度坐标
|
||||||
|
|
||||||
|
#### 事件表 (events)
|
||||||
|
- 事件信息:类型、设备ID、算法ID
|
||||||
|
- 检测结果:置信度、边界框、检测对象
|
||||||
|
- 状态管理:事件状态、严重程度
|
||||||
|
- 告警信息:是否告警、告警发送状态
|
||||||
|
- 处理信息:处理人员、处理备注、解决时间
|
||||||
|
|
||||||
|
### 开发说明
|
||||||
|
|
||||||
|
1. **添加新模型**: 在 `models/` 目录下创建新的模型文件
|
||||||
|
2. **添加新接口**: 在 `routers/` 目录下创建新的路由文件
|
||||||
|
3. **添加新Schema**: 在 `schemas/` 目录下创建新的Pydantic模型
|
||||||
|
4. **数据库迁移**: 修改模型后重启服务,数据库表会自动更新
|
||||||
|
|
||||||
|
### 部署说明
|
||||||
|
|
||||||
|
#### 使用Supervisor管理进程
|
||||||
|
```ini
|
||||||
|
[program:border_inspection]
|
||||||
|
command=python /path/to/server/start.py
|
||||||
|
directory=/path/to/server
|
||||||
|
user=www-data
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stderr_logfile=/var/log/border_inspection.err.log
|
||||||
|
stdout_logfile=/var/log/border_inspection.out.log
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 环境变量配置
|
||||||
|
复制 `env.example` 为 `.env` 并修改相应配置:
|
||||||
|
```bash
|
||||||
|
cp env.example .env
|
||||||
|
# 编辑 .env 文件
|
||||||
|
```
|
12
server/requirements.txt
Normal file
12
server/requirements.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn==0.24.0
|
||||||
|
sqlalchemy==2.0.23
|
||||||
|
pydantic==2.5.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
python-jose[cryptography]==3.3.0
|
||||||
|
passlib[bcrypt]==1.7.4
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
aiofiles==23.2.1
|
||||||
|
pillow==10.1.0
|
||||||
|
opencv-python==4.8.1.78
|
||||||
|
ultralytics==8.0.196
|
1
server/routers/__init__.py
Normal file
1
server/routers/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# 路由包
|
130
server/routers/algorithms.py
Normal file
130
server/routers/algorithms.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List, Optional
|
||||||
|
from core.database import get_db
|
||||||
|
from models.algorithm import Algorithm
|
||||||
|
from schemas.algorithm import (
|
||||||
|
AlgorithmCreate,
|
||||||
|
AlgorithmUpdate,
|
||||||
|
AlgorithmResponse,
|
||||||
|
AlgorithmListResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/", response_model=AlgorithmResponse, summary="创建算法")
|
||||||
|
async def create_algorithm(
|
||||||
|
algorithm: AlgorithmCreate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""创建新的算法"""
|
||||||
|
db_algorithm = Algorithm(**algorithm.dict())
|
||||||
|
db.add(db_algorithm)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_algorithm)
|
||||||
|
return db_algorithm
|
||||||
|
|
||||||
|
@router.get("/", response_model=AlgorithmListResponse, summary="获取算法列表")
|
||||||
|
async def get_algorithms(
|
||||||
|
skip: int = Query(0, ge=0, description="跳过记录数"),
|
||||||
|
limit: int = Query(10, ge=1, le=100, description="返回记录数"),
|
||||||
|
name: Optional[str] = Query(None, description="算法名称"),
|
||||||
|
status: Optional[str] = Query(None, description="算法状态"),
|
||||||
|
is_enabled: Optional[bool] = Query(None, description="是否启用"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取算法列表,支持分页和筛选"""
|
||||||
|
query = db.query(Algorithm)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
query = query.filter(Algorithm.name.contains(name))
|
||||||
|
if status:
|
||||||
|
query = query.filter(Algorithm.status == status)
|
||||||
|
if is_enabled is not None:
|
||||||
|
query = query.filter(Algorithm.is_enabled == is_enabled)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
algorithms = query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
return AlgorithmListResponse(
|
||||||
|
algorithms=algorithms,
|
||||||
|
total=total,
|
||||||
|
page=skip // limit + 1,
|
||||||
|
size=limit
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/{algorithm_id}", response_model=AlgorithmResponse, summary="获取算法详情")
|
||||||
|
async def get_algorithm(
|
||||||
|
algorithm_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""根据ID获取算法详情"""
|
||||||
|
algorithm = db.query(Algorithm).filter(Algorithm.id == algorithm_id).first()
|
||||||
|
if not algorithm:
|
||||||
|
raise HTTPException(status_code=404, detail="算法不存在")
|
||||||
|
return algorithm
|
||||||
|
|
||||||
|
@router.put("/{algorithm_id}", response_model=AlgorithmResponse, summary="更新算法")
|
||||||
|
async def update_algorithm(
|
||||||
|
algorithm_id: int,
|
||||||
|
algorithm: AlgorithmUpdate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""更新算法信息"""
|
||||||
|
db_algorithm = db.query(Algorithm).filter(Algorithm.id == algorithm_id).first()
|
||||||
|
if not db_algorithm:
|
||||||
|
raise HTTPException(status_code=404, detail="算法不存在")
|
||||||
|
|
||||||
|
update_data = algorithm.dict(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(db_algorithm, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_algorithm)
|
||||||
|
return db_algorithm
|
||||||
|
|
||||||
|
@router.delete("/{algorithm_id}", summary="删除算法")
|
||||||
|
async def delete_algorithm(
|
||||||
|
algorithm_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""删除算法"""
|
||||||
|
algorithm = db.query(Algorithm).filter(Algorithm.id == algorithm_id).first()
|
||||||
|
if not algorithm:
|
||||||
|
raise HTTPException(status_code=404, detail="算法不存在")
|
||||||
|
|
||||||
|
db.delete(algorithm)
|
||||||
|
db.commit()
|
||||||
|
return {"message": "算法删除成功"}
|
||||||
|
|
||||||
|
@router.patch("/{algorithm_id}/status", response_model=AlgorithmResponse, summary="更新算法状态")
|
||||||
|
async def update_algorithm_status(
|
||||||
|
algorithm_id: int,
|
||||||
|
status: str = Query(..., description="新状态"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""更新算法状态"""
|
||||||
|
algorithm = db.query(Algorithm).filter(Algorithm.id == algorithm_id).first()
|
||||||
|
if not algorithm:
|
||||||
|
raise HTTPException(status_code=404, detail="算法不存在")
|
||||||
|
|
||||||
|
algorithm.status = status
|
||||||
|
db.commit()
|
||||||
|
db.refresh(algorithm)
|
||||||
|
return algorithm
|
||||||
|
|
||||||
|
@router.patch("/{algorithm_id}/enable", response_model=AlgorithmResponse, summary="启用/禁用算法")
|
||||||
|
async def toggle_algorithm_enabled(
|
||||||
|
algorithm_id: int,
|
||||||
|
enabled: bool = Query(..., description="是否启用"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""启用或禁用算法"""
|
||||||
|
algorithm = db.query(Algorithm).filter(Algorithm.id == algorithm_id).first()
|
||||||
|
if not algorithm:
|
||||||
|
raise HTTPException(status_code=404, detail="算法不存在")
|
||||||
|
|
||||||
|
algorithm.is_enabled = enabled
|
||||||
|
db.commit()
|
||||||
|
db.refresh(algorithm)
|
||||||
|
return algorithm
|
165
server/routers/devices.py
Normal file
165
server/routers/devices.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List, Optional
|
||||||
|
from core.database import get_db
|
||||||
|
from models.device import Device
|
||||||
|
from schemas.device import (
|
||||||
|
DeviceCreate,
|
||||||
|
DeviceUpdate,
|
||||||
|
DeviceResponse,
|
||||||
|
DeviceListResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/", response_model=DeviceResponse, summary="创建设备")
|
||||||
|
async def create_device(
|
||||||
|
device: DeviceCreate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""创建新的设备"""
|
||||||
|
db_device = Device(**device.dict())
|
||||||
|
db.add(db_device)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_device)
|
||||||
|
return db_device
|
||||||
|
|
||||||
|
@router.get("/", response_model=DeviceListResponse, 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()
|
||||||
|
|
||||||
|
return DeviceListResponse(
|
||||||
|
devices=devices,
|
||||||
|
total=total,
|
||||||
|
page=skip // limit + 1,
|
||||||
|
size=limit
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/{device_id}", response_model=DeviceResponse, 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 device
|
||||||
|
|
||||||
|
@router.put("/{device_id}", response_model=DeviceResponse, summary="更新设备")
|
||||||
|
async def update_device(
|
||||||
|
device_id: int,
|
||||||
|
device: DeviceUpdate,
|
||||||
|
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="设备不存在")
|
||||||
|
|
||||||
|
update_data = device.dict(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(db_device, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_device)
|
||||||
|
return db_device
|
||||||
|
|
||||||
|
@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", response_model=DeviceResponse, 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 device
|
||||||
|
|
||||||
|
@router.patch("/{device_id}/enable", response_model=DeviceResponse, 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 device
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
213
server/routers/events.py
Normal file
213
server/routers/events.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from core.database import get_db
|
||||||
|
from models.event import Event
|
||||||
|
from schemas.event import (
|
||||||
|
EventCreate,
|
||||||
|
EventUpdate,
|
||||||
|
EventResponse,
|
||||||
|
EventListResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("/", response_model=EventResponse, summary="创建事件")
|
||||||
|
async def create_event(
|
||||||
|
event: EventCreate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""创建新的事件"""
|
||||||
|
db_event = Event(**event.dict())
|
||||||
|
db.add(db_event)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_event)
|
||||||
|
return db_event
|
||||||
|
|
||||||
|
@router.get("/", response_model=EventListResponse, summary="获取事件列表")
|
||||||
|
async def get_events(
|
||||||
|
skip: int = Query(0, ge=0, description="跳过记录数"),
|
||||||
|
limit: int = Query(10, ge=1, le=100, description="返回记录数"),
|
||||||
|
event_type: Optional[str] = Query(None, description="事件类型"),
|
||||||
|
device_id: Optional[int] = Query(None, description="设备ID"),
|
||||||
|
algorithm_id: Optional[int] = Query(None, description="算法ID"),
|
||||||
|
severity: Optional[str] = Query(None, description="严重程度"),
|
||||||
|
status: Optional[str] = Query(None, description="事件状态"),
|
||||||
|
is_alert: Optional[bool] = Query(None, description="是否告警"),
|
||||||
|
start_time: Optional[str] = Query(None, description="开始时间"),
|
||||||
|
end_time: Optional[str] = Query(None, description="结束时间"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取事件列表,支持分页和筛选"""
|
||||||
|
query = db.query(Event)
|
||||||
|
|
||||||
|
if event_type:
|
||||||
|
query = query.filter(Event.event_type == event_type)
|
||||||
|
if device_id:
|
||||||
|
query = query.filter(Event.device_id == device_id)
|
||||||
|
if algorithm_id:
|
||||||
|
query = query.filter(Event.algorithm_id == algorithm_id)
|
||||||
|
if severity:
|
||||||
|
query = query.filter(Event.severity == severity)
|
||||||
|
if status:
|
||||||
|
query = query.filter(Event.status == status)
|
||||||
|
if is_alert is not None:
|
||||||
|
query = query.filter(Event.is_alert == is_alert)
|
||||||
|
if start_time:
|
||||||
|
try:
|
||||||
|
start_dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
|
||||||
|
query = query.filter(Event.created_at >= start_dt)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if end_time:
|
||||||
|
try:
|
||||||
|
end_dt = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
|
||||||
|
query = query.filter(Event.created_at <= end_dt)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 按创建时间倒序排列
|
||||||
|
query = query.order_by(Event.created_at.desc())
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
events = query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
return EventListResponse(
|
||||||
|
events=events,
|
||||||
|
total=total,
|
||||||
|
page=skip // limit + 1,
|
||||||
|
size=limit
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/{event_id}", response_model=EventResponse, summary="获取事件详情")
|
||||||
|
async def get_event(
|
||||||
|
event_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""根据ID获取事件详情"""
|
||||||
|
event = db.query(Event).filter(Event.id == event_id).first()
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
return event
|
||||||
|
|
||||||
|
@router.put("/{event_id}", response_model=EventResponse, summary="更新事件")
|
||||||
|
async def update_event(
|
||||||
|
event_id: int,
|
||||||
|
event: EventUpdate,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""更新事件信息"""
|
||||||
|
db_event = db.query(Event).filter(Event.id == event_id).first()
|
||||||
|
if not db_event:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
|
||||||
|
update_data = event.dict(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(db_event, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_event)
|
||||||
|
return db_event
|
||||||
|
|
||||||
|
@router.delete("/{event_id}", summary="删除事件")
|
||||||
|
async def delete_event(
|
||||||
|
event_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""删除事件"""
|
||||||
|
event = db.query(Event).filter(Event.id == event_id).first()
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
|
||||||
|
db.delete(event)
|
||||||
|
db.commit()
|
||||||
|
return {"message": "事件删除成功"}
|
||||||
|
|
||||||
|
@router.patch("/{event_id}/status", response_model=EventResponse, summary="更新事件状态")
|
||||||
|
async def update_event_status(
|
||||||
|
event_id: int,
|
||||||
|
status: str = Query(..., description="新状态"),
|
||||||
|
resolution_notes: Optional[str] = Query(None, description="处理备注"),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""更新事件状态"""
|
||||||
|
event = db.query(Event).filter(Event.id == event_id).first()
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
|
||||||
|
event.status = status
|
||||||
|
if resolution_notes:
|
||||||
|
event.resolution_notes = resolution_notes
|
||||||
|
|
||||||
|
# 如果状态为resolved,设置解决时间
|
||||||
|
if status == "resolved":
|
||||||
|
event.resolved_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(event)
|
||||||
|
return event
|
||||||
|
|
||||||
|
@router.get("/types/list", summary="获取事件类型列表")
|
||||||
|
async def get_event_types():
|
||||||
|
"""获取所有事件类型"""
|
||||||
|
return {
|
||||||
|
"types": [
|
||||||
|
{"value": "person_detection", "label": "人员检测"},
|
||||||
|
{"value": "vehicle_detection", "label": "车辆检测"},
|
||||||
|
{"value": "intrusion", "label": "入侵检测"},
|
||||||
|
{"value": "face_recognition", "label": "人脸识别"},
|
||||||
|
{"value": "license_plate", "label": "车牌识别"},
|
||||||
|
{"value": "object_detection", "label": "物体检测"},
|
||||||
|
{"value": "behavior_analysis", "label": "行为分析"},
|
||||||
|
{"value": "other", "label": "其他"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/stats/summary", summary="获取事件统计摘要")
|
||||||
|
async def get_event_stats_summary(db: Session = Depends(get_db)):
|
||||||
|
"""获取事件统计摘要"""
|
||||||
|
total = db.query(Event).count()
|
||||||
|
pending = db.query(Event).filter(Event.status == "pending").count()
|
||||||
|
processing = db.query(Event).filter(Event.status == "processing").count()
|
||||||
|
resolved = db.query(Event).filter(Event.status == "resolved").count()
|
||||||
|
ignored = db.query(Event).filter(Event.status == "ignored").count()
|
||||||
|
alerts = db.query(Event).filter(Event.is_alert == True).count()
|
||||||
|
|
||||||
|
# 按严重程度统计
|
||||||
|
critical = db.query(Event).filter(Event.severity == "critical").count()
|
||||||
|
high = db.query(Event).filter(Event.severity == "high").count()
|
||||||
|
medium = db.query(Event).filter(Event.severity == "medium").count()
|
||||||
|
low = db.query(Event).filter(Event.severity == "low").count()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"pending": pending,
|
||||||
|
"processing": processing,
|
||||||
|
"resolved": resolved,
|
||||||
|
"ignored": ignored,
|
||||||
|
"alerts": alerts,
|
||||||
|
"severity": {
|
||||||
|
"critical": critical,
|
||||||
|
"high": high,
|
||||||
|
"medium": medium,
|
||||||
|
"low": low
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.get("/stats/by-type", summary="按类型统计事件")
|
||||||
|
async def get_event_stats_by_type(db: Session = Depends(get_db)):
|
||||||
|
"""按事件类型统计"""
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
stats = db.query(
|
||||||
|
Event.event_type,
|
||||||
|
func.count(Event.id).label('count')
|
||||||
|
).group_by(Event.event_type).all()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"stats": [
|
||||||
|
{"type": stat.event_type, "count": stat.count}
|
||||||
|
for stat in stats
|
||||||
|
]
|
||||||
|
}
|
5
server/schemas/__init__.py
Normal file
5
server/schemas/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .algorithm import *
|
||||||
|
from .device import *
|
||||||
|
from .event import *
|
||||||
|
|
||||||
|
__all__ = ["algorithm", "device", "event"]
|
50
server/schemas/algorithm.py
Normal file
50
server/schemas/algorithm.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class AlgorithmBase(BaseModel):
|
||||||
|
name: str = Field(..., description="算法名称")
|
||||||
|
description: Optional[str] = Field(None, description="算法描述")
|
||||||
|
version: str = Field(..., description="算法版本")
|
||||||
|
model_path: Optional[str] = Field(None, description="模型文件路径")
|
||||||
|
config_path: Optional[str] = Field(None, description="配置文件路径")
|
||||||
|
status: str = Field("inactive", description="算法状态")
|
||||||
|
accuracy: Optional[float] = Field(None, description="算法准确率")
|
||||||
|
detection_classes: Optional[str] = Field(None, description="检测类别")
|
||||||
|
input_size: Optional[str] = Field(None, description="输入尺寸")
|
||||||
|
inference_time: Optional[float] = Field(None, description="推理时间")
|
||||||
|
is_enabled: bool = Field(True, description="是否启用")
|
||||||
|
creator: Optional[str] = Field(None, description="创建者")
|
||||||
|
tags: Optional[str] = Field(None, description="标签")
|
||||||
|
|
||||||
|
class AlgorithmCreate(AlgorithmBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AlgorithmUpdate(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
version: Optional[str] = None
|
||||||
|
model_path: Optional[str] = None
|
||||||
|
config_path: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
accuracy: Optional[float] = None
|
||||||
|
detection_classes: Optional[str] = None
|
||||||
|
input_size: Optional[str] = None
|
||||||
|
inference_time: Optional[float] = None
|
||||||
|
is_enabled: Optional[bool] = None
|
||||||
|
creator: Optional[str] = None
|
||||||
|
tags: Optional[str] = None
|
||||||
|
|
||||||
|
class AlgorithmResponse(AlgorithmBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class AlgorithmListResponse(BaseModel):
|
||||||
|
algorithms: List[AlgorithmResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
size: int
|
63
server/schemas/device.py
Normal file
63
server/schemas/device.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class DeviceBase(BaseModel):
|
||||||
|
name: str = Field(..., description="设备名称")
|
||||||
|
device_type: str = Field(..., description="设备类型")
|
||||||
|
location: Optional[str] = Field(None, description="设备位置")
|
||||||
|
ip_address: Optional[str] = Field(None, description="IP地址")
|
||||||
|
port: Optional[int] = Field(None, description="端口号")
|
||||||
|
username: Optional[str] = Field(None, description="用户名")
|
||||||
|
password: Optional[str] = Field(None, description="密码")
|
||||||
|
rtsp_url: Optional[str] = Field(None, description="RTSP流地址")
|
||||||
|
status: str = Field("offline", description="设备状态")
|
||||||
|
resolution: Optional[str] = Field(None, description="分辨率")
|
||||||
|
fps: Optional[int] = Field(None, description="帧率")
|
||||||
|
algorithm_id: Optional[int] = Field(None, description="关联的算法ID")
|
||||||
|
is_enabled: bool = Field(True, description="是否启用")
|
||||||
|
latitude: Optional[float] = Field(None, description="纬度")
|
||||||
|
longitude: Optional[float] = Field(None, description="经度")
|
||||||
|
description: Optional[str] = Field(None, description="设备描述")
|
||||||
|
manufacturer: Optional[str] = Field(None, description="制造商")
|
||||||
|
model: Optional[str] = Field(None, description="设备型号")
|
||||||
|
serial_number: Optional[str] = Field(None, description="序列号")
|
||||||
|
|
||||||
|
class DeviceCreate(DeviceBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DeviceUpdate(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
device_type: Optional[str] = None
|
||||||
|
location: Optional[str] = None
|
||||||
|
ip_address: Optional[str] = None
|
||||||
|
port: Optional[int] = None
|
||||||
|
username: Optional[str] = None
|
||||||
|
password: Optional[str] = None
|
||||||
|
rtsp_url: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
resolution: Optional[str] = None
|
||||||
|
fps: Optional[int] = None
|
||||||
|
algorithm_id: Optional[int] = None
|
||||||
|
is_enabled: Optional[bool] = None
|
||||||
|
latitude: Optional[float] = None
|
||||||
|
longitude: Optional[float] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
manufacturer: Optional[str] = None
|
||||||
|
model: Optional[str] = None
|
||||||
|
serial_number: Optional[str] = None
|
||||||
|
|
||||||
|
class DeviceResponse(DeviceBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
last_heartbeat: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class DeviceListResponse(BaseModel):
|
||||||
|
devices: List[DeviceResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
size: int
|
59
server/schemas/event.py
Normal file
59
server/schemas/event.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class EventBase(BaseModel):
|
||||||
|
event_type: str = Field(..., description="事件类型")
|
||||||
|
device_id: int = Field(..., description="关联设备ID")
|
||||||
|
algorithm_id: Optional[int] = Field(None, description="关联算法ID")
|
||||||
|
severity: str = Field("medium", description="严重程度")
|
||||||
|
status: str = Field("pending", description="事件状态")
|
||||||
|
confidence: Optional[float] = Field(None, description="置信度")
|
||||||
|
bbox: Optional[str] = Field(None, description="边界框坐标")
|
||||||
|
image_path: Optional[str] = Field(None, description="事件图片路径")
|
||||||
|
video_path: Optional[str] = Field(None, description="事件视频路径")
|
||||||
|
description: Optional[str] = Field(None, description="事件描述")
|
||||||
|
location: Optional[str] = Field(None, description="事件发生位置")
|
||||||
|
detected_objects: Optional[str] = Field(None, description="检测到的对象")
|
||||||
|
processing_time: Optional[float] = Field(None, description="处理时间")
|
||||||
|
is_alert: bool = Field(False, description="是否触发告警")
|
||||||
|
alert_sent: bool = Field(False, description="是否已发送告警")
|
||||||
|
operator_id: Optional[int] = Field(None, description="处理人员ID")
|
||||||
|
resolution_notes: Optional[str] = Field(None, description="处理备注")
|
||||||
|
|
||||||
|
class EventCreate(EventBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class EventUpdate(BaseModel):
|
||||||
|
event_type: Optional[str] = None
|
||||||
|
device_id: Optional[int] = None
|
||||||
|
algorithm_id: Optional[int] = None
|
||||||
|
severity: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
confidence: Optional[float] = None
|
||||||
|
bbox: Optional[str] = None
|
||||||
|
image_path: Optional[str] = None
|
||||||
|
video_path: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
location: Optional[str] = None
|
||||||
|
detected_objects: Optional[str] = None
|
||||||
|
processing_time: Optional[float] = None
|
||||||
|
is_alert: Optional[bool] = None
|
||||||
|
alert_sent: Optional[bool] = None
|
||||||
|
operator_id: Optional[int] = None
|
||||||
|
resolution_notes: Optional[str] = None
|
||||||
|
|
||||||
|
class EventResponse(EventBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
resolved_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class EventListResponse(BaseModel):
|
||||||
|
events: List[EventResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
size: int
|
Loading…
x
Reference in New Issue
Block a user