283 lines
9.3 KiB
Python
283 lines
9.3 KiB
Python
import json
|
||
import threading
|
||
from fastapi import HTTPException, Query, APIRouter, Depends, Request
|
||
from mysql.connector import Error as MySQLError
|
||
|
||
from ds.config import LIVE_CONFIG
|
||
from ds.db import db
|
||
from middle.auth_middleware import get_current_user
|
||
# 注意:导入的Schema已更新字段
|
||
from schema.device_schema import (
|
||
DeviceCreateRequest,
|
||
DeviceResponse,
|
||
DeviceListResponse,
|
||
md5_encrypt
|
||
)
|
||
from schema.response_schema import APIResponse
|
||
from schema.user_schema import UserResponse
|
||
|
||
# 导入之前封装的WEBRTC处理函数
|
||
from rtc.rtc import process_webrtc_stream
|
||
|
||
router = APIRouter(
|
||
prefix="/devices",
|
||
tags=["设备管理"]
|
||
)
|
||
|
||
|
||
# 在后台线程中运行WEBRTC处理
|
||
def run_webrtc_processing(ip, webrtc_url):
|
||
try:
|
||
print(f"开始处理来自设备 {ip} 的WEBRTC流: {webrtc_url}")
|
||
process_webrtc_stream(ip, webrtc_url)
|
||
except Exception as e:
|
||
print(f"WEBRTC处理出错: {str(e)}")
|
||
|
||
|
||
# ------------------------------
|
||
# 1. 创建设备信息
|
||
# ------------------------------
|
||
@router.post("/add", response_model=APIResponse, summary="创建设备信息")
|
||
async def create_device(request: Request, device_data: DeviceCreateRequest):
|
||
conn = None
|
||
cursor = None
|
||
try:
|
||
conn = db.get_connection()
|
||
cursor = conn.cursor(dictionary=True)
|
||
|
||
# 检查client_ip是否已存在
|
||
cursor.execute("SELECT * FROM devices WHERE client_ip = %s", (device_data.ip,))
|
||
existing_device = cursor.fetchone()
|
||
if existing_device:
|
||
# 设备创建成功后,在后台线程启动WEBRTC流处理
|
||
threading.Thread(
|
||
target=run_webrtc_processing,
|
||
args=(device_data.ip, full_webrtc_url),
|
||
daemon=True # 设为守护线程,主程序退出时自动结束
|
||
).start()
|
||
# IP已存在时返回该设备信息
|
||
return APIResponse(
|
||
code=200,
|
||
message=f"客户端IP {device_data.ip} 已存在",
|
||
data=DeviceResponse(**existing_device)
|
||
)
|
||
|
||
# 获取RTMP URL和WEBRTC URL配置
|
||
rtmp_url = str(LIVE_CONFIG.get("rtmp_url", ""))
|
||
webrtc_url = str(LIVE_CONFIG.get("webrtc_url", ""))
|
||
|
||
# 将设备详细信息(params)转换为JSON字符串
|
||
device_params_json = json.dumps(device_data.params) if device_data.params else None
|
||
|
||
# 对JSON字符串进行MD5加密
|
||
device_md5 = md5_encrypt(device_params_json) if device_params_json else ""
|
||
|
||
# 解析User-Agent获取设备类型
|
||
user_agent = request.headers.get("User-Agent", "").lower()
|
||
|
||
# 优先处理User-Agent为default的情况
|
||
if user_agent == "default":
|
||
# 检查params中是否存在os键
|
||
if device_data.params and isinstance(device_data.params, dict) and "os" in device_data.params:
|
||
device_type = device_data.params["os"]
|
||
else:
|
||
device_type = "unknown"
|
||
elif "windows" in user_agent:
|
||
device_type = "windows"
|
||
elif "android" in user_agent:
|
||
device_type = "android"
|
||
elif "linux" in user_agent:
|
||
device_type = "linux"
|
||
else:
|
||
device_type = "unknown"
|
||
|
||
# 构建完整的WEBRTC URL
|
||
full_webrtc_url = webrtc_url + device_md5
|
||
|
||
# SQL插入语句
|
||
insert_query = """
|
||
INSERT INTO devices
|
||
(client_ip, hostname, rtmp_push_url, live_webrtc_url, detection_webrtc_url,
|
||
device_online_status, device_type, alarm_count, params)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||
"""
|
||
cursor.execute(insert_query, (
|
||
device_data.ip,
|
||
device_data.hostname,
|
||
rtmp_url + device_md5,
|
||
full_webrtc_url, # 存储完整的WEBRTC URL
|
||
"",
|
||
1,
|
||
device_type,
|
||
0,
|
||
device_params_json
|
||
))
|
||
conn.commit()
|
||
|
||
# 获取刚创建的设备信息
|
||
device_id = cursor.lastrowid
|
||
cursor.execute("SELECT * FROM devices WHERE id = %s", (device_id,))
|
||
device = cursor.fetchone()
|
||
|
||
# 设备创建成功后,在后台线程启动WEBRTC流处理
|
||
threading.Thread(
|
||
target=run_webrtc_processing,
|
||
args=(device_data.ip, full_webrtc_url),
|
||
daemon=True # 设为守护线程,主程序退出时自动结束
|
||
).start()
|
||
return APIResponse(
|
||
code=200,
|
||
message="设备创建成功,已开始处理WEBRTC流",
|
||
data=DeviceResponse(**device)
|
||
)
|
||
except MySQLError as e:
|
||
if conn:
|
||
conn.rollback()
|
||
raise Exception(f"创建设备失败:{str(e)}") from e
|
||
except json.JSONDecodeError as e:
|
||
raise Exception(f"设备信息JSON序列化失败:{str(e)}") from e
|
||
except Exception as e:
|
||
if conn:
|
||
conn.rollback()
|
||
raise e
|
||
finally:
|
||
db.close_connection(conn, cursor)
|
||
|
||
|
||
# ------------------------------
|
||
# 2. 获取设备列表
|
||
# ------------------------------
|
||
@router.get("/", response_model=APIResponse, summary="获取设备列表")
|
||
async def get_device_list(
|
||
page: int = Query(1, ge=1, description="页码"),
|
||
page_size: int = Query(10, ge=1, le=100, description="每页条数"),
|
||
device_type: str = Query(None, description="设备类型筛选"),
|
||
online_status: int = Query(None, ge=0, le=1, description="在线状态筛选(1-在线、0-离线)")
|
||
):
|
||
conn = None
|
||
cursor = None
|
||
try:
|
||
conn = db.get_connection()
|
||
cursor = conn.cursor(dictionary=True)
|
||
|
||
# 构建查询条件
|
||
where_clause = []
|
||
params = []
|
||
|
||
if device_type:
|
||
where_clause.append("device_type = %s")
|
||
params.append(device_type)
|
||
|
||
if online_status is not None:
|
||
where_clause.append("device_online_status = %s")
|
||
params.append(online_status)
|
||
|
||
# 总条数查询
|
||
count_query = "SELECT COUNT(*) as total FROM devices"
|
||
if where_clause:
|
||
count_query += " WHERE " + " AND ".join(where_clause)
|
||
|
||
cursor.execute(count_query, params)
|
||
total = cursor.fetchone()["total"]
|
||
|
||
# 分页查询(SELECT * 会自动匹配表字段、响应模型已对齐)
|
||
offset = (page - 1) * page_size
|
||
query = "SELECT * FROM devices"
|
||
if where_clause:
|
||
query += " WHERE " + " AND ".join(where_clause)
|
||
query += " ORDER BY id DESC LIMIT %s OFFSET %s"
|
||
params.extend([page_size, offset])
|
||
|
||
cursor.execute(query, params)
|
||
devices = cursor.fetchall()
|
||
|
||
# 响应模型已更新为params字段、直接转换即可
|
||
device_list = [DeviceResponse(**device) for device in devices]
|
||
|
||
return APIResponse(
|
||
code=200,
|
||
message="获取设备列表成功",
|
||
data=DeviceListResponse(total=total, devices=device_list)
|
||
)
|
||
except MySQLError as e:
|
||
raise Exception(f"获取设备列表失败:{str(e)}") from e
|
||
finally:
|
||
db.close_connection(conn, cursor)
|
||
|
||
|
||
# ------------------------------
|
||
# 3. 获取单个设备详情
|
||
# ------------------------------
|
||
@router.get("/{device_id}", response_model=APIResponse, summary="获取设备详情")
|
||
async def get_device_detail(
|
||
device_id: int,
|
||
current_user: UserResponse = Depends(get_current_user)
|
||
):
|
||
conn = None
|
||
cursor = None
|
||
try:
|
||
conn = db.get_connection()
|
||
cursor = conn.cursor(dictionary=True)
|
||
|
||
# 查询设备信息(SELECT * 匹配表字段)
|
||
query = "SELECT * FROM devices WHERE id = %s"
|
||
cursor.execute(query, (device_id,))
|
||
device = cursor.fetchone()
|
||
|
||
if not device:
|
||
raise HTTPException(
|
||
status_code=404,
|
||
detail=f"设备ID为 {device_id} 的设备不存在"
|
||
)
|
||
|
||
# 响应模型已更新为params字段
|
||
return APIResponse(
|
||
code=200,
|
||
message="获取设备详情成功",
|
||
data=DeviceResponse(**device)
|
||
)
|
||
except MySQLError as e:
|
||
raise Exception(f"获取设备详情失败:{str(e)}") from e
|
||
finally:
|
||
db.close_connection(conn, cursor)
|
||
|
||
|
||
# ------------------------------
|
||
# 4. 删除设备信息
|
||
# ------------------------------
|
||
@router.delete("/{device_id}", response_model=APIResponse, summary="删除设备信息")
|
||
async def delete_device(
|
||
device_id: int,
|
||
current_user: UserResponse = Depends(get_current_user)
|
||
):
|
||
conn = None
|
||
cursor = None
|
||
try:
|
||
conn = db.get_connection()
|
||
cursor = conn.cursor(dictionary=True)
|
||
|
||
# 检查设备是否存在
|
||
cursor.execute("SELECT id FROM devices WHERE id = %s", (device_id,))
|
||
if not cursor.fetchone():
|
||
raise HTTPException(
|
||
status_code=404,
|
||
detail=f"设备ID为 {device_id} 的设备不存在"
|
||
)
|
||
|
||
# 执行删除
|
||
delete_query = "DELETE FROM devices WHERE id = %s"
|
||
cursor.execute(delete_query, (device_id,))
|
||
conn.commit()
|
||
|
||
return APIResponse(
|
||
code=200,
|
||
message=f"设备ID为 {device_id} 的设备已成功删除",
|
||
data=None
|
||
)
|
||
except MySQLError as e:
|
||
if conn:
|
||
conn.rollback()
|
||
raise Exception(f"删除设备失败:{str(e)}") from e
|
||
finally:
|
||
db.close_connection(conn, cursor)
|