初始化
This commit is contained in:
BIN
service/__pycache__/device_service.cpython-312.pyc
Normal file
BIN
service/__pycache__/device_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
service/__pycache__/user_service.cpython-312.pyc
Normal file
BIN
service/__pycache__/user_service.cpython-312.pyc
Normal file
Binary file not shown.
251
service/device_service.py
Normal file
251
service/device_service.py
Normal file
@ -0,0 +1,251 @@
|
||||
import json
|
||||
|
||||
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
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/devices",
|
||||
tags=["设备管理"]
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# 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 id FROM devices WHERE client_ip = %s", (device_data.ip,))
|
||||
existing_device = cursor.fetchone()
|
||||
if existing_device:
|
||||
raise Exception(f"客户端IP {device_data.ip} 已存在,无法重复添加")
|
||||
|
||||
# 获取RTMP URL
|
||||
rtmp_url = str(LIVE_CONFIG.get("rtmp_url", ""))
|
||||
webrtc_url = str(LIVE_CONFIG.get("webrtc_url", ""))
|
||||
|
||||
# 将设备详细信息(params)转换为JSON字符串(对应表中params字段)
|
||||
device_params_json = json.dumps(device_data.params) if device_data.params else None
|
||||
|
||||
# 对JSON字符串进行MD5加密(用于生成唯一RTMP地址)
|
||||
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"
|
||||
|
||||
# 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,
|
||||
webrtc_url + device_md5,
|
||||
"",
|
||||
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()
|
||||
|
||||
return APIResponse(
|
||||
code=200,
|
||||
message="设备创建成功",
|
||||
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:
|
||||
# 捕获IP已存在的自定义异常
|
||||
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)
|
154
service/user_service.py
Normal file
154
service/user_service.py
Normal file
@ -0,0 +1,154 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from mysql.connector import Error as MySQLError
|
||||
|
||||
from ds.db import db
|
||||
from schema.user_schema import UserRegisterRequest, UserLoginRequest, UserResponse
|
||||
from schema.response_schema import APIResponse
|
||||
from middle.auth_middleware import (
|
||||
get_password_hash,
|
||||
verify_password,
|
||||
create_access_token,
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||
get_current_user
|
||||
)
|
||||
|
||||
# 创建用户接口路由(前缀 /users、标签用于 Swagger 分类)
|
||||
router = APIRouter(
|
||||
prefix="/users",
|
||||
tags=["用户管理"]
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# 1. 用户注册接口
|
||||
# ------------------------------
|
||||
@router.post("/register", response_model=APIResponse, summary="用户注册")
|
||||
async def user_register(request: UserRegisterRequest):
|
||||
"""
|
||||
用户注册:
|
||||
- 校验用户名是否已存在
|
||||
- 加密密码后插入数据库
|
||||
- 返回注册成功信息
|
||||
"""
|
||||
conn = None
|
||||
cursor = None
|
||||
try:
|
||||
conn = db.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
|
||||
# 1. 检查用户名是否已存在(唯一索引)
|
||||
check_query = "SELECT username FROM users WHERE username = %s"
|
||||
cursor.execute(check_query, (request.username,))
|
||||
existing_user = cursor.fetchone()
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"用户名 '{request.username}' 已存在、请更换其他用户名"
|
||||
)
|
||||
|
||||
# 2. 加密密码
|
||||
hashed_password = get_password_hash(request.password)
|
||||
|
||||
# 3. 插入新用户到数据库
|
||||
insert_query = """
|
||||
INSERT INTO users (username, password)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (request.username, hashed_password))
|
||||
conn.commit() # 提交事务
|
||||
|
||||
# 4. 返回注册成功响应
|
||||
return APIResponse(
|
||||
code=201, # 201 表示资源创建成功
|
||||
message=f"用户 '{request.username}' 注册成功",
|
||||
data=None
|
||||
)
|
||||
except MySQLError as e:
|
||||
conn.rollback() # 数据库错误时回滚事务
|
||||
raise Exception(f"注册失败:{str(e)}") from e
|
||||
finally:
|
||||
db.close_connection(conn, cursor)
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# 2. 用户登录接口
|
||||
# ------------------------------
|
||||
@router.post("/login", response_model=APIResponse, summary="用户登录(获取 Token)")
|
||||
async def user_login(request: UserLoginRequest):
|
||||
"""
|
||||
用户登录:
|
||||
- 校验用户名是否存在
|
||||
- 校验密码是否正确
|
||||
- 生成 JWT Token 并返回
|
||||
"""
|
||||
conn = None
|
||||
cursor = None
|
||||
try:
|
||||
conn = db.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
|
||||
# 修复:SQL查询添加 created_at 和 updated_at 字段
|
||||
query = """
|
||||
SELECT id, username, password, created_at, updated_at
|
||||
FROM users
|
||||
WHERE username = %s
|
||||
"""
|
||||
cursor.execute(query, (request.username,))
|
||||
user = cursor.fetchone()
|
||||
|
||||
# 2. 校验用户名和密码
|
||||
if not user or not verify_password(request.password, user["password"]):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="用户名或密码错误",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# 3. 生成 Token(过期时间从配置读取)
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user["username"]},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
# 4. 返回 Token 和用户基本信息
|
||||
return APIResponse(
|
||||
code=200,
|
||||
message="登录成功",
|
||||
data={
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": UserResponse(
|
||||
id=user["id"],
|
||||
username=user["username"],
|
||||
created_at=user.get("created_at"),
|
||||
updated_at=user.get("updated_at")
|
||||
)
|
||||
}
|
||||
)
|
||||
except MySQLError as e:
|
||||
raise Exception(f"登录失败:{str(e)}") from e
|
||||
finally:
|
||||
db.close_connection(conn, cursor)
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# 3. 获取当前登录用户信息(需认证)
|
||||
# ------------------------------
|
||||
@router.get("/me", response_model=APIResponse, summary="获取当前用户信息")
|
||||
async def get_current_user_info(
|
||||
current_user: UserResponse = Depends(get_current_user) # 依赖认证中间件
|
||||
):
|
||||
"""
|
||||
获取当前登录用户信息:
|
||||
- 需在请求头携带 Token(格式:Bearer <token>)
|
||||
- 认证通过后返回用户信息
|
||||
"""
|
||||
return APIResponse(
|
||||
code=200,
|
||||
message="获取用户信息成功",
|
||||
data=current_user
|
||||
)
|
||||
|
Reference in New Issue
Block a user