7a2b7cb8ed
现有 sms.py(极光自管码)升级为 app/integrations/sms/ 包:
- base : SmsError + provider 无关的 mock_verify
- jiguang: 原极光自管码逻辑逐字迁入,行为零改动(git 识别为 sms.py 的 rename)
- aliyun : 新增阿里云 dypns 号码认证(Mode A:阿里云生成+下发+校验,核验免费)
- __init__: 按 SMS_PROVIDER 每次调用路由的分派器(默认 jiguang,可秒切回退)
关键决策:
- Mode A:发码 SendSmsVerifyCode(##code## 占位)、校验 CheckSmsVerifyCode(PASS/UNKNOWN);
本服务不再存码 → 消除极光路径「内存存码、多 worker 不共享」技术债。
- 防爆破与极光一致:aliyun 保留 per-phone 失败计数(SMS_MAX_VERIFY_ATTEMPTS),达上限本地作废,
避免两 provider 行为不同致排查困惑(此为 aliyun 路径唯一本地态)。
- 校验降级:阿里云接口异常 → verify_code 抛 SmsError(503),auth 两处 try/except 透出 503(非误报 400)。
- 官方 SDK alibabacloud_dypnsapi20170525;SDK 交互隔离在 _call_send/_call_check(惰性 import + 惰性建
client),单测 monkeypatch 不触真网络。
测试:test_sms_aliyun(17)+ test_sms_dispatch(3)全绿;test_auth 内部访问 retarget 到 jiguang.*。
配置:SMS_PROVIDER + ALIYUN_SMS_*(见 .env.example);文档 docs/integrations/sms.md + aliyun 接口参考。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
34 lines
1.4 KiB
Python
34 lines
1.4 KiB
Python
"""短信验证码服务 —— provider 分派入口。
|
|
|
|
对外只暴露 `send_code` / `verify_code` / `SmsError`,api 层无需关心用哪个 provider。
|
|
provider 由 `settings.SMS_PROVIDER` 选择(**每次调用读取**,支持运行时切换 + 灰度回退):
|
|
- `jiguang`(默认):自管码(本服务生成、内存存/校验,极光只发)。见 [jiguang.py](jiguang.py)。
|
|
- `aliyun`:阿里云号码认证(阿里云生成+下发+校验,Mode A)。见 [aliyun.py](aliyun.py)。
|
|
|
|
mock(`SMS_MOCK=true`)与各 provider 的行为差异都封在 provider 内部;本层只做路由。
|
|
拆包前本模块是单文件 `sms.py`;拆包后极光逻辑迁入 `jiguang` 子模块,行为零改动。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from app.core.config import settings
|
|
|
|
from . import aliyun, jiguang
|
|
from .base import SmsError
|
|
|
|
__all__ = ["SmsError", "send_code", "verify_code"]
|
|
|
|
|
|
def _provider():
|
|
"""按配置选 provider 模块(每次调用读 settings,支持运行时切换 / 测试注入)。"""
|
|
return aliyun if settings.SMS_PROVIDER == "aliyun" else jiguang
|
|
|
|
|
|
def send_code(phone: str) -> int:
|
|
"""发送验证码,返回距下次可发的冷却秒数;失败抛 SmsError。委托给当前 provider。"""
|
|
return _provider().send_code(phone)
|
|
|
|
|
|
def verify_code(phone: str, code: str) -> bool:
|
|
"""校验验证码,返回是否通过;provider 异常降级抛 SmsError。委托给当前 provider。"""
|
|
return _provider().verify_code(phone, code)
|