import os import json from pathlib import Path from dotenv import load_dotenv # 基础路径配置 BASE_DIR = Path(__file__).parent.parent.parent REPO_DIR = BASE_DIR.parent UPLOAD_DIR = BASE_DIR / "uploads" AUDIO_DIR = UPLOAD_DIR / "audio" TEMP_UPLOAD_DIR = UPLOAD_DIR / "temp_audio" MARKDOWN_DIR = UPLOAD_DIR / "markdown" CLIENT_DIR = UPLOAD_DIR / "clients" EXTERNAL_APPS_DIR = UPLOAD_DIR / "external_apps" USER_DIR = UPLOAD_DIR / "user" LEGACY_VOICEPRINT_DIR = USER_DIR / "voiceprint" LEGACY_AVATAR_DIR = USER_DIR / "avatar" VOICEPRINT_DIR = USER_DIR AVATAR_DIR = USER_DIR def _is_running_in_docker() -> bool: return Path("/.dockerenv").exists() def _load_env_file(dotenv_path: Path) -> None: if dotenv_path.exists(): load_dotenv(dotenv_path=dotenv_path, override=False) # 非 Docker 本地运行时,兼容读取仓库根目录 .env 与 backend/.env; # Docker 容器内只使用显式注入的环境变量,避免意外读取镜像内的开发配置。 if not _is_running_in_docker(): _load_env_file(REPO_DIR / ".env") _load_env_file(BASE_DIR / ".env") def _get_env(*names: str, default: str | None = None, allow_blank: bool = True) -> str | None: for name in names: value = os.getenv(name) if value is None: continue value = value.strip() if isinstance(value, str) else value if value == "" and not allow_blank: continue return value return default def _get_int_env(*names: str, default: int) -> int: raw_value = _get_env(*names, default=str(default), allow_blank=False) try: return int(raw_value) if raw_value is not None else default except (TypeError, ValueError): return default def _normalize_base_url(value: str | None) -> str: normalized = str(value or "").strip().rstrip("/") return normalized or "http://localhost" def get_user_data_dir(user_id: int | str) -> Path: return USER_DIR / str(user_id) def get_user_voiceprint_dir(user_id: int | str) -> Path: return get_user_data_dir(user_id) / "voiceprint" def get_user_avatar_dir(user_id: int | str) -> Path: return get_user_data_dir(user_id) / "avatar" # 文件上传配置 ALLOWED_EXTENSIONS = {".mp3", ".wav", ".m4a", ".mpeg", ".mp4"} ALLOWED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} ALLOWED_VOICEPRINT_EXTENSIONS = {".wav"} ALLOWED_CLIENT_EXTENSIONS = {".apk", ".exe", ".dmg", ".deb", ".rpm", ".pkg", ".msi", ".zip", ".tar.gz"} MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB MAX_CLIENT_SIZE = 500 * 1024 * 1024 # 500MB for client installers # 确保上传目录存在 UPLOAD_DIR.mkdir(exist_ok=True) AUDIO_DIR.mkdir(exist_ok=True) MARKDOWN_DIR.mkdir(exist_ok=True) CLIENT_DIR.mkdir(exist_ok=True) EXTERNAL_APPS_DIR.mkdir(exist_ok=True) USER_DIR.mkdir(exist_ok=True) LEGACY_VOICEPRINT_DIR.mkdir(exist_ok=True) LEGACY_AVATAR_DIR.mkdir(exist_ok=True) # 数据库配置 DATABASE_CONFIG = { 'host': _get_env('DB_HOST', 'MYSQL_HOST', default='127.0.0.1', allow_blank=False), 'user': _get_env('DB_USER', 'MYSQL_USER', default='root', allow_blank=False), 'password': _get_env('DB_PASSWORD', 'MYSQL_PASSWORD', default=''), 'database': _get_env('DB_NAME', 'MYSQL_DATABASE', default='imeeting', allow_blank=False), 'port': _get_int_env('DB_PORT', 'MYSQL_PORT', default=3306), 'charset': 'utf8mb4' } # API配置 API_CONFIG = { 'host': _get_env('API_HOST', default='0.0.0.0', allow_blank=False), 'port': _get_int_env('API_PORT', default=8000) } # 七牛云配置 # QINIU_ACCESS_KEY = os.getenv('QINIU_ACCESS_KEY', 'A0tp96HCtg-wZCughTgi5vc2pJnw3btClwxRE_e8') # QINIU_SECRET_KEY = os.getenv('QINIU_SECRET_KEY', 'Lj-MSHpaVbmzpS86kMIjmwikvYOT9iPBjCk9hm6k') # QINIU_BUCKET = os.getenv('QINIU_BUCKET', 'imeeting_dev') # QINIU_DOMAIN = os.getenv('QINIU_DOMAIN', 't0vogyxkz.hn-bkt.clouddn.com') # 应用配置 APP_CONFIG = { 'base_url': _normalize_base_url(_get_env('BASE_URL', default='http://localhost', allow_blank=False)) } # Redis配置 REDIS_CONFIG = { 'host': _get_env('REDIS_HOST', default='127.0.0.1', allow_blank=False), 'port': _get_int_env('REDIS_PORT', default=6379), 'db': _get_int_env('REDIS_DB', default=0), 'password': _get_env('REDIS_PASSWORD', default=''), 'decode_responses': True } # 转录轮询配置 - 用于 upload-audio-complete 接口 TRANSCRIPTION_POLL_CONFIG = { 'poll_interval': _get_int_env('TRANSCRIPTION_POLL_INTERVAL', default=10), # 轮询间隔:10秒 'max_wait_time': _get_int_env('TRANSCRIPTION_MAX_WAIT_TIME', default=1800), # 最大等待:30分钟 } # 后台任务配置 BACKGROUND_TASK_CONFIG = { 'summary_workers': _get_int_env('SUMMARY_TASK_MAX_WORKERS', default=2), 'monitor_workers': _get_int_env('MONITOR_TASK_MAX_WORKERS', default=8), 'transcription_status_cache_ttl': _get_int_env('TRANSCRIPTION_STATUS_CACHE_TTL', default=3), }