feat(email): update email polling logic to use configurable intervals

This commit is contained in:
cnlimiter
2026-03-28 00:48:16 +08:00
parent 101169ec4a
commit b751d656ea
12 changed files with 46 additions and 53 deletions

View File

@@ -37,7 +37,7 @@ from ..config.settings import get_settings
logger = logging.getLogger(__name__)
OTP_SECONDARY_TIMEOUT_SECONDS = 120
OTP_SECONDARY_TIMEOUT_SECONDS = 120 # 默认值,运行时由 get_settings().email_code_timeout 覆盖
PHASE_EMAIL_PREPARE = "email_prepare"
PHASE_OTP_SECONDARY = "otp_secondary"
ERROR_EMAIL_PROVIDER_RATE_LIMITED = "EMAIL_PROVIDER_RATE_LIMITED"
@@ -646,7 +646,7 @@ class RegistrationEngine:
email_id = self.email_info.get("service_id") if self.email_info else None
budget = Budget(
timeout_seconds=OTP_SECONDARY_TIMEOUT_SECONDS,
timeout_seconds=get_settings().email_code_timeout,
started_at=started_at if started_at is not None else time.time(),
)
remaining_timeout = budget.remaining_seconds()

View File

@@ -13,11 +13,21 @@ from typing import Optional, Dict, Any, List
from enum import Enum
from ..config.constants import EmailServiceType, OTP_CODE_PATTERN, OTP_CODE_SEMANTIC_PATTERN
from ..config.settings import get_settings
logger = logging.getLogger(__name__)
EMAIL_PROVIDER_BACKOFF_BASE_SECONDS = 30
def get_email_code_settings() -> dict:
"""获取验证码等待配置timeout、poll_interval"""
settings = get_settings()
return {
"timeout": settings.email_code_timeout,
"poll_interval": settings.email_code_poll_interval,
}
EMAIL_PROVIDER_BACKOFF_MAX_SECONDS = 3600
OTP_TIMEOUT_ERROR_PREFIX = "OTP_TIMEOUT"

View File

@@ -10,7 +10,7 @@ import time
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError, get_email_code_settings
from ..config.constants import OTP_CODE_PATTERN
from ..core.http_client import HTTPClient, RequestConfig
@@ -231,6 +231,7 @@ class CloudMailService(BaseEmailService):
) -> Optional[str]:
logger.info(f"正在从 Cloud Mail 邮箱 {email} 获取验证码...")
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_mail_ids: set = set()
@@ -252,7 +253,7 @@ class CloudMailService(BaseEmailService):
mails = mails["list"]
if not isinstance(mails, list):
time.sleep(3)
time.sleep(poll_interval)
continue
for mail in mails:
@@ -288,7 +289,7 @@ class CloudMailService(BaseEmailService):
except Exception as e:
logger.debug(f"检查 Cloud Mail 邮件时出错: {e}")
time.sleep(3)
time.sleep(poll_interval)
logger.warning(f"等待 Cloud Mail 验证码超时: {email}")
return None

View File

@@ -12,7 +12,7 @@ from datetime import datetime, timezone
from html import unescape
from typing import Any, Dict, List, Optional
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError, get_email_code_settings
from ..config.constants import OTP_CODE_PATTERN
from ..core.http_client import HTTPClient, RequestConfig
@@ -258,6 +258,7 @@ class DuckMailService(BaseEmailService):
logger.warning(f"DuckMail 邮箱缺少访问 token: {email}")
return None
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_message_ids = set()
@@ -307,7 +308,7 @@ class DuckMailService(BaseEmailService):
except Exception as e:
logger.debug(f"DuckMail 轮询验证码失败: {e}")
time.sleep(3)
time.sleep(poll_interval)
return None

View File

