From 76efc047b3850a05497616f5036d79d15f5ed716 Mon Sep 17 00:00:00 2001 From: cnlimiter Date: Sun, 15 Mar 2026 03:03:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(settings):=20=E6=B7=BB=E5=8A=A0=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2=E5=92=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E5=AD=98=E5=82=A8=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/settings.py | 4 --- src/services/outlook.py | 37 ++++++++++++++++++++---- src/web/routes/settings.py | 59 ++++++++++++++++++++++++++++++++++++++ static/js/settings.js | 45 ++++++++++++++++++++++++++++- templates/settings.html | 47 ++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 10 deletions(-) diff --git a/src/config/settings.py b/src/config/settings.py index a289b92..e875eb2 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -110,10 +110,6 @@ class Settings(BaseSettings): tempmail_timeout: int = Field(default=30) tempmail_max_retries: int = Field(default=3) - # 验证码等待配置 - email_code_timeout: int = Field(default=120) # 验证码等待超时(秒) - email_code_poll_interval: int = Field(default=3) # 验证码轮询间隔(秒) - # 自定义域名邮箱配置 custom_domain_base_url: str = Field(default="") custom_domain_api_key: Optional[SecretStr] = Field(default=None) diff --git a/src/services/outlook.py b/src/services/outlook.py index 5bbdfdf..51c5571 100644 --- a/src/services/outlook.py +++ b/src/services/outlook.py @@ -27,8 +27,35 @@ from ..config.constants import ( OTP_CODE_SEMANTIC_PATTERN, OPENAI_EMAIL_SENDERS, OPENAI_VERIFICATION_KEYWORDS, + OTP_WAIT_TIMEOUT, + OTP_POLL_INTERVAL, ) -from ..config import get_settings +from ..database import crud +from ..database.session import get_db + + +def get_email_code_settings() -> dict: + """ + 从数据库获取验证码等待配置 + + Returns: + dict: 包含 timeout 和 poll_interval 的字典 + """ + try: + with get_db() as db: + timeout_setting = crud.get_setting(db, "email_code.timeout") + poll_interval_setting = crud.get_setting(db, "email_code.poll_interval") + + return { + "timeout": int(timeout_setting.value) if timeout_setting else OTP_WAIT_TIMEOUT, + "poll_interval": int(poll_interval_setting.value) if poll_interval_setting else OTP_POLL_INTERVAL, + } + except Exception as e: + logger.warning(f"获取验证码配置失败,使用默认值: {e}") + return { + "timeout": OTP_WAIT_TIMEOUT, + "poll_interval": OTP_POLL_INTERVAL, + } logger = logging.getLogger(__name__) @@ -474,10 +501,10 @@ class OutlookService(BaseEmailService): self.update_status(False, EmailServiceError(f"未找到邮箱对应的账户: {email}")) return None - # 使用配置的超时时间 - settings = get_settings() - actual_timeout = timeout or settings.email_code_timeout - poll_interval = settings.email_code_poll_interval + # 从数据库获取验证码等待配置 + code_settings = get_email_code_settings() + actual_timeout = timeout or code_settings["timeout"] + poll_interval = code_settings["poll_interval"] logger.info(f"[{email}] 开始获取验证码,超时 {actual_timeout}s,OTP发送时间: {otp_sent_at}") diff --git a/src/web/routes/settings.py b/src/web/routes/settings.py index 329d1c4..d378922 100644 --- a/src/web/routes/settings.py +++ b/src/web/routes/settings.py @@ -11,6 +11,7 @@ from pydantic import BaseModel from ...database import crud from ...database.session import get_db from ...config.settings import get_settings, update_settings +from ...config.constants import OTP_WAIT_TIMEOUT, OTP_POLL_INTERVAL logger = logging.getLogger(__name__) router = APIRouter() @@ -71,6 +72,11 @@ async def get_all_settings(): """获取所有设置""" settings = get_settings() + # 从数据库获取验证码设置 + with get_db() as db: + timeout_setting = crud.get_setting(db, "email_code.timeout") + poll_interval_setting = crud.get_setting(db, "email_code.poll_interval") + return { "proxy": { "enabled": settings.proxy_enabled, @@ -97,6 +103,10 @@ async def get_all_settings(): "timeout": settings.tempmail_timeout, "max_retries": settings.tempmail_max_retries, }, + "email_code": { + "timeout": int(timeout_setting.value) if timeout_setting else OTP_WAIT_TIMEOUT, + "poll_interval": int(poll_interval_setting.value) if poll_interval_setting else OTP_POLL_INTERVAL, + }, } @@ -362,6 +372,12 @@ class TempmailSettings(BaseModel): enabled: bool = True +class EmailCodeSettings(BaseModel): + """验证码等待设置""" + timeout: int = 120 # 验证码等待超时(秒) + poll_interval: int = 3 # 验证码轮询间隔(秒) + + @router.get("/tempmail") async def get_tempmail_settings(): """获取临时邮箱设置""" @@ -388,6 +404,49 @@ async def update_tempmail_settings(request: TempmailSettings): return {"success": True, "message": "临时邮箱设置已更新"} +# ============== 验证码等待设置 ============== + +@router.get("/email-code") +async def get_email_code_settings(): + """获取验证码等待设置""" + with get_db() as db: + timeout_setting = crud.get_setting(db, "email_code.timeout") + poll_interval_setting = crud.get_setting(db, "email_code.poll_interval") + + return { + "timeout": int(timeout_setting.value) if timeout_setting else OTP_WAIT_TIMEOUT, + "poll_interval": int(poll_interval_setting.value) if poll_interval_setting else OTP_POLL_INTERVAL, + } + + +@router.post("/email-code") +async def update_email_code_settings(request: EmailCodeSettings): + """更新验证码等待设置""" + with get_db() as db: + # 验证参数范围 + if request.timeout < 30 or request.timeout > 600: + raise HTTPException(status_code=400, detail="超时时间必须在 30-600 秒之间") + if request.poll_interval < 1 or request.poll_interval > 30: + raise HTTPException(status_code=400, detail="轮询间隔必须在 1-30 秒之间") + + crud.set_setting( + db, + "email_code.timeout", + str(request.timeout), + description="验证码等待超时(秒)", + category="email" + ) + crud.set_setting( + db, + "email_code.poll_interval", + str(request.poll_interval), + description="验证码轮询间隔(秒)", + category="email" + ) + + return {"success": True, "message": "验证码等待设置已更新"} + + # ============== 代理列表 CRUD ============== class ProxyCreateRequest(BaseModel): diff --git a/static/js/settings.js b/static/js/settings.js index 4b50437..2a545d0 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -40,7 +40,9 @@ const elements = { proxyModalTitle: document.getElementById('proxy-modal-title'), // CPA 设置 cpaForm: document.getElementById('cpa-form'), - testCpaBtn: document.getElementById('test-cpa-btn') + testCpaBtn: document.getElementById('test-cpa-btn'), + // 验证码设置 + emailCodeForm: document.getElementById('email-code-form') }; // 选中的服务 ID @@ -206,6 +208,11 @@ function initEventListeners() { if (elements.testCpaBtn) { elements.testCpaBtn.addEventListener('click', handleTestCpa); } + + // 验证码设置 + if (elements.emailCodeForm) { + elements.emailCodeForm.addEventListener('submit', handleSaveEmailCode); + } } // 加载设置 @@ -227,6 +234,12 @@ async function loadSettings() { document.getElementById('sleep-min').value = data.registration?.sleep_min || 5; document.getElementById('sleep-max').value = data.registration?.sleep_max || 30; + // 验证码等待配置 + if (data.email_code) { + document.getElementById('email-code-timeout').value = data.email_code.timeout || 120; + document.getElementById('email-code-poll-interval').value = data.email_code.poll_interval || 3; + } + // 加载 CPA 设置 loadCpaSettings(); @@ -399,6 +412,36 @@ async function handleSaveRegistration(e) { } } +// 保存验证码等待配置 +async function handleSaveEmailCode(e) { + e.preventDefault(); + + const timeout = parseInt(document.getElementById('email-code-timeout').value); + const pollInterval = parseInt(document.getElementById('email-code-poll-interval').value); + + // 客户端验证 + if (timeout < 30 || timeout > 600) { + toast.error('等待超时必须在 30-600 秒之间'); + return; + } + if (pollInterval < 1 || pollInterval > 30) { + toast.error('轮询间隔必须在 1-30 秒之间'); + return; + } + + const data = { + timeout: timeout, + poll_interval: pollInterval + }; + + try { + await api.post('/settings/email-code', data); + toast.success('验证码配置已保存'); + } catch (error) { + toast.error('保存失败: ' + error.message); + } +} + // 备份数据库 async function handleBackup() { elements.backupBtn.disabled = true; diff --git a/templates/settings.html b/templates/settings.html index 7c9dff9..d2fbfd8 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -37,6 +37,7 @@ + @@ -270,6 +271,52 @@ + +
+
+
+

验证码等待配置

+ 配置 Outlook 邮箱验证码获取的超时时间和轮询间隔 +
+
+
+
+
+ + + 等待验证码的最大时间,建议 60-300 秒 +
+ +
+ + + 检查邮箱的时间间隔,建议 2-5 秒 +
+
+ +
+ +
+
+
+
+ +
+
+

验证码获取策略

+
+
+
    +
  • 渐进式检查:前 3 次轮询只检查未读邮件,之后检查所有邮件
  • +
  • 时间戳过滤:自动跳过 OTP 发送前的旧邮件
  • +
  • 验证码去重:避免重复使用同一验证码
  • +
  • 多策略提取:主题优先 → 语义匹配 → 兜底匹配
  • +
  • 发件人验证:严格验证邮件来自 OpenAI 官方
  • +
+
+
+
+