mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-06-27 18:22:03 +08:00
768 lines
25 KiB
Python
768 lines
25 KiB
Python
"""
|
||
配置管理 - 完全基于数据库存储
|
||
所有配置都从数据库读取,不再使用环境变量或 .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"
|
||
|
||
|
||
@dataclass
|
||
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=15555,
|
||
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=120,
|
||
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"
|
||
|
||
@field_validator('database_url', mode='before')
|
||
@classmethod
|
||
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 = 15555
|
||
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 = ""
|
||
|
||
@property
|
||
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 = 120
|
||
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()
|