@@ -10,7 +10,7 @@ import random
import string
from typing import Optional, Dict, Any, List
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError, get_email_code_settings
from ..core.http_client import HTTPClient, RequestConfig
from ..config.constants import OTP_CODE_PATTERN
@@ -211,6 +211,7 @@ class FreemailService(BaseEmailService):
"""
logger.info(f"正在从 Freemail 邮箱 {email} 获取验证码...")
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_mail_ids: set = set()
@@ -218,7 +219,7 @@ class FreemailService(BaseEmailService):
try:
mails = self._make_request("GET", "/api/emails", params={"mailbox": email, "limit": 20})
if not isinstance(mails, list):
time.sleep(3)
time.sleep(poll_interval)
continue
ordered_mails = self._sort_items_by_message_time(
@@ -287,7 +288,7 @@ class FreemailService(BaseEmailService):
except Exception as e:
logger.debug(f"检查 Freemail 邮件时出错: {e}")
time.sleep(3)
time.sleep(poll_interval)
logger.warning(f"等待 Freemail 验证码超时: {email}")
return None

View File

@@ -12,7 +12,7 @@ import logging
from email.header import decode_header
from typing import Any, Dict, Optional
from .base import BaseEmailService, EmailServiceError
from .base import BaseEmailService, EmailServiceError, get_email_code_settings
from ..config.constants import (
EmailServiceType,
OPENAI_EMAIL_SENDERS,
@@ -123,6 +123,7 @@ class ImapMailService(BaseEmailService):
otp_sent_at: Optional[float] = None,
) -> Optional[str]:
"""轮询 IMAP 收件箱,获取 OpenAI 验证码"""
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_ids: set = set()
mail = None
@@ -136,7 +137,7 @@ class ImapMailService(BaseEmailService):
# 搜索所有未读邮件
status, data = mail.search(None, "UNSEEN")
if status != "OK" or not data or not data[0]:
time.sleep(3)
time.sleep(poll_interval)
continue
msg_ids = data[0].split()
@@ -177,7 +178,7 @@ class ImapMailService(BaseEmailService):
except Exception:
pass
time.sleep(3)
time.sleep(poll_interval)
except Exception as e:
logger.warning(f"IMAP 连接/轮询失败: {e}")

View File

@@ -10,7 +10,7 @@ import logging
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError, get_email_code_settings
from ..core.http_client import HTTPClient, RequestConfig
from ..config.constants import OTP_CODE_PATTERN
@@ -303,6 +303,7 @@ class MeoMailEmailService(BaseEmailService):
logger.info(f"正在从自定义域名邮箱 {email} 获取验证码...")
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_message_ids = set()
@@ -313,7 +314,7 @@ class MeoMailEmailService(BaseEmailService):
messages = response.get("messages", [])
if not isinstance(messages, list):
time.sleep(3)
time.sleep(poll_interval)
continue
ordered_messages = self._sort_items_by_message_time(
@@ -370,7 +371,7 @@ class MeoMailEmailService(BaseEmailService):
logger.debug(f"检查邮件时出错: {e}")
# 等待一段时间再检查
time.sleep(3)
time.sleep(poll_interval)
logger.warning(f"等待验证码超时: {email}")
return None

View File

@@ -8,9 +8,8 @@ import threading
import time
from typing import Optional, Dict, Any, List
from ..base import BaseEmailService, EmailServiceError, EmailServiceStatus, EmailServiceType
from ..base import BaseEmailService, EmailServiceError, EmailServiceStatus, EmailServiceType, get_email_code_settings
from ...config.constants import EmailServiceType as ServiceType
from ...config.settings import get_settings
from .account import OutlookAccount
from .base import ProviderType, EmailMessage
from .email_parser import EmailParser, get_email_parser
@@ -34,15 +33,6 @@ DEFAULT_PROVIDER_PRIORITY = [
]
def get_email_code_settings() -> dict:
"""获取验证码等待配置"""
settings = get_settings()
return {
"timeout": settings.email_code_timeout,
"poll_interval": settings.email_code_poll_interval,
}
class OutlookService(BaseEmailService):
"""
Outlook 邮箱服务

View File

