feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动 #188
Reference in New Issue
Block a user
Delete Branch "feat/aliyun-sms-verify"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
读完了两份 spec + 分派器,这分支的全貌清晰了。下面是可直接粘进 Gitea PR 的 MR 说明(标题 + 正文):
建议标题
feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动
MR 正文(复制以下 markdown):
概述
把原单文件
app/integrations/sms.py重构为可切换 provider 包app/integrations/sms/,在保留极光(默认、行为零改动)的基础上,新增两家验证码短信 provider:Provider 由
SMS_PROVIDER按调用实时选择,默认jiguang。短信=花钱 + 登录关键路径,故新 provider opt-in、可灰度、秒级回退,默认路径零变更。为什么
现有极光路径本地内存存码(多 worker 不共享,已是技术债),且单一供应商无法灰度/切换。引入 provider 抽象后:阿里云托管码可消除存码债,创蓝作为备选降低单点依赖,三家随配置切换与回退。
改动内容
架构(
app/integrations/sms/)__init__.pysend_code/verify_code/SmsError(auth 导入不变);按SMS_PROVIDER每次调用分派;未知值回退jiguangbase.pySmsError(status_code→HTTP) + provider 无关的mock_verifyjiguang.pysms.py逻辑原样迁入,行为零改动(git 识别为 rename)aliyun.pySendSmsVerifyCode+CheckSmsVerifyCode,惰性加载 SDKchuanglan.pytpl/send+ HMAC 签名两种验证码模式
##code##托管生成+校验;本地仅留 per-phone 失败计数防爆破。secrets生成 N 位 → 进程内存 → 供应商只下发;本地一次性校验 + 失败 N 次作废。创蓝复制极光存码机器(不重构极光,零回归风险)。配置(
config.py+.env.example)SMS_PROVIDER = jiguang | aliyun | chuanglan(默认 jiguang)ALIYUN_SMS_*(AK/签名/模板/方案名/时长…) +aliyun_sms_configured门控CHUANGLAN_SMS_*(账号/密码/模板/签名/endpoint…) +chuanglan_sms_configured门控SMS_MOCK / SMS_CODE_LENGTH / SMS_CODE_TTL_SEC / SMS_SEND_INTERVAL_SEC / SMS_MAX_VERIFY_ATTEMPTSsend_code抛SmsError(503),不静默auth.py(最小改动)
verify_code现在可能抛SmsError(阿里云降级 503)→sms_login、wechat_bind_phone_sms两处各包try/except SmsError → HTTPException,与send_code现有写法一致。依赖
pyproject.toml增alibabacloud_dypnsapi20170525(仅阿里云 provider 惰性 import;jiguang/chuanglan 不加载)。创蓝零新依赖(httpx + 标准库)。测试
test_sms_aliyun.py/test_sms_chuanglan.py(均 monkeypatch 网络接缝,不发真短信) +test_sms_dispatch.py(分派/回退)。test_auth.py相应更新。SMS_MOCK=true在分派层短路,不受影响。文档
docs/superpowers/specs/2026-07-25-aliyun-sms-verify-design.md、2026-07-26-chuanglan-sms-verify-design.mddocs/integrations/aliyun/*、docs/integrations/chuanglan/tpl-send.md、docs/integrations/sms.md兼容性 & 回退
SMS_PROVIDER=jiguang,线上行为与现状完全一致;不改极光逻辑、不动 API 层频控与测试账号短路。SMS_PROVIDER一律回退极光,防误配打挂登录。现有 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>冲突仅 app/api/v1/auth.py::sms_login,合并两侧改动: - 保留(本分支)阿里云 provider 的 verify_code try/except —— 校验降级抛 SmsError 时透出其状态码(503),不误报 400。 - 保留(main)风控失败事件 record_behavior_event —— 但仅在「验证码错误」(ok 为 False)时记; provider 降级 503 已在 try/except 提前 raise,不计入「验证失败」风控。 wechat_bind_phone_sms 的 try/except 自动合并、无冲突(main 未在该处加风控)。 验证:全量 pytest 552 passed / 8 failed(8 个均为合并前已存在、与本次无关)。🤖 review-pr 深审结论
#188「feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝)」 🟢 可合并(置信度 0.9)
隔离 worktree 完整仓深审 + 实跑验证:63 条 SMS/auth 测试全过,且合并最新 main(领先 12 提交,含刚合入的 applog)后仍全过、无冲突;创蓝 HMAC 签名与接口文档逐条一致;provider 未配置时优雅降级 503(非启动崩)。未发现 high/med 级问题。
实跑证据
pytest test_sms_aliyun/chuanglan/dispatch/auth→ 63 passed(PR head)origin/main(领先 12 提交)→ 无冲突,合并树上 63 passed(挡住「各分支自洽、合并后炸」)_sign:md5(pwd)作 key、sorted([md5pwd,ts,nonce])无分隔拼接去空白、HmacSHA256 小写 hex、头X-QA-Hmac-Signature—— 与docs/integrations/chuanglan/tpl-send.md逐条一致aliyun_sms_configured/chuanglan_sms_configured均为@property(未配置 → 503 生效)app/integrations/sms/*.py+auth.pyruff 全清写得好
sms.py→sms/jiguang.py行为保持迁移(91% 相似),SmsError上提base.py,包入口只做路由并默认兜底 jiguang(防误配打挂登录)secrets.choice生成、secrets.compare_digest常量时比对、一次性作废、过期、失败上限、发送失败保留冷却(挡重试风暴)SmsError→原状态码映射:provider 降级返 503 而非误报「验证码错误」400,且不记风控失败事件(供应商故障不算用户失败)—— 关键正确点观察点(均非阻塞)
--workers 1+ API 层设备/IP 频控兜底;aliyun 托管码天然规避)。两处 Mode B 逻辑刻意隔离复制,后续改动需同步 jiguang 与 chuanglanSMS_PROVIDER=aliyun前需在服务器装新依赖alibabacloud_dypnsapi20170525(uv sync);未装则该 provider import 失败 → 503(优雅但功能不可用)。默认 jiguang 不受影响app/core/config.py有 2 处 ruff 告警(Field未用 F401、"Settings"注解带引号 UP037),在 main 上已存在,可顺手ruff --fix🤖 由 review-pr 技能生成(只读隔离深审,worktree 已清理);结论供参考,合并前请人工复核。