diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..05cdaba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.10-slim + +# 设置工作目录 +WORKDIR /app + +# 复制依赖文件 +COPY requirements.txt . + +# 安装依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY src/ . + +# 暴露端口 +EXPOSE 8000 + +# 设置环境变量 +ENV PYTHONPATH=/app +ENV DEBUG=True + +# 启动命令 +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/src/__init__.py b/algorithm/__init__.py similarity index 100% rename from src/__init__.py rename to algorithm/__init__.py diff --git a/src/algorithm/face_recognition_algorithm.py b/algorithm/face_recognition_algorithm.py similarity index 100% rename from src/algorithm/face_recognition_algorithm.py rename to algorithm/face_recognition_algorithm.py diff --git a/src/api/dependencies.py b/api/dependencies.py similarity index 100% rename from src/api/dependencies.py rename to api/dependencies.py diff --git a/src/api/errors.py b/api/errors.py similarity index 100% rename from src/api/errors.py rename to api/errors.py diff --git a/src/api/routes/algorithm_router.py b/api/routes/algorithm_router.py similarity index 100% rename from src/api/routes/algorithm_router.py rename to api/routes/algorithm_router.py diff --git a/src/api/routes/face_features.py b/api/routes/face_features.py similarity index 100% rename from src/api/routes/face_features.py rename to api/routes/face_features.py diff --git a/src/algorithm/__init__.py b/biz/__init__.py similarity index 100% rename from src/algorithm/__init__.py rename to biz/__init__.py diff --git a/src/biz/base_face_biz.py b/biz/base_face_biz.py similarity index 100% rename from src/biz/base_face_biz.py rename to biz/base_face_biz.py diff --git a/src/biz/video_check_biz.py b/biz/video_check_biz.py similarity index 100% rename from src/biz/video_check_biz.py rename to biz/video_check_biz.py diff --git a/src/biz/video_face_biz.py b/biz/video_face_biz.py similarity index 100% rename from src/biz/video_face_biz.py rename to biz/video_face_biz.py diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..0f1df34 --- /dev/null +++ b/config.yaml @@ -0,0 +1,4 @@ +cameras: + - id: 1 + name: "Entrance" + rtsp_url: "rtsp://8.130.165.33:8554/test" \ No newline at end of file diff --git a/src/database/base.py b/database/base.py similarity index 100% rename from src/database/base.py rename to database/base.py diff --git a/src/database/connection.py b/database/connection.py similarity index 100% rename from src/database/connection.py rename to database/connection.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9f01746 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.8' + +services: + face-recognition: + build: . + ports: + - "8000:8000" + environment: + - DEBUG=True + - DATABASE_URL=postgresql://username:password@host:port/database + restart: unless-stopped + volumes: + - ./src/videos:/app/videos + - ./src/face_database.pkl:/app/face_database.pkl \ No newline at end of file diff --git a/face_database.pkl b/face_database.pkl new file mode 100644 index 0000000..ebe707e Binary files /dev/null and b/face_database.pkl differ diff --git a/src/main.py b/main.py similarity index 100% rename from src/main.py rename to main.py diff --git a/src/models/face_feature.py b/models/face_feature.py similarity index 100% rename from src/models/face_feature.py rename to models/face_feature.py diff --git a/src/models/sur_config.py b/models/sur_config.py similarity index 100% rename from src/models/sur_config.py rename to models/sur_config.py diff --git a/src/models/sur_person.py b/models/sur_person.py similarity index 100% rename from src/models/sur_person.py rename to models/sur_person.py diff --git a/src/models/video_check_task.py b/models/video_check_task.py similarity index 100% rename from src/models/video_check_task.py rename to models/video_check_task.py diff --git a/src/npu_yolo_onnx_yolo11n.py b/npu_yolo_onnx_yolo11n.py similarity index 100% rename from src/npu_yolo_onnx_yolo11n.py rename to npu_yolo_onnx_yolo11n.py diff --git a/src/repositories/face_feature_repository.py b/repositories/face_feature_repository.py similarity index 100% rename from src/repositories/face_feature_repository.py rename to repositories/face_feature_repository.py diff --git a/src/repositories/sur_config_repository.py b/repositories/sur_config_repository.py similarity index 100% rename from src/repositories/sur_config_repository.py rename to repositories/sur_config_repository.py diff --git a/src/repositories/sur_person_repository.py b/repositories/sur_person_repository.py similarity index 100% rename from src/repositories/sur_person_repository.py rename to repositories/sur_person_repository.py diff --git a/src/repositories/video_check_repository.py b/repositories/video_check_repository.py similarity index 100% rename from src/repositories/video_check_repository.py rename to repositories/video_check_repository.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d8dbf3f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +insightface==0.7.3 +numpy==2.3.5 +onnxruntime_gpu==1.23.2 +opencv_contrib_python==4.12.0.88 +opencv_python_headless==4.12.0.88 +setuptools==75.6.0 +setuptools==80.9.0 +sympy==1.13.1 +sympy==1.14.0 +torch==2.5.1+cu121 diff --git a/src/rtsp/service.py b/rtsp/service.py similarity index 100% rename from src/rtsp/service.py rename to rtsp/service.py diff --git a/src/rtsp_service_ws_1217.py b/rtsp_service_ws_1217.py similarity index 100% rename from src/rtsp_service_ws_1217.py rename to rtsp_service_ws_1217.py diff --git a/src/run.py b/run.py similarity index 100% rename from src/run.py rename to run.py diff --git a/src/schemas/face_feature.py b/schemas/face_feature.py similarity index 100% rename from src/schemas/face_feature.py rename to schemas/face_feature.py diff --git a/src/services/face_feature_service.py b/services/face_feature_service.py similarity index 100% rename from src/services/face_feature_service.py rename to services/face_feature_service.py diff --git a/src/app.py b/src/app.py deleted file mode 100644 index 17377d3..0000000 --- a/src/app.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -FastAPI主应用 -将原来的main.py重命名为app.py -""" - -import time -from contextlib import asynccontextmanager -from fastapi import FastAPI, Request -from fastapi.exceptions import RequestValidationError -from fastapi.middleware.cors import CORSMiddleware -from fastapi.middleware.trustedhost import TrustedHostMiddleware -from fastapi.responses import JSONResponse -from fastapi.openapi.docs import ( - get_swagger_ui_html, - get_swagger_ui_oauth2_redirect_html, - get_redoc_html, -) -from fastapi.staticfiles import StaticFiles - -from api.routes import face_features -from api.routes.algorithm_router import router as algorithm_router, sync_videofacebiz_params, sync_videofacebiz_blacklist -from api.errors import ( - APIError, - validation_exception_handler, - api_error_handler, - generic_exception_handler -) -from config import settings -from database.connection import init_database -from database.connection import db_manager -from rtsp.service import rtsp_server - - -# 生命周期管理 -@asynccontextmanager -async def lifespan(app: FastAPI): - """ - 应用生命周期管理 - - - 启动时:初始化数据库 - - 关闭时:清理资源 - """ - # 启动时 - print("🚀 start algorithm service...") - print(f"📊 db: {settings.DATABASE_NAME}") - print(f"🔧 debug mode: {settings.DEBUG}") - - # 初始化数据库 - init_database() - - # 数据库健康检查 - if db_manager.health_check(): - print("✅ 数据库连接正常") - else: - print("❌ 数据库连接失败") - - # 启动 RTSP 服务(如果启用) - if settings.RTSP_ENABLED: - print("📹 启动 RTSP 服务...") - rtsp_server.start() - # 将 RTSP 服务实例保存到应用状态 - app.state.rtsp_server = rtsp_server - - # 自动同步VideoFaceBiz参数和黑名单 - print("🔄 自动同步VideoFaceBiz参数和黑名单...") - try: - params_updated = sync_videofacebiz_params() - blacklist_loaded = sync_videofacebiz_blacklist() - print(f"✅ 自动同步完成 - 参数更新: {params_updated}个, 黑名单加载: {blacklist_loaded}个") - except Exception as e: - print(f"⚠️ 自动同步失败: {e}") - else: - print("⚠️ RTSP 服务未启用") - - yield - - # 关闭时 - print("🛑 algorithm service stopped...") - - # 停止 RTSP 服务 - if settings.RTSP_ENABLED: - print("🛑 停止 RTSP 服务...") - rtsp_server.stop() - - db_manager.close() - - -# 创建FastAPI应用 -app = FastAPI( - title=settings.PROJECT_NAME, - version=settings.PROJECT_VERSION, - description=settings.PROJECT_DESCRIPTION, - openapi_url=f"{settings.API_V1_PREFIX}/openapi.json", - docs_url=None, # 自定义docs - redoc_url=None, # 自定义redoc - lifespan=lifespan -) - - -# 自定义API文档页面 -@app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): - return get_swagger_ui_html( - openapi_url=app.openapi_url, - title=f"{app.title} - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js", - swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css", - ) - - -@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) -async def swagger_ui_redirect(): - return get_swagger_ui_oauth2_redirect_html() - - -@app.get("/redoc", include_in_schema=False) -async def redoc_html(): - return get_redoc_html( - openapi_url=app.openapi_url, - title=f"{app.title} - ReDoc", - redoc_js_url="https://unpkg.com/redoc@next/bundles/redoc.standalone.js", - ) - - -# 中间件配置 -app.add_middleware( - CORSMiddleware, - allow_origins=settings.BACKEND_CORS_ORIGINS, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -app.add_middleware( - TrustedHostMiddleware, - allowed_hosts=["*"] if settings.DEBUG else ["localhost", "127.0.0.1", "0.0.0.0"] -) - - -# 请求计时中间件 -@app.middleware("http") -async def add_process_time_header(request: Request, call_next): - """ - 添加请求处理时间头 - """ - start_time = time.time() - response = await call_next(request) - process_time = time.time() - start_time - response.headers["X-Process-Time"] = str(process_time) - return response - - -# 异常处理器 -app.add_exception_handler(RequestValidationError, validation_exception_handler) -app.add_exception_handler(APIError, api_error_handler) -app.add_exception_handler(Exception, generic_exception_handler) - - -# 根路由 -@app.get("/") -async def root(): - """ - 根路径 - """ - return { - "message": "algorithm service", - "version": settings.PROJECT_VERSION, - "docs": "/docs", - "api_prefix": settings.API_V1_PREFIX - } - - -@app.get("/health") -async def health_check(): - """ - 健康检查端点 - """ - # 检查数据库连接 - db_healthy = db_manager.health_check() - - return { - "status": "healthy" if db_healthy else "unhealthy", - "database": "connected" if db_healthy else "disconnected", - "timestamp": time.time() - } - - -# 注册API路由 -app.include_router( - face_features.router, - prefix=settings.API_V1_PREFIX -) - -app.include_router( - algorithm_router, - prefix=settings.API_V1_PREFIX -) - - -# 自定义404处理器 -@app.exception_handler(404) -async def not_found_handler(request: Request, exc): - """ - 自定义404错误处理器 - """ - return JSONResponse( - status_code=404, - content={ - "error": { - "code": "NOT_FOUND", - "message": f"请求的资源不存在: {request.url.path}" - } - } - ) - - -# 导出应用实例 -__all__ = ["app"] \ No newline at end of file diff --git a/src/biz/__init__.py b/src/biz/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/config.py b/src/config.py deleted file mode 100644 index 48a4bb6..0000000 --- a/src/config.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -数据库配置模块 -使用pydantic进行配置验证和管理 -""" - -from typing import Optional, List -from pydantic_settings import BaseSettings -from functools import lru_cache -from pydantic import PostgresDsn, field_validator -from pydantic_core.core_schema import FieldValidationInfo - - -class Settings(BaseSettings): - """应用配置类""" - - RTSP_ENABLED: bool = True - - # API配置 - API_V1_PREFIX: str = "/api/v1" - PROJECT_NAME: str = "algorithm-service" - PROJECT_VERSION: str = "1.0.0" - PROJECT_DESCRIPTION: str = "algorithm-service" - BACKEND_CORS_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:8000"] - - - # 数据库配置 - DATABASE_HOST: str = "localhost" - DATABASE_PORT: int = 5432 - DATABASE_USER: str = "postgres" - DATABASE_PASSWORD: str = "yipai123" - DATABASE_NAME: str = "pmms" - DATABASE_SCHEMA: str = "public" - - # 连接池配置 - DATABASE_POOL_SIZE: int = 10 - DATABASE_MAX_OVERFLOW: int = 20 - DATABASE_POOL_RECYCLE: int = 3600 # 连接回收时间(秒) - DATABASE_ECHO: bool = False # SQL日志,生产环境设为False - - # 应用配置 - APP_NAME: str = "SurFaceFeature API" - APP_VERSION: str = "1.0.0" - DEBUG: bool = False - - # 日志配置 - LOG_LEVEL: str = "INFO" - LOG_FILE: Optional[str] = None - - # 异步配置 - ASYNC_MODE: bool = False - - # 资源文件夹配置 - FACE_REGISTER_IMAGE_RESOURCE_DIR: str = "D:/ruoyi/uploadPath/face" - VIDEO_RESOURCE_DIR: str = "D:/ruoyi/uploadPath/video" - FACE_CAL_FEATURE_TIMEOUT_HOURS: int = 10 - FACE_MODEL_VERSION: int = 0 #insight_face_buffalo_l - FACE_USE_GPU: bool = True - FACE_USE_NPU: bool = False - SUR_CONFIG_TYPE_FACE: int = 0 - SUR_CONFIG_SCOPE_GLOBAL: int = 0 - - # JWT配置(预留) - SECRET_KEY: str = "your-secret-key-here-change-in-production" - ALGORITHM: str = "HS256" - ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 - - @property - def DATABASE_URL(self) -> str: - """构建数据库连接URL""" - return f"postgresql://{self.DATABASE_USER}:{self.DATABASE_PASSWORD}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}" - - @property - def ASYNC_DATABASE_URL(self) -> str: - """构建异步数据库连接URL""" - return f"postgresql+asyncpg://{self.DATABASE_USER}:{self.DATABASE_PASSWORD}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}" - - @field_validator("DATABASE_POOL_SIZE") - def validate_pool_size(cls, v): - """验证连接池大小""" - if v < 1: - raise ValueError("DATABASE_POOL_SIZE must be at least 1") - if v > 100: - raise ValueError("DATABASE_POOL_SIZE cannot exceed 100") - return v - - class Config: - env_file = ".env" - env_file_encoding = "utf-8" - case_sensitive = False - extra = "ignore" - - -@lru_cache() -def get_settings() -> Settings: - """ - 获取配置单例 - 使用lru_cache避免重复加载.env文件 - """ - return Settings() - - -# 导出配置实例 -settings = get_settings() \ No newline at end of file diff --git a/src/utils/logger.py b/src/utils/logger.py deleted file mode 100644 index 5ae9da3..0000000 --- a/src/utils/logger.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -日志配置模块 -""" - -import logging -import sys -from typing import Optional -from logging.handlers import RotatingFileHandler - -from config import settings - - -def setup_logger( - name: str, - level: Optional[str] = None, - log_file: Optional[str] = None -) -> logging.Logger: - """ - 配置和获取logger - - Args: - name: logger名称 - level: 日志级别 - log_file: 日志文件路径 - - Returns: - 配置好的logger实例 - """ - # 获取日志级别 - if level is None: - level = settings.LOG_LEVEL - - log_level = getattr(logging, level.upper(), logging.INFO) - - # 创建logger - logger = logging.getLogger(name) - logger.setLevel(log_level) - - # 避免重复添加handler - if logger.handlers: - return logger - - # 创建formatter - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - - # 控制台handler - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(log_level) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - - # 文件handler(如果配置了日志文件) - if log_file or settings.LOG_FILE: - file_path = log_file or settings.LOG_FILE - try: - file_handler = RotatingFileHandler( - file_path, - maxBytes=10 * 1024 * 1024, # 10MB - backupCount=5, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - except Exception as e: - logger.warning(f"Failed to create log file handler: {e}") - - return logger - - -# 创建根logger -root_logger = setup_logger("sur_face_feature") - - -def get_logger(name: str) -> logging.Logger: - """ - 获取指定名称的logger - - Args: - name: logger名称 - - Returns: - logger实例 - """ - return setup_logger(name) \ No newline at end of file diff --git a/src/video_face_recognition_cann_3.py b/video_face_recognition_cann_3.py similarity index 100% rename from src/video_face_recognition_cann_3.py rename to video_face_recognition_cann_3.py