@@ -20,7 +20,7 @@ from email.header import decode_header
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from .base import BaseEmailService, EmailServiceError, EmailServiceType
from .base import BaseEmailService, EmailServiceError, EmailServiceType, get_email_code_settings
from ..config.constants import (
OTP_CODE_PATTERN,
OTP_CODE_SIMPLE_PATTERN,
@@ -28,21 +28,6 @@ from ..config.constants import (
OPENAI_EMAIL_SENDERS,
OPENAI_VERIFICATION_KEYWORDS,
)
from ..config.settings import get_settings
def get_email_code_settings() -> dict:
"""
获取验证码等待配置
Returns:
dict: 包含 timeout 和 poll_interval 的字典
"""
settings = get_settings()
return {
"timeout": settings.email_code_timeout,
"poll_interval": settings.email_code_poll_interval,
}
logger = logging.getLogger(__name__)

View File

@@ -15,7 +15,7 @@ from email.policy import default as email_policy
from html import unescape
from typing import Optional, Dict, Any, List
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError
from .base import BaseEmailService, EmailServiceError, EmailServiceType, RateLimitedEmailServiceError, get_email_code_settings
from ..core.http_client import HTTPClient, RequestConfig
from ..config.constants import OTP_CODE_PATTERN
@@ -307,6 +307,7 @@ class TempMailService(BaseEmailService):
logger.info(f"正在从 TempMail 邮箱 {email} 获取验证码...")
start_time = time.time()
poll_interval = get_email_code_settings()["poll_interval"]
seen_mail_ids: set = set()
# 优先使用用户级 JWT回退到 admin API 先注释用户级API
@@ -332,7 +333,7 @@ class TempMailService(BaseEmailService):
# /user_api/mails 和 /admin/mails 返回格式相同: {"results": [...], "total": N}
mails = response.get("results", [])
if not isinstance(mails, list):
time.sleep(3)
time.sleep(poll_interval)
continue
ordered_mails = self._sort_items_by_message_time(
@@ -381,7 +382,7 @@ class TempMailService(BaseEmailService):
except Exception as e:
logger.debug(f"检查 TempMail 邮件时出错: {e}")
time.sleep(3)
time.sleep(poll_interval)
logger.warning(f"等待 TempMail 验证码超时: {email}")
return None

View File

@@ -8,7 +8,7 @@ import logging
from typing import Optional, Dict, Any, List
from datetime import datetime, timezone
from .base import BaseEmailService, EmailServiceError, EmailServiceType
from .base import BaseEmailService, EmailServiceError, EmailServiceType, get_email_code_settings
from ..core.http_client import HTTPClient, RequestConfig
from ..config.constants import OTP_CODE_PATTERN
@@ -212,6 +212,7 @@ class TempmailService(BaseEmailService):
logger.info(f"正在等待邮箱 {email} 的验证码...")
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_ids = set()
@@ -225,7 +226,7 @@ class TempmailService(BaseEmailService):
)
if response.status_code != 200:
time.sleep(3)
time.sleep(poll_interval)
continue
data = response.json()
@@ -238,7 +239,7 @@ class TempmailService(BaseEmailService):
email_list = data.get("emails", []) if isinstance(data, dict) else []
if not isinstance(email_list, list):
time.sleep(3)
time.sleep(poll_interval)
continue
ordered_emails = self._sort_items_by_message_time(
@@ -292,7 +293,7 @@ class TempmailService(BaseEmailService):
logger.debug(f"检查邮件时出错: {e}")
# 等待一段时间再检查
time.sleep(3)
time.sleep(poll_interval)
logger.warning(f"等待验证码超时: {email}")
return None
@@ -384,6 +385,7 @@ class TempmailService(BaseEmailService):
Returns:
验证码或 None
"""
poll_interval = get_email_code_settings()["poll_interval"]
start_time = time.time()
seen_ids = set()
check_count = 0
@@ -402,7 +404,7 @@ class TempmailService(BaseEmailService):
try:
data = self.get_inbox(token)
if not data:
time.sleep(3)
time.sleep(poll_interval)
continue
# 检查 inbox 是否过期
@@ -465,7 +467,7 @@ class TempmailService(BaseEmailService):
"message": "检查邮件时出错"
})
time.sleep(3)
time.sleep(poll_interval)
if callback:
callback({

View File

@@ -559,7 +559,7 @@ MyProxy|socks5://user:pass@host:port"></textarea>
<div class="card" style="margin-top: var(--spacing-lg);">
<div class="card-header">
<h3>验证码等待配置</h3>
<span class="hint">配置 Outlook 邮箱验证码获取的超时时间和轮询间隔</span>
<span class="hint">配置验证码获取的超时时间和轮询间隔</span>
</div>
<div class="card-body">
<form id="email-code-form">