This commit is contained in:
zlgecc 2025-08-02 12:38:52 +08:00
parent 6e7e065763
commit eb8af4ba8b
19 changed files with 1267 additions and 0 deletions

40
server/app.py Normal file
View 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
View 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
View 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
View 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()

View 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"]

View 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
View 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
View 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
View 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
View 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
View 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

View File

@ -0,0 +1 @@
# 路由包

View 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
View 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
View 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
]
}

View File

@ -0,0 +1,5 @@
from .algorithm import *
from .device import *
from .event import *
__all__ = ["algorithm", "device", "event"]

View 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
View 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
View 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