Merge remote-tracking branch 'origin/master'

This commit is contained in:
ZZX9599
2025-09-15 18:52:08 +08:00
8 changed files with 62 additions and 35 deletions

View File

@ -1,7 +1,8 @@
import json
from functools import wraps
from encryption.encryption import aes_encrypt
from schema.response_schema import APIResponse
from utils.encrypt_utils import aes_encrypt
def encrypt_response(field: str = "data"):

View File

@ -2,6 +2,7 @@ from fastapi import APIRouter, Query, Path
from mysql.connector import Error as MySQLError
from ds.db import db
from encryption.encrypt_decorator import encrypt_response
from schema.device_action_schema import (
DeviceActionCreate,
DeviceActionResponse,
@ -62,6 +63,7 @@ def add_device_action(action_data: DeviceActionCreate) -> DeviceActionResponse:
# 接口: 分页查询操作记录列表(仅返回 total + device_actions
# ------------------------------
@router.get("/list", response_model=APIResponse, summary="分页查询设备操作记录")
@encrypt_response()
async def get_device_action_list(
page: int = Query(1, ge=1, description="页码、默认1"),
page_size: int = Query(10, ge=1, le=100, description="每页条数、1-100"),
@ -119,6 +121,7 @@ async def get_device_action_list(
@router.get("/{client_ip}", response_model=APIResponse, summary="根据IP查询设备操作记录")
@encrypt_response()
async def get_device_actions_by_ip(
client_ip: str = Path(..., description="客户端IP地址")
):

View File

@ -5,6 +5,7 @@ from fastapi import APIRouter, Query, HTTPException, Request, Path
from mysql.connector import Error as MySQLError
from ds.db import db
from encryption.encrypt_decorator import encrypt_response
from schema.device_schema import (
DeviceCreateRequest, DeviceResponse, DeviceListResponse,
DeviceStatusHistoryResponse, DeviceStatusHistoryListResponse
@ -161,6 +162,7 @@ def update_online_status_by_ip(client_ip: str, online_status: int) -> bool:
# 创建设备信息接口
# ------------------------------
@router.post("/add", response_model=APIResponse, summary="创建设备信息")
@encrypt_response()
async def create_device(device_data: DeviceCreateRequest, request: Request):
conn = None
cursor = None
@ -242,6 +244,7 @@ async def create_device(device_data: DeviceCreateRequest, request: Request):
# 获取设备列表接口
# ------------------------------
@router.get("/", response_model=APIResponse, summary="获取设备列表(支持筛选分页)")
@encrypt_response()
async def get_device_list(
page: int = Query(1, ge=1, description="页码默认第1页"),
page_size: int = Query(10, ge=1, le=100, description="每页条数1-100之间"),
@ -301,6 +304,7 @@ async def get_device_list(
# 获取设备上下线记录接口
# ------------------------------
@router.get("/{device_id}/status-history", response_model=APIResponse, summary="获取设备上下线记录")
@encrypt_response()
async def get_device_status_history(
device_id: int = Path(..., description="设备ID"),
page: int = Query(1, ge=1, description="页码默认第1页"),
@ -380,6 +384,7 @@ async def get_device_status_history(
# 手动更新设备在线状态接口
# ------------------------------
@router.put("/{device_id}/status", response_model=APIResponse, summary="更新设备在线状态")
@encrypt_response()
async def update_device_status(
device_id: int = Path(..., description="设备ID"),
status: int = Query(..., ge=0, le=1, description="在线状态1-在线、0-离线)")

View File

@ -5,6 +5,7 @@ import os
from pathlib import Path
from ds.db import db
from encryption.encrypt_decorator import encrypt_response
from schema.face_schema import (
FaceCreateRequest,
FaceUpdateRequest,
@ -23,6 +24,7 @@ router = APIRouter(prefix="/faces", tags=["人脸管理"])
# 1. 创建人脸记录(使用修复后的路径)
# ------------------------------
@router.post("", response_model=APIResponse, summary="创建人脸记录")
@encrypt_response()
async def create_face(
request: Request,
name: str = Form(None, max_length=255, description="名称(可选)"),
@ -97,6 +99,7 @@ async def create_face(
# 2. 获取单个人脸记录
# ------------------------------
@router.get("/{face_id}", response_model=APIResponse, summary="获取单个人脸记录")
@encrypt_response()
async def get_face(face_id: int):
conn = None
cursor = None
@ -130,6 +133,7 @@ async def get_face(face_id: int):
# 3. 获取人脸列表
# ------------------------------
@router.get("", response_model=APIResponse, summary="获取人脸列表(分页+筛选)")
@encrypt_response()
async def get_face_list(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
@ -189,6 +193,7 @@ async def get_face_list(
# 4. 更新人脸记录
# ------------------------------
@router.put("/{face_id}", response_model=APIResponse, summary="更新人脸记录")
@encrypt_response()
async def update_face(face_id: int, face_update: FaceUpdateRequest):
conn = None
cursor = None
@ -255,6 +260,7 @@ async def update_face(face_id: int, face_update: FaceUpdateRequest):
# 5. 删除人脸记录
# ------------------------------
@router.delete("/{face_id}", response_model=APIResponse, summary="删除人脸记录")
@encrypt_response()
async def delete_face(face_id: int):
conn = None
cursor = None
@ -305,6 +311,7 @@ async def delete_face(face_id: int):
# 6. 获取人脸图片
# ------------------------------
@router.get("/{face_id}/image", summary="获取人脸图片")
@encrypt_response()
async def get_face_image(face_id: int):
conn = None
cursor = None

View File

@ -18,12 +18,11 @@ app = Flask(__name__)
# ------------------------------
# 核心修改:与 FastAPI 对齐的跨域配置
# ------------------------------
# 1. 允许的前端域名(完全复制 FastAPI 的 ALLOWED_ORIGINS确保前后端一致
# 1. 允许的前端域名(根据实际环境修改,生产环境删除 "*"
ALLOWED_ORIGINS = [
# "http://localhost:8080", # 本地前端开发地址(必改:替换为你的前端实际地址)
# "http://localhost:8080", # 本地前端开发地址
# "http://127.0.0.1:8080",
# "http://服务器IP:8080", # 部署后前端地址替换为你的服务器IP/域名)
# # "*" 仅开发环境临时使用,生产环境必须删除(安全风险)
# "http://服务器IP:8080", # 部署后前端地址
"*"
]
@ -31,33 +30,29 @@ ALLOWED_ORIGINS = [
CORS(
app,
resources={
r"/*": { # 对所有 Flask 路由生效(覆盖图片、模型下载所有接口)
"origins": ALLOWED_ORIGINS, # 允许的前端域名(与 FastAPI 一致)
"allow_credentials": True, # 允许携带 Cookie与 FastAPI 一致,需登录态必开)
"methods": ["*"], # 允许所有 HTTP 方法FastAPI 用 "*",此处同步)
"allow_headers": ["*"], # 允许所有请求头(与 FastAPI 一致)
r"/*": {
"origins": ALLOWED_ORIGINS,
"allow_credentials": True,
"methods": ["*"],
"allow_headers": ["*"],
}
},
)
# ------------------------------
# 核心路径配置(不变,确保资源目录正确
# 核心路径配置(关键修改:修正 PROJECT_ROOT 计算
# 原问题file_service.py 在 service 文件夹内,需向上跳一级到项目根目录
# ------------------------------
CURRENT_FILE_PATH = Path(__file__).resolve()
PROJECT_ROOT = CURRENT_FILE_PATH.parent # 项目根目录video/
# 资源目录(图片、模型
BASE_IMAGE_DIR_DECT = str((PROJECT_ROOT / "resource" / "dect").resolve()) # 检测图片目录
BASE_IMAGE_DIR_UP_IMAGES = str((PROJECT_ROOT / "up_images").resolve()) # 人脸图片目录
BASE_MODEL_DIR = str((PROJECT_ROOT / "resource" / "models").resolve()) # 模型文件目录
CURRENT_FILE_PATH = Path(__file__).resolve() # 当前文件路径service/file_service.py
PROJECT_ROOT = CURRENT_FILE_PATH.parent.parent # 项目根目录service 文件夹的父目录
# 资源目录(现在正确指向项目根目录下的文件夹
BASE_IMAGE_DIR_DECT = str((PROJECT_ROOT / "resource" / "dect").resolve()) # 根目录/resource/dect
BASE_IMAGE_DIR_UP_IMAGES = str((PROJECT_ROOT / "up_images").resolve()) # 根目录/up_images
BASE_MODEL_DIR = str((PROJECT_ROOT / "resource" / "models").resolve()) # 根目录/resource/models
# 打印路径配置(调试用,确认目录正确)
# logger.info(f"[Flask 配置] 项目根目录:{PROJECT_ROOT}")
# logger.info(f"[Flask 配置] 模型目录:{BASE_MODEL_DIR}")
# logger.info(f"[Flask 配置] 人脸图片目录:{BASE_IMAGE_DIR_UP_IMAGES}")
# logger.info(f"[Flask 配置] 检测图片目录:{BASE_IMAGE_DIR_DECT}")
# ------------------------------
# 安全检查装饰器(不变,防路径遍历/非法文件
# 安全检查装饰器(不变)
# ------------------------------
def safe_path_check(root_dir: str):
def decorator(func):
@ -86,7 +81,7 @@ def safe_path_check(root_dir: str):
)
abort(404)
# 3. 限制文件大小模型200MB图片10MB,避免超大文件攻击
# 3. 限制文件大小模型200MB图片10MB
max_size = 200 * 1024 * 1024 if "models" in root_dir else 10 * 1024 * 1024
if os.path.getsize(full_file_path) > max_size:
logger.warning(
@ -121,7 +116,7 @@ def download_model(resource_path, root_dir):
f"[Flask 模型下载] 成功请求IP{request.remote_addr} | 文件:{file_name} | 目录:{full_dir}"
)
# 强制浏览器下载(而非预览),设置二进制文件类型
# 强制浏览器下载(而非预览)
return send_from_directory(
full_dir,
file_name,
@ -158,7 +153,7 @@ def get_face_image(resource_path, root_dir):
f"[Flask 人脸图片] 成功请求IP{request.remote_addr} | 文件:{file_name} | 目录:{full_dir}"
)
# 允许浏览器预览图片(而非下载)
# 允许浏览器预览图片
return send_from_directory(full_dir, file_name, as_attachment=False)
except Exception as e:
@ -205,7 +200,6 @@ def get_dect_image(resource_path, root_dir):
@safe_path_check(root_dir=BASE_IMAGE_DIR_DECT)
def get_compatible_image(resource_path, root_dir):
try:
# 逻辑与检测图片接口一致仅URL前缀不同兼容旧前端
resource_path = resource_path.replace("/", os.sep).replace("\\", os.sep)
dir_path, file_name = os.path.split(resource_path)
full_dir = os.path.abspath(os.path.join(root_dir, dir_path))
@ -230,7 +224,7 @@ def get_compatible_image(resource_path, root_dir):
abort(500)
# ------------------------------
# 全局错误处理器(友好提示,与 FastAPI 错误信息风格一致
# 全局错误处理器(不变
# ------------------------------
@app.errorhandler(403)
def forbidden_error(error):
@ -256,7 +250,7 @@ def server_error(error):
# Flask 独立启动入口(供测试,实际由 main.py 子线程启动)
# ------------------------------
if __name__ == '__main__':
# 确保所有资源目录存在(防止初始化失败)
# 确保所有资源目录存在
required_dirs = [
(BASE_IMAGE_DIR_DECT, "检测图片目录"),
(BASE_IMAGE_DIR_UP_IMAGES, "人脸图片目录"),
@ -267,16 +261,16 @@ if __name__ == '__main__':
logger.info(f"[Flask 初始化] {dir_desc}不存在,创建:{dir_path}")
os.makedirs(dir_path, exist_ok=True)
# 启动提示(含访问示例)
# 启动提示
logger.info("\n[Flask 服务启动成功!] 支持的接口:")
logger.info(f"1. 模型下载 → http://服务器IP:5000/model/download/resource/models/xxx.pt")
logger.info(f"2. 人脸图片 → http://服务器IP:5000/up_images/xxx.jpg")
logger.info(f"3. 检测图片 → http://服务器IP:5000/resource/dect/xxx.jpg 或 http://服务器IP:5000/images/xxx.jpg\n")
# 启动服务(禁用 debug 和自动重载,避免多线程冲突
# 启动服务(禁用 debug 和自动重载)
app.run(
host="0.0.0.0", # 允许外部IP访问
port=5000, # 与 main.py 中 Flask 端口一致
host="0.0.0.0",
port=5000,
debug=False,
use_reloader=False
)

View File

@ -11,6 +11,7 @@ from mysql.connector import Error as MySQLError
# 复用项目依赖
from ds.db import db
from encryption.encrypt_decorator import encrypt_response
from schema.model_schema import (
ModelCreateRequest,
ModelUpdateRequest,
@ -190,6 +191,7 @@ def get_current_conf_threshold():
# 1. 上传模型(保持不变)
@router.post("", response_model=APIResponse, summary="上传YOLO模型.pt格式")
@encrypt_response()
async def upload_model(
name: str = Form(..., description="模型名称"),
description: str = Form(None, description="模型描述"),
@ -281,6 +283,7 @@ async def upload_model(
# 2. 获取模型列表(保持不变)
@router.get("", response_model=APIResponse, summary="获取模型列表(分页)")
@encrypt_response()
async def get_model_list(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
@ -337,6 +340,7 @@ async def get_model_list(
# 3. 获取默认模型(保持不变)
@router.get("/default", response_model=APIResponse, summary="获取当前默认模型")
@encrypt_response()
async def get_default_model():
conn = None
cursor = None
@ -379,6 +383,7 @@ async def get_default_model():
# 4. 获取单个模型详情(保持不变)
@router.get("/{model_id}", response_model=APIResponse, summary="获取单个模型详情")
@encrypt_response()
async def get_model(model_id: int):
conn = None
cursor = None
@ -414,6 +419,7 @@ async def get_model(model_id: int):
# 5. 更新模型信息(保持不变)
@router.put("/{model_id}", response_model=APIResponse, summary="更新模型信息")
@encrypt_response()
async def update_model(model_id: int, model_update: ModelUpdateRequest):
conn = None
cursor = None
@ -497,6 +503,7 @@ async def update_model(model_id: int, model_update: ModelUpdateRequest):
# 5.1 更换默认模型(添加置信度参数)
@router.put("/{model_id}/set-default", response_model=APIResponse, summary="更换默认模型(自动重启服务)")
@encrypt_response()
async def set_default_model(
model_id: int,
conf_threshold: float = Query(0.8, ge=0.01, le=0.99, description="模型检测置信度阈值0.01-0.99")
@ -591,6 +598,7 @@ async def set_default_model(
# 6. 删除模型(保持不变)
@router.delete("/{model_id}", response_model=APIResponse, summary="删除模型")
@encrypt_response()
async def delete_model(model_id: int):
conn = None
cursor = None
@ -650,6 +658,7 @@ async def delete_model(model_id: int):
# 7. 下载模型文件(保持不变)
@router.get("/{model_id}/download", summary="下载模型文件")
@encrypt_response()
async def download_model(model_id: int):
conn = None
cursor = None

View File

@ -25,6 +25,7 @@ router = APIRouter(
# 1. 创建敏感信息记录
# ------------------------------
@router.post("", response_model=APIResponse, summary="创建敏感信息记录")
@encrypt_response()
async def create_sensitive(
sensitive: SensitiveCreateRequest,
current_user: UserResponse = Depends(get_current_user) # 补充登录认证依赖(与其他接口保持一致)
@ -76,7 +77,9 @@ async def create_sensitive(
# ------------------------------
# 2. 获取单个敏感信息记录
# ------------------------------
@router.get("/{sensitive_id}", response_model=APIResponse, summary="获取单个敏感信息记录")
@encrypt_response()
async def get_sensitive(
sensitive_id: int,
current_user: UserResponse = Depends(get_current_user) # 需登录认证
@ -125,8 +128,7 @@ async def get_sensitive(
async def get_sensitive_list(
page: int = Query(1, ge=1, description="页码默认1最小1"),
page_size: int = Query(10, ge=1, le=100, description="每页条数默认101-100"),
name: Optional[str] = Query(None, description="敏感词关键词搜索(模糊匹配)"),
current_user: UserResponse = Depends(get_current_user) # 需登录认证
name: Optional[str] = Query(None, description="敏感词关键词搜索(模糊匹配)")
):
"""
获取敏感信息分页列表:
@ -191,6 +193,7 @@ async def get_sensitive_list(
# 4. 更新敏感信息记录
# ------------------------------
@router.put("/{sensitive_id}", response_model=APIResponse, summary="更新敏感信息记录")
@encrypt_response()
async def update_sensitive(
sensitive_id: int,
sensitive_update: SensitiveUpdateRequest,
@ -269,6 +272,7 @@ async def update_sensitive(
# 5. 删除敏感信息记录
# ------------------------------
@router.delete("/{sensitive_id}", response_model=APIResponse, summary="删除敏感信息记录")
@encrypt_response()
async def delete_sensitive(
sensitive_id: int,
current_user: UserResponse = Depends(get_current_user) # 需登录认证

View File

@ -27,6 +27,7 @@ router = APIRouter(
# 1. 用户注册接口
# ------------------------------
@router.post("/register", response_model=APIResponse, summary="用户注册")
@encrypt_response()
async def user_register(request: UserRegisterRequest):
"""
用户注册:
@ -78,6 +79,7 @@ async def user_register(request: UserRegisterRequest):
# 2. 用户登录接口
# ------------------------------
@router.post("/login", response_model=APIResponse, summary="用户登录(获取 Token")
@encrypt_response()
async def user_login(request: UserLoginRequest):
"""
用户登录:
@ -140,6 +142,7 @@ async def user_login(request: UserLoginRequest):
# 3. 获取当前登录用户信息(需认证)
# ------------------------------
@router.get("/me", response_model=APIResponse, summary="获取当前用户信息")
@encrypt_response()
async def get_current_user_info(
current_user: UserResponse = Depends(get_current_user) # 依赖认证中间件
):
@ -159,6 +162,7 @@ async def get_current_user_info(
# 4. 获取用户列表(仅需登录权限)
# ------------------------------
@router.get("/list", response_model=APIResponse, summary="获取用户列表")
@encrypt_response()
async def get_user_list(
page: int = Query(1, ge=1, description="页码从1开始"),
page_size: int = Query(10, ge=1, le=100, description="每页条数1-100之间"),