Files
shaguabijia-app-server/app/integrations/sms/__init__.py
T
2026-07-30 16:02:58 +08:00

73 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""短信验证码服务 —— provider 分派入口(极光主 + 可选创蓝备)。
对外暴露 `send_code`(返回 SendResult / `verify_code` / `SmsError` / `SendResult`。
主 provider = settings.SMS_PROVIDER(默认 jiguang);备 = settings.SMS_FALLBACK_PROVIDER
(默认空=无备)。**每次调用读 settings**,支持运行时切换 / 灰度回退。
"""
from __future__ import annotations
import logging
from app.core.config import settings
from . import aliyun, chuanglan, jiguang
from .base import SendResult, SmsError
__all__ = ["SmsError", "SendResult", "send_code", "verify_code"]
logger = logging.getLogger("shagua.sms")
# provider 名 <-> 模块。未知/缺省主 provider 回退 jiguang(防误配把登录打挂)。
_ALL = {"jiguang": jiguang, "aliyun": aliyun, "chuanglan": chuanglan}
_NAME = {jiguang: "jiguang", aliyun: "aliyun", chuanglan: "chuanglan"}
def _primary():
"""当前主 provider 模块(未知/缺省回退 jiguang)。"""
return _ALL.get(settings.SMS_PROVIDER, jiguang)
def _fallback():
"""启用的备 provider 模块;未配置 / 同主 / 未知名 → None= 不启用 fallback)。"""
name = (settings.SMS_FALLBACK_PROVIDER or "").strip()
if not name or name == settings.SMS_PROVIDER:
return None
return _ALL.get(name)
def send_code(phone: str) -> SendResult:
"""发码:主成功即返回;仅主「供应商不可用(503)」且配置了备时转备补发。
429(本地冷却/超频)、400(手机号无效)不转——不绕过防刷、不为无效号白烧。
备也失败则抛备的 SmsError。返回 SendResultcooldown + 实际渠道 + 是否 fallback)。
"""
primary = _primary()
fb = _fallback()
try:
cooldown = primary.send_code(phone)
return SendResult(cooldown_sec=cooldown, provider=_NAME[primary], fallback=False)
except SmsError as e:
if fb is not None and e.status_code == 503:
logger.warning("[SMS] primary=%s 不可用(%s)fallback→%s",
_NAME[primary], e, _NAME[fb])
cooldown = fb.send_code(phone) # 备的冷却/错误码原样透出
return SendResult(cooldown_sec=cooldown, provider=_NAME[fb], fallback=True)
raise
def verify_code(phone: str, code: str) -> bool:
"""校验:try-both,遍历「启用的 fallback 链」(主→备),任一命中即 True。
码只存在实际发码那家(fallback 前主已 pop 掉自己的码),另一家 rec is None 即 False、
不误判、不累加其防爆破计数。关闭 fallback 时链中只有主,备完全不参与。
"""
chain = [_primary()]
fb = _fallback()
if fb is not None:
chain.append(fb)
for prov in chain:
if prov.verify_code(phone, code): # Mode B:纯本地内存比对,不联网
logger.info("[SMS] verify hit provider=%s", _NAME[prov])
return True
return False