diff --git a/apps/server/core/__init__.py b/apps/server/core/__init__.py new file mode 100644 index 0000000..32f91bf --- /dev/null +++ b/apps/server/core/__init__.py @@ -0,0 +1,10 @@ +"""核心基础模块 (配置、日志、异常等)。""" + +from .settings import ( + Settings, + get_settings, + SERVER_DIR, + PROJECT_ROOT, +) + +__all__ = ["Settings", "get_settings", "SERVER_DIR", "PROJECT_ROOT"] diff --git a/apps/server/core/settings.py b/apps/server/core/settings.py new file mode 100644 index 0000000..b879cf4 --- /dev/null +++ b/apps/server/core/settings.py @@ -0,0 +1,175 @@ +"""统一应用配置 (MVP-1 / 新增) + +基于 pydantic-settings 的多环境配置管理,集中收敛原先散落在 +``main.py``、各 service 模块中的环境变量、路径、阈值等配置。 + +加载顺序 (优先级从高到低):: + + 1. 显式构造参数 + 2. 环境变量 (大小写不敏感) + 3. ``.env`` 文件 (位于 apps/server/.env) + 4. 默认值 + +使用方式:: + + from core.settings import get_settings + + settings = get_settings() + print(settings.api.port) + print(settings.detection.default_confidence) +""" + +from __future__ import annotations + +import os +from functools import lru_cache +from pathlib import Path +from typing import List, Optional + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +# 项目根 (apps/server) +SERVER_DIR: Path = Path(__file__).resolve().parent.parent +PROJECT_ROOT: Path = SERVER_DIR.parent.parent + + +# --------------------------------------------------------------------------- +# 子配置 +# --------------------------------------------------------------------------- + + +class APISettings(BaseSettings): + """API 与服务器相关配置。""" + + model_config = SettingsConfigDict(env_prefix="API_", extra="ignore") + + host: str = Field(default="0.0.0.0", description="监听地址") + port: int = Field(default=8000, description="监听端口") + reload: bool = Field(default=True, description="开发模式自动重载") + cors_origins: List[str] = Field( + default_factory=lambda: ["*"], description="允许的跨域来源" + ) + + +class DetectionSettings(BaseSettings): + """检测相关全局默认值。""" + + model_config = SettingsConfigDict(env_prefix="DETECTION_", extra="ignore") + + default_confidence: float = Field(default=0.5, ge=0.0, le=1.0) + default_iou: float = Field(default=0.45, ge=0.0, le=1.0) + # 决策引擎过滤的最低置信度 + min_confidence: float = Field(default=0.3, ge=0.0, le=1.0) + + +class ActionDetectionSettings(BaseSettings): + """ppTSM 行为识别 (Docker) 服务配置。""" + + model_config = SettingsConfigDict(env_prefix="ACTION_DETECTION_", extra="ignore") + + api_url: str = Field(default="http://localhost:8081") + timeout: int = Field(default=30, ge=1) + + +class EventEngineSettings(BaseSettings): + """事件决策 + 聚合 + 规则引擎配置。""" + + model_config = SettingsConfigDict(env_prefix="EVENT_", extra="ignore") + + # 时间窗口去重 (秒),同一 (source_id, event_type, track_id) 在窗口内只产生一条 + dedup_window_seconds: float = Field(default=30.0, ge=0.0) + # 规则 YAML 目录 (相对 server 根) + rules_dir: str = Field(default="config/rules") + # 事件聚合最大活跃事件数 (超过将按 LRU 淘汰) + max_active_events: int = Field(default=1000, ge=1) + + +class LoggingSettings(BaseSettings): + """日志配置。""" + + model_config = SettingsConfigDict(env_prefix="LOG_", extra="ignore") + + level: str = Field(default="INFO") + json_format: bool = Field(default=False) + + +class PathSettings(BaseSettings): + """关键路径 (静态资源 / 外部模型) 配置。""" + + model_config = SettingsConfigDict(env_prefix="PATH_", extra="ignore") + + server_dir: Path = SERVER_DIR + project_root: Path = PROJECT_ROOT + static_dir: Path = SERVER_DIR / "static" + results_dir: Path = SERVER_DIR / "static" / "results" + temp_dir: Path = SERVER_DIR / "static" / "temp" + uploads_dir: Path = SERVER_DIR / "static" / "uploads" + external_paddle: Path = ( + PROJECT_ROOT / "external" / "video-recognition-system" / "PaddlePaddle" + ) + + def ensure(self) -> None: + """确保所有需要的目录存在 (启动时调用)。""" + + for p in (self.static_dir, self.results_dir, self.temp_dir, self.uploads_dir): + p.mkdir(parents=True, exist_ok=True) + + +# --------------------------------------------------------------------------- +# 总配置 +# --------------------------------------------------------------------------- + + +class Settings(BaseSettings): + """应用总配置。 + + 子配置统一在此聚合,便于以 ``settings.api.port`` 这种命名空间方式访问。 + """ + + model_config = SettingsConfigDict( + env_file=str(SERVER_DIR / ".env"), + env_file_encoding="utf-8", + env_nested_delimiter="__", + case_sensitive=False, + extra="ignore", + ) + + env: str = Field(default="development", description="运行环境") + debug: bool = Field(default=True) + + api: APISettings = Field(default_factory=APISettings) + detection: DetectionSettings = Field(default_factory=DetectionSettings) + action_detection: ActionDetectionSettings = Field( + default_factory=ActionDetectionSettings + ) + event_engine: EventEngineSettings = Field(default_factory=EventEngineSettings) + logging: LoggingSettings = Field(default_factory=LoggingSettings) + paths: PathSettings = Field(default_factory=PathSettings) + + +@lru_cache(maxsize=1) +def get_settings() -> Settings: + """获取全局 Settings 单例 (带缓存)。 + + 测试场景下可调用 ``get_settings.cache_clear()`` 重新加载。 + """ + + settings = Settings() + settings.paths.ensure() + return settings + + +__all__ = [ + "Settings", + "APISettings", + "DetectionSettings", + "ActionDetectionSettings", + "EventEngineSettings", + "LoggingSettings", + "PathSettings", + "get_settings", + "SERVER_DIR", + "PROJECT_ROOT", +]