Spaces:
Paused
Paused
| """ | |
| 配置管理 - 完全基于数据库存储 | |
| 所有配置都从数据库读取,不再使用环境变量或 .env 文件 | |
| """ | |
| import os | |
| from typing import Optional, Dict, Any, Type, List | |
| from enum import Enum | |
| from pydantic import BaseModel, field_validator | |
| from pydantic.types import SecretStr | |
| from dataclasses import dataclass | |
| class SettingCategory(str, Enum): | |
| """设置分类""" | |
| GENERAL = "general" | |
| DATABASE = "database" | |
| WEBUI = "webui" | |
| LOG = "log" | |
| OPENAI = "openai" | |
| PROXY = "proxy" | |
| REGISTRATION = "registration" | |
| EMAIL = "email" | |
| TEMPMAIL = "tempmail" | |
| CUSTOM_DOMAIN = "moe_mail" | |
| SECURITY = "security" | |
| CPA = "cpa" | |
| class SettingDefinition: | |
| """设置定义""" | |
| db_key: str | |
| default_value: Any | |
| category: SettingCategory | |
| description: str = "" | |
| is_secret: bool = False | |
| # 所有配置项定义(包含数据库键名、默认值、分类、描述) | |
| SETTING_DEFINITIONS: Dict[str, SettingDefinition] = { | |
| # 应用信息 | |
| "app_name": SettingDefinition( | |
| db_key="app.name", | |
| default_value="OpenAI/Codex CLI 自动注册系统", | |
| category=SettingCategory.GENERAL, | |
| description="应用名称" | |
| ), | |
| "app_version": SettingDefinition( | |
| db_key="app.version", | |
| default_value="2.0.0", | |
| category=SettingCategory.GENERAL, | |
| description="应用版本" | |
| ), | |
| "debug": SettingDefinition( | |
| db_key="app.debug", | |
| default_value=False, | |
| category=SettingCategory.GENERAL, | |
| description="调试模式" | |
| ), | |
| # 数据库配置 | |
| "database_url": SettingDefinition( | |
| db_key="database.url", | |
| default_value="data/database.db", | |
| category=SettingCategory.DATABASE, | |
| description="数据库路径或连接字符串" | |
| ), | |
| # Web UI 配置 | |
| "webui_host": SettingDefinition( | |
| db_key="webui.host", | |
| default_value="0.0.0.0", | |
| category=SettingCategory.WEBUI, | |
| description="Web UI 监听地址" | |
| ), | |
| "webui_port": SettingDefinition( | |
| db_key="webui.port", | |
| default_value=8000, | |
| category=SettingCategory.WEBUI, | |
| description="Web UI 监听端口" | |
| ), | |
| "webui_secret_key": SettingDefinition( | |
| db_key="webui.secret_key", | |
| default_value="your-secret-key-change-in-production", | |
| category=SettingCategory.WEBUI, | |
| description="Web UI 密钥", | |
| is_secret=True | |
| ), | |
| "webui_access_password": SettingDefinition( | |
| db_key="webui.access_password", | |
| default_value="admin123", | |
| category=SettingCategory.WEBUI, | |
| description="Web UI 访问密码", | |
| is_secret=True | |
| ), | |
| # 日志配置 | |
| "log_level": SettingDefinition( | |
| db_key="log.level", | |
| default_value="INFO", | |
| category=SettingCategory.LOG, | |
| description="日志级别" | |
| ), | |
| "log_file": SettingDefinition( | |
| db_key="log.file", | |
| default_value="logs/app.log", | |
| category=SettingCategory.LOG, | |
| description="日志文件路径" | |
| ), | |
| "log_retention_days": SettingDefinition( | |
| db_key="log.retention_days", | |
| default_value=30, | |
| category=SettingCategory.LOG, | |
| description="日志保留天数" | |
| ), | |
| # OpenAI 配置 | |
| "openai_client_id": SettingDefinition( | |
| db_key="openai.client_id", | |
| default_value="app_EMoamEEZ73f0CkXaXp7hrann", | |
| category=SettingCategory.OPENAI, | |
| description="OpenAI OAuth 客户端 ID" | |
| ), | |
| "openai_auth_url": SettingDefinition( | |
| db_key="openai.auth_url", | |
| default_value="https://auth.openai.com/oauth/authorize", | |
| category=SettingCategory.OPENAI, | |
| description="OpenAI OAuth 授权 URL" | |
| ), | |
| "openai_token_url": SettingDefinition( | |
| db_key="openai.token_url", | |
| default_value="https://auth.openai.com/oauth/token", | |
| category=SettingCategory.OPENAI, | |
| description="OpenAI OAuth Token URL" | |
| ), | |
| "openai_redirect_uri": SettingDefinition( | |
| db_key="openai.redirect_uri", | |
| default_value="http://localhost:1455/auth/callback", | |
| category=SettingCategory.OPENAI, | |
| description="OpenAI OAuth 回调 URI" | |
| ), | |
| "openai_scope": SettingDefinition( | |
| db_key="openai.scope", | |
| default_value="openid email profile offline_access", | |
| category=SettingCategory.OPENAI, | |
| description="OpenAI OAuth 权限范围" | |
| ), | |
| # 代理配置 | |
| "proxy_enabled": SettingDefinition( | |
| db_key="proxy.enabled", | |
| default_value=False, | |
| category=SettingCategory.PROXY, | |
| description="是否启用代理" | |
| ), | |
| "proxy_type": SettingDefinition( | |
| db_key="proxy.type", | |
| default_value="http", | |
| category=SettingCategory.PROXY, | |
| description="代理类型 (http/socks5)" | |
| ), | |
| "proxy_host": SettingDefinition( | |
| db_key="proxy.host", | |
| default_value="127.0.0.1", | |
| category=SettingCategory.PROXY, | |
| description="代理服务器地址" | |
| ), | |
| "proxy_port": SettingDefinition( | |
| db_key="proxy.port", | |
| default_value=7890, | |
| category=SettingCategory.PROXY, | |
| description="代理服务器端口" | |
| ), | |
| "proxy_username": SettingDefinition( | |
| db_key="proxy.username", | |
| default_value="", | |
| category=SettingCategory.PROXY, | |
| description="代理用户名" | |
| ), | |
| "proxy_password": SettingDefinition( | |
| db_key="proxy.password", | |
| default_value="", | |
| category=SettingCategory.PROXY, | |
| description="代理密码", | |
| is_secret=True | |
| ), | |
| "proxy_dynamic_enabled": SettingDefinition( | |
| db_key="proxy.dynamic_enabled", | |
| default_value=False, | |
| category=SettingCategory.PROXY, | |
| description="是否启用动态代理" | |
| ), | |
| "proxy_dynamic_api_url": SettingDefinition( | |
| db_key="proxy.dynamic_api_url", | |
| default_value="", | |
| category=SettingCategory.PROXY, | |
| description="动态代理 API 地址,返回代理 URL 字符串" | |
| ), | |
| "proxy_dynamic_api_key": SettingDefinition( | |
| db_key="proxy.dynamic_api_key", | |
| default_value="", | |
| category=SettingCategory.PROXY, | |
| description="动态代理 API 密钥(可选)", | |
| is_secret=True | |
| ), | |
| "proxy_dynamic_api_key_header": SettingDefinition( | |
| db_key="proxy.dynamic_api_key_header", | |
| default_value="X-API-Key", | |
| category=SettingCategory.PROXY, | |
| description="动态代理 API 密钥请求头名称" | |
| ), | |
| "proxy_dynamic_result_field": SettingDefinition( | |
| db_key="proxy.dynamic_result_field", | |
| default_value="", | |
| category=SettingCategory.PROXY, | |
| description="从 JSON 响应中提取代理 URL 的字段路径(留空则使用响应原文)" | |
| ), | |
| # 注册配置 | |
| "registration_max_retries": SettingDefinition( | |
| db_key="registration.max_retries", | |
| default_value=3, | |
| category=SettingCategory.REGISTRATION, | |
| description="注册最大重试次数" | |
| ), | |
| "registration_timeout": SettingDefinition( | |
| db_key="registration.timeout", | |
| default_value=120, | |
| category=SettingCategory.REGISTRATION, | |
| description="注册超时时间(秒)" | |
| ), | |
| "registration_default_password_length": SettingDefinition( | |
| db_key="registration.default_password_length", | |
| default_value=12, | |
| category=SettingCategory.REGISTRATION, | |
| description="默认密码长度" | |
| ), | |
| "registration_sleep_min": SettingDefinition( | |
| db_key="registration.sleep_min", | |
| default_value=5, | |
| category=SettingCategory.REGISTRATION, | |
| description="注册间隔最小值(秒)" | |
| ), | |
| "registration_sleep_max": SettingDefinition( | |
| db_key="registration.sleep_max", | |
| default_value=30, | |
| category=SettingCategory.REGISTRATION, | |
| description="注册间隔最大值(秒)" | |
| ), | |
| # 邮箱服务配置 | |
| "email_service_priority": SettingDefinition( | |
| db_key="email.service_priority", | |
| default_value={"tempmail": 0, "outlook": 1, "moe_mail": 2}, | |
| category=SettingCategory.EMAIL, | |
| description="邮箱服务优先级" | |
| ), | |
| # Tempmail.lol 配置 | |
| "tempmail_base_url": SettingDefinition( | |
| db_key="tempmail.base_url", | |
| default_value="https://api.tempmail.lol/v2", | |
| category=SettingCategory.TEMPMAIL, | |
| description="Tempmail API 地址" | |
| ), | |
| "tempmail_timeout": SettingDefinition( | |
| db_key="tempmail.timeout", | |
| default_value=30, | |
| category=SettingCategory.TEMPMAIL, | |
| description="Tempmail 超时时间(秒)" | |
| ), | |
| "tempmail_max_retries": SettingDefinition( | |
| db_key="tempmail.max_retries", | |
| default_value=3, | |
| category=SettingCategory.TEMPMAIL, | |
| description="Tempmail 最大重试次数" | |
| ), | |
| # 自定义域名邮箱配置 | |
| "custom_domain_base_url": SettingDefinition( | |
| db_key="custom_domain.base_url", | |
| default_value="", | |
| category=SettingCategory.CUSTOM_DOMAIN, | |
| description="自定义域名 API 地址" | |
| ), | |
| "custom_domain_api_key": SettingDefinition( | |
| db_key="custom_domain.api_key", | |
| default_value="", | |
| category=SettingCategory.CUSTOM_DOMAIN, | |
| description="自定义域名 API 密钥", | |
| is_secret=True | |
| ), | |
| # 安全配置 | |
| "encryption_key": SettingDefinition( | |
| db_key="security.encryption_key", | |
| default_value="your-encryption-key-change-in-production", | |
| category=SettingCategory.SECURITY, | |
| description="加密密钥", | |
| is_secret=True | |
| ), | |
| # Team Manager 配置 | |
| "tm_enabled": SettingDefinition( | |
| db_key="tm.enabled", | |
| default_value=False, | |
| category=SettingCategory.GENERAL, | |
| description="是否启用 Team Manager 上传" | |
| ), | |
| "tm_api_url": SettingDefinition( | |
| db_key="tm.api_url", | |
| default_value="", | |
| category=SettingCategory.GENERAL, | |
| description="Team Manager API 地址" | |
| ), | |
| "tm_api_key": SettingDefinition( | |
| db_key="tm.api_key", | |
| default_value="", | |
| category=SettingCategory.GENERAL, | |
| description="Team Manager API Key", | |
| is_secret=True | |
| ), | |
| # CPA 上传配置 | |
| "cpa_enabled": SettingDefinition( | |
| db_key="cpa.enabled", | |
| default_value=False, | |
| category=SettingCategory.CPA, | |
| description="是否启用 CPA 上传" | |
| ), | |
| "cpa_api_url": SettingDefinition( | |
| db_key="cpa.api_url", | |
| default_value="", | |
| category=SettingCategory.CPA, | |
| description="CPA API 地址" | |
| ), | |
| "cpa_api_token": SettingDefinition( | |
| db_key="cpa.api_token", | |
| default_value="", | |
| category=SettingCategory.CPA, | |
| description="CPA API Token", | |
| is_secret=True | |
| ), | |
| # 验证码配置 | |
| "email_code_timeout": SettingDefinition( | |
| db_key="email_code.timeout", | |
| default_value=30, | |
| category=SettingCategory.EMAIL, | |
| description="验证码等待超时时间(秒)" | |
| ), | |
| "email_code_poll_interval": SettingDefinition( | |
| db_key="email_code.poll_interval", | |
| default_value=3, | |
| category=SettingCategory.EMAIL, | |
| description="验证码轮询间隔(秒)" | |
| ), | |
| # Outlook 配置 | |
| "outlook_provider_priority": SettingDefinition( | |
| db_key="outlook.provider_priority", | |
| default_value=["imap_old", "imap_new", "graph_api"], | |
| category=SettingCategory.EMAIL, | |
| description="Outlook 提供者优先级" | |
| ), | |
| "outlook_health_failure_threshold": SettingDefinition( | |
| db_key="outlook.health_failure_threshold", | |
| default_value=5, | |
| category=SettingCategory.EMAIL, | |
| description="Outlook 提供者连续失败次数阈值" | |
| ), | |
| "outlook_health_disable_duration": SettingDefinition( | |
| db_key="outlook.health_disable_duration", | |
| default_value=60, | |
| category=SettingCategory.EMAIL, | |
| description="Outlook 提供者禁用时长(秒)" | |
| ), | |
| "outlook_default_client_id": SettingDefinition( | |
| db_key="outlook.default_client_id", | |
| default_value="24d9a0ed-8787-4584-883c-2fd79308940a", | |
| category=SettingCategory.EMAIL, | |
| description="Outlook OAuth 默认 Client ID" | |
| ), | |
| } | |
| # 属性名到数据库键名的映射(用于向后兼容) | |
| DB_SETTING_KEYS = {name: defn.db_key for name, defn in SETTING_DEFINITIONS.items()} | |
| # 类型定义映射 | |
| SETTING_TYPES: Dict[str, Type] = { | |
| "debug": bool, | |
| "webui_port": int, | |
| "log_retention_days": int, | |
| "proxy_enabled": bool, | |
| "proxy_port": int, | |
| "proxy_dynamic_enabled": bool, | |
| "registration_max_retries": int, | |
| "registration_timeout": int, | |
| "registration_default_password_length": int, | |
| "registration_sleep_min": int, | |
| "registration_sleep_max": int, | |
| "email_service_priority": dict, | |
| "tempmail_timeout": int, | |
| "tempmail_max_retries": int, | |
| "tm_enabled": bool, | |
| "cpa_enabled": bool, | |
| "email_code_timeout": int, | |
| "email_code_poll_interval": int, | |
| "outlook_provider_priority": list, | |
| "outlook_health_failure_threshold": int, | |
| "outlook_health_disable_duration": int, | |
| } | |
| # 需要作为 SecretStr 处理的字段 | |
| SECRET_FIELDS = {name for name, defn in SETTING_DEFINITIONS.items() if defn.is_secret} | |
| def _convert_value(attr_name: str, value: str) -> Any: | |
| """将数据库字符串值转换为正确的类型""" | |
| if attr_name in SECRET_FIELDS: | |
| return SecretStr(value) if value else SecretStr("") | |
| target_type = SETTING_TYPES.get(attr_name, str) | |
| if target_type == bool: | |
| if isinstance(value, bool): | |
| return value | |
| return str(value).lower() in ("true", "1", "yes", "on") | |
| elif target_type == int: | |
| if isinstance(value, int): | |
| return value | |
| return int(value) if value else 0 | |
| elif target_type == dict: | |
| if isinstance(value, dict): | |
| return value | |
| if not value: | |
| return {} | |
| import json | |
| import ast | |
| try: | |
| return json.loads(value) | |
| except (json.JSONDecodeError, ValueError): | |
| try: | |
| return ast.literal_eval(value) | |
| except Exception: | |
| return {} | |
| elif target_type == list: | |
| if isinstance(value, list): | |
| return value | |
| if not value: | |
| return [] | |
| import json | |
| import ast | |
| try: | |
| return json.loads(value) | |
| except (json.JSONDecodeError, ValueError): | |
| try: | |
| return ast.literal_eval(value) | |
| except Exception: | |
| return [] | |
| else: | |
| return value | |
| def _normalize_database_url(url: str) -> str: | |
| if url.startswith("postgres://"): | |
| return "postgresql+psycopg://" + url[len("postgres://"):] | |
| if url.startswith("postgresql://"): | |
| return "postgresql+psycopg://" + url[len("postgresql://"):] | |
| return url | |
| def _value_to_string(value: Any) -> str: | |
| """将值转换为数据库存储的字符串""" | |
| if isinstance(value, SecretStr): | |
| return value.get_secret_value() | |
| elif isinstance(value, bool): | |
| return "true" if value else "false" | |
| elif isinstance(value, (dict, list)): | |
| import json | |
| return json.dumps(value) | |
| elif value is None: | |
| return "" | |
| else: | |
| return str(value) | |
| def init_default_settings() -> None: | |
| """ | |
| 初始化数据库中的默认设置 | |
| 如果设置项不存在,则创建并设置默认值 | |
| """ | |
| try: | |
| from ..database.session import get_db | |
| from ..database.crud import get_setting, set_setting | |
| with get_db() as db: | |
| for attr_name, defn in SETTING_DEFINITIONS.items(): | |
| existing = get_setting(db, defn.db_key) | |
| if not existing: | |
| default_value = defn.default_value | |
| if attr_name == "database_url": | |
| env_url = os.environ.get("APP_DATABASE_URL") or os.environ.get("DATABASE_URL") | |
| if env_url: | |
| default_value = _normalize_database_url(env_url) | |
| default_value = _value_to_string(default_value) | |
| set_setting( | |
| db, | |
| defn.db_key, | |
| default_value, | |
| category=defn.category.value, | |
| description=defn.description | |
| ) | |
| print(f"[Settings] 初始化默认设置: {defn.db_key} = {default_value if not defn.is_secret else '***'}") | |
| except Exception as e: | |
| if "未初始化" not in str(e): | |
| print(f"[Settings] 初始化默认设置失败: {e}") | |
| def _load_settings_from_db() -> Dict[str, Any]: | |
| """从数据库加载所有设置""" | |
| try: | |
| from ..database.session import get_db | |
| from ..database.crud import get_setting | |
| settings_dict = {} | |
| with get_db() as db: | |
| for attr_name, defn in SETTING_DEFINITIONS.items(): | |
| db_setting = get_setting(db, defn.db_key) | |
| if db_setting: | |
| settings_dict[attr_name] = _convert_value(attr_name, db_setting.value) | |
| else: | |
| # 数据库中没有此设置,使用默认值 | |
| settings_dict[attr_name] = _convert_value(attr_name, _value_to_string(defn.default_value)) | |
| env_url = os.environ.get("APP_DATABASE_URL") or os.environ.get("DATABASE_URL") | |
| if env_url: | |
| settings_dict["database_url"] = _normalize_database_url(env_url) | |
| env_host = os.environ.get("APP_HOST") | |
| if env_host: | |
| settings_dict["webui_host"] = env_host | |
| env_port = os.environ.get("APP_PORT") | |
| if env_port: | |
| try: | |
| settings_dict["webui_port"] = int(env_port) | |
| except ValueError: | |
| pass | |
| env_password = os.environ.get("APP_ACCESS_PASSWORD") | |
| if env_password: | |
| settings_dict["webui_access_password"] = env_password | |
| return settings_dict | |
| except Exception as e: | |
| if "未初始化" not in str(e): | |
| print(f"[Settings] 从数据库加载设置失败: {e},使用默认值") | |
| return {name: defn.default_value for name, defn in SETTING_DEFINITIONS.items()} | |
| def _save_settings_to_db(**kwargs) -> None: | |
| """保存设置到数据库""" | |
| try: | |
| from ..database.session import get_db | |
| from ..database.crud import set_setting | |
| with get_db() as db: | |
| for attr_name, value in kwargs.items(): | |
| if attr_name in SETTING_DEFINITIONS: | |
| defn = SETTING_DEFINITIONS[attr_name] | |
| str_value = _value_to_string(value) | |
| set_setting( | |
| db, | |
| defn.db_key, | |
| str_value, | |
| category=defn.category.value, | |
| description=defn.description | |
| ) | |
| except Exception as e: | |
| if "未初始化" not in str(e): | |
| print(f"[Settings] 保存设置到数据库失败: {e}") | |
| class Settings(BaseModel): | |
| """ | |
| 应用配置 - 完全基于数据库存储 | |
| """ | |
| # 应用信息 | |
| app_name: str = "OpenAI/Codex CLI 自动注册系统" | |
| app_version: str = "2.0.0" | |
| debug: bool = False | |
| # 数据库配置 | |
| database_url: str = "data/database.db" | |
| def validate_database_url(cls, v): | |
| if isinstance(v, str): | |
| if v.startswith(("postgres://", "postgresql://")): | |
| return _normalize_database_url(v) | |
| if v.startswith(("postgresql+psycopg://", "postgresql+psycopg2://")): | |
| return v | |
| if isinstance(v, str) and v.startswith("sqlite:///"): | |
| return v | |
| if isinstance(v, str) and not v.startswith(("sqlite:///", "postgresql://", "postgresql+psycopg://", "postgresql+psycopg2://", "mysql://")): | |
| # 如果是文件路径,转换为 SQLite URL | |
| if os.path.isabs(v) or ":/" not in v: | |
| return f"sqlite:///{v}" | |
| return v | |
| # Web UI 配置 | |
| webui_host: str = "0.0.0.0" | |
| webui_port: int = 8000 | |
| webui_secret_key: SecretStr = SecretStr("your-secret-key-change-in-production") | |
| webui_access_password: SecretStr = SecretStr("admin123") | |
| # 日志配置 | |
| log_level: str = "INFO" | |
| log_file: str = "logs/app.log" | |
| log_retention_days: int = 30 | |
| # OpenAI 配置 | |
| openai_client_id: str = "app_EMoamEEZ73f0CkXaXp7hrann" | |
| openai_auth_url: str = "https://auth.openai.com/oauth/authorize" | |
| openai_token_url: str = "https://auth.openai.com/oauth/token" | |
| openai_redirect_uri: str = "http://localhost:1455/auth/callback" | |
| openai_scope: str = "openid email profile offline_access" | |
| # 代理配置 | |
| proxy_enabled: bool = False | |
| proxy_type: str = "http" | |
| proxy_host: str = "127.0.0.1" | |
| proxy_port: int = 7890 | |
| proxy_username: Optional[str] = None | |
| proxy_password: Optional[SecretStr] = None | |
| proxy_dynamic_enabled: bool = False | |
| proxy_dynamic_api_url: str = "" | |
| proxy_dynamic_api_key: Optional[SecretStr] = None | |
| proxy_dynamic_api_key_header: str = "X-API-Key" | |
| proxy_dynamic_result_field: str = "" | |
| def proxy_url(self) -> Optional[str]: | |
| """获取完整的代理 URL""" | |
| if not self.proxy_enabled: | |
| return None | |
| if self.proxy_type == "http": | |
| scheme = "http" | |
| elif self.proxy_type == "socks5": | |
| scheme = "socks5" | |
| else: | |
| return None | |
| auth = "" | |
| if self.proxy_username and self.proxy_password: | |
| auth = f"{self.proxy_username}:{self.proxy_password.get_secret_value()}@" | |
| return f"{scheme}://{auth}{self.proxy_host}:{self.proxy_port}" | |
| # 注册配置 | |
| registration_max_retries: int = 3 | |
| registration_timeout: int = 120 | |
| registration_default_password_length: int = 12 | |
| registration_sleep_min: int = 5 | |
| registration_sleep_max: int = 30 | |
| # 邮箱服务配置 | |
| email_service_priority: Dict[str, int] = {"tempmail": 0, "outlook": 1, "moe_mail": 2} | |
| # Tempmail.lol 配置 | |
| tempmail_base_url: str = "https://api.tempmail.lol/v2" | |
| tempmail_timeout: int = 30 | |
| tempmail_max_retries: int = 3 | |
| # 自定义域名邮箱配置 | |
| custom_domain_base_url: str = "" | |
| custom_domain_api_key: Optional[SecretStr] = None | |
| # 安全配置 | |
| encryption_key: SecretStr = SecretStr("your-encryption-key-change-in-production") | |
| # Team Manager 配置 | |
| tm_enabled: bool = False | |
| tm_api_url: str = "" | |
| tm_api_key: Optional[SecretStr] = None | |
| # CPA 上传配置 | |
| cpa_enabled: bool = False | |
| cpa_api_url: str = "" | |
| cpa_api_token: SecretStr = SecretStr("") | |
| # 验证码配置 | |
| email_code_timeout: int = 30 | |
| email_code_poll_interval: int = 3 | |
| # Outlook 配置 | |
| outlook_provider_priority: List[str] = ["imap_old", "imap_new", "graph_api"] | |
| outlook_health_failure_threshold: int = 5 | |
| outlook_health_disable_duration: int = 60 | |
| outlook_default_client_id: str = "24d9a0ed-8787-4584-883c-2fd79308940a" | |
| # 全局配置实例 | |
| _settings: Optional[Settings] = None | |
| def get_settings() -> Settings: | |
| """ | |
| 获取全局配置实例(单例模式) | |
| 完全从数据库加载配置 | |
| """ | |
| global _settings | |
| if _settings is None: | |
| # 先初始化默认设置(如果数据库中没有的话) | |
| init_default_settings() | |
| # 从数据库加载所有设置 | |
| settings_dict = _load_settings_from_db() | |
| _settings = Settings(**settings_dict) | |
| return _settings | |
| def update_settings(**kwargs) -> Settings: | |
| """ | |
| 更新配置并保存到数据库 | |
| """ | |
| global _settings | |
| if _settings is None: | |
| _settings = get_settings() | |
| # 创建新的配置实例 | |
| updated_data = _settings.model_dump() | |
| updated_data.update(kwargs) | |
| _settings = Settings(**updated_data) | |
| # 保存到数据库 | |
| _save_settings_to_db(**kwargs) | |
| return _settings | |
| def get_database_url() -> str: | |
| """ | |
| 获取数据库 URL(处理相对路径) | |
| """ | |
| settings = get_settings() | |
| url = settings.database_url | |
| # 如果 URL 是相对路径,转换为绝对路径 | |
| if url.startswith("sqlite:///"): | |
| path = url[10:] # 移除 "sqlite:///" | |
| if not os.path.isabs(path): | |
| # 转换为相对于项目根目录的路径 | |
| project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) | |
| abs_path = os.path.join(project_root, path) | |
| return f"sqlite:///{abs_path}" | |
| return url | |
| def get_setting_definition(attr_name: str) -> Optional[SettingDefinition]: | |
| """获取设置项的定义信息""" | |
| return SETTING_DEFINITIONS.get(attr_name) | |
| def get_all_setting_definitions() -> Dict[str, SettingDefinition]: | |
| """获取所有设置项的定义""" | |
| return SETTING_DEFINITIONS.copy() | |