Files
shaguabijia-app-server/docs/integrations/aliyun/SendSmsVerifyCode.md
T
guke ed76820e97 feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动 (#188)
读完了两份 spec + 分派器,这分支的全貌清晰了。下面是可直接粘进 Gitea PR 的 MR 说明(标题 + 正文):

建议标题

feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动
MR 正文(复制以下 markdown):

## 概述
把原单文件 `app/integrations/sms.py` 重构为**可切换 provider 包** `app/integrations/sms/`,在保留极光(默认、行为零改动)的基础上,新增两家验证码短信 provider:
- **阿里云号码认证 dypns**(Mode A:阿里云生成/存储/校验验证码,核验免费)
- **创蓝云智 253**(Mode B:本服务自管码,httpx 直连 + HMAC 签名)

Provider 由 `SMS_PROVIDER` 按调用实时选择,默认 `jiguang`。短信=花钱 + 登录关键路径,故新 provider **opt-in、可灰度、秒级回退**,默认路径零变更。

## 为什么
现有极光路径本地内存存码(多 worker 不共享,已是技术债),且单一供应商无法灰度/切换。引入 provider 抽象后:阿里云托管码可消除存码债,创蓝作为备选降低单点依赖,三家随配置切换与回退。

## 改动内容

**架构(`app/integrations/sms/`)**
| 文件 | 说明 |
|---|---|
| `__init__.py` | 对外仍暴露 `send_code/verify_code/SmsError`(auth 导入不变);按 `SMS_PROVIDER` **每次调用**分派;未知值回退 `jiguang` |
| `base.py` | `SmsError`(status_code→HTTP) + provider 无关的 `mock_verify` |
| `jiguang.py` | 原 `sms.py` 逻辑**原样迁入**,行为零改动(git 识别为 rename) |
| `aliyun.py` | 新增,Mode A:`SendSmsVerifyCode` + `CheckSmsVerifyCode`,惰性加载 SDK |
| `chuanglan.py` | 新增,Mode B:自管码 + `tpl/send` + HMAC 签名 |

**两种验证码模式**
- Mode A(阿里云):不本地存码,阿里云 `##code##` 托管生成+校验;本地仅留 per-phone 失败计数防爆破。
- Mode B(极光/创蓝):`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_ATTEMPTS`
- 切到某 provider 却未配齐 → `send_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` 在分派层短路,不受影响。

**文档**
- 设计 spec:`docs/superpowers/specs/2026-07-25-aliyun-sms-verify-design.md`、`2026-07-26-chuanglan-sms-verify-design.md`
- 接口调研:`docs/integrations/aliyun/*`、`docs/integrations/chuanglan/tpl-send.md`、`docs/integrations/sms.md`

## 兼容性 & 回退
- **默认 `SMS_PROVIDER=jiguang`,线上行为与现状完全一致**;不改极光逻辑、不动 API 层频控与测试账号短路。
- 切阿里云/创蓝仅改环境变量,出问题秒切回极光;未知 `SMS_PROVIDER` 一律回退极光,防误配打挂登录。

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #188
2026-07-28 09:20:57 +08:00

397 lines
7.1 KiB
Markdown

SendSmsVerifyCode - 发送短信验证码
更新时间:2026年7月3日 09:54:53
发送短信验证码。
接口说明
由于运营商近期加强对短信签名的管控。您自定义的签名面临下发失败问题,推荐您使用号码认证控制台赠送的短信签名和模板进行短信认证。系统赠送签名必须搭配系统赠送模板使用。
请确保在使用该接口前,已充分了解号码认证服务产品的收费方式和价格,短信认证服务仅收取短信发送费用(按运营商回执状态计费,短信提交成功但运营商回执失败时不计费),核验服务免费。
调试
您可以在OpenAPI Explorer中直接运行该接口,免去您计算签名的困扰。运行成功后,OpenAPI Explorer可以自动生成SDK代码示例。
调试
授权信息
下表是API对应的授权信息,可以在RAM权限策略语句的Action元素中使用,用来给RAM用户或RAM角色授予调用此API的权限。具体说明如下:
操作:是指具体的权限点。
访问级别:是指每个操作的访问级别,取值为写入(Write)、读取(Read)或列出(List)。
资源类型:是指操作中支持授权的资源类型。具体说明如下:
对于必选的资源类型,用前面加 * 表示。
对于不支持资源级授权的操作,用全部资源表示。
条件关键字:是指云产品自身定义的条件关键字。
关联操作:是指成功执行操作所需要的其他权限。操作者必须同时具备关联操作的权限,操作才能成功。
放大查看
操作
访问级别
资源类型
条件关键字
关联操作
dypns:SendSmsVerifyCode
create
*全部资源
*
无 无
请求参数
放大查看
名称
类型
必填
描述
示例值
SchemeName
string
方案名称,如果不填则为“默认方案”。最多不超过 20 个字符。
测试方案
CountryCode
string
号码国家编码。默认为 86,目前也仅支持国内号码发送。
86
PhoneNumber
string
短信接收方手机号。
130****0000
SignName
string
签名名称。暂不支持使用自定义签名,请使用系统赠送的签名,您可在赠送签名配置页面选择需要下发的签名。
恒创联众
TemplateCode
string
短信模板 CODE。参数SignName选择赠送签名时,必须搭配赠送模板下发短信。您可在赠送模板配置页面选择适用您业务场景的模板。
100001
TemplateParam
string
短信模板参数。验证码位置有两种传值方式:
可使用"##code##"替代,由参数 CodeType 指定验证码生成规则;
也可直接传入具体的验证码值,直接下发至接收方。
示例:如模板内容为:“您的验证码是${code},有效期${min}分钟,请勿告诉他人。”。
重要 上文中的 code 请替换成您实际申请的验证码模板中的参数名称
该字段可传入{"code":"##code##","min":"5"}由系统根据规则生成验证码;
或直接传入指定的验证码值{"code":"123456","min":"5"}。
说明
{"code":"##code##","min":"5"}验证码是 api 动态生成的,阿里云接口可以完成校验。
{"code":"123456","min":"5"}验证码是用户配置的不是 api 动态生成,阿里云接口无法校验。
说明
如果 JSON 中需要带换行符,请参照标准的 JSON 协议处理。
模板变量规范,请参见短信模板规范。
{"code":"##code##","min":"5"}
SmsUpExtendCode
string
上行短信扩展码。上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。
说明
扩展码是生成签名时系统自动默认生成的,不支持自行传入。无特殊需要此字段的用户请忽略此字段。如需使用,请联系您的商务经理。
1213123
OutId
string
外部流水号。
外部流水号(透传)
CodeLength
integer
验证码长度支持 4~8 位长度,默认是 4 位。
4
ValidTime
integer
验证码有效时长,单位秒,默认为 300 秒。
300
DuplicatePolicy
integer
核验规则,当有效时间内对同场景内的同号码重复发送验证码时,旧验证码如何处理。
1:覆盖处理(默认),即旧验证码会失效掉。
2:保留,即多个验证码都是在有效期内都可以校验通过。
枚举值:
1 :
覆盖
2 :
保留
1
Interval
integer
时间间隔,单位:秒。即多久间隔可以发送一次验证码,用于频控,默认 60 秒。
60
CodeType
integer
生成的验证码类型。当参数 TemplateParam 传入占位符时,此参数必填,将由系统根据指定的规则生成验证码。取值:
1:纯数字(默认)。
2:纯大写字母。
3:纯小写字母。
4:大小字母混合。
5:数字+大写字母混合。
6:数字+小写字母混合。
7:数字+大小写字母混合。
枚举值:
1 :
纯数字
2 :
纯大写字母
3 :
纯小写字母
4 :
大小字母混合
5 :
数字+大写字母混合
6 :
数字+小写字母混合
7 :
数字+大小写字母混合
1
ReturnVerifyCode
boolean
是否返回验证码。取值:
true:返回。
false:不返回。
true
AutoRetry
integer
是否自动替换签名重试(默认开启),可取值:
1 开启自动重试功能,开启后,在验证码有效期内,当运营商返回明确的失败状态时,允许阿里云尽可能的尝试使用其他方式发送验证码,以提升发送成功率。其他方式包括且不限于:通过其他运营商重试、更换签名重试等
0 不开启自动重试
是否自动重试
返回参数
放大查看
名称
类型
描述
示例值
object
AccessDeniedDetail
string
访问被拒绝详细信息。
Message
string
状态码的描述。
成功
RequestId
string
请求 ID。
CC3BB6D2-2FDF-4321-9DCE-B38165CE4C47
Model
object
请求结果数据。
VerifyCode
string
验证码。
4232
RequestId
string
请求 ID。
a3671ccf-0102-4c8e-8797-a3678e091d09
OutId
string
外部流水号。
1231231313
BizId
string
业务 ID。
112231421412414124123^4
Code
string
请求状态码。返回 OK 代表请求成功。其他错误码,请参见返回码列表。
OK
Success
boolean
请求是否成功。
true:请求成功。
false:请求失败。
true
示例
正常返回示例
JSON格式
放大查看复制代码
{
"AccessDeniedDetail": "无",
"Message": "成功 ",
"RequestId": "CC3BB6D2-2FDF-4321-9DCE-B38165CE4C47",
"Model": {
"VerifyCode": "4232",
"RequestId": "a3671ccf-0102-4c8e-8797-a3678e091d09",
"OutId": "1231231313",
"BizId": "112231421412414124123^4"
},
"Code": "OK",
"Success": true
}
错误码
放大查看
HTTP status code
错误码
错误信息
描述
400 MOBILE_NUMBER_ILLEGAL The mobile number is illegal. 手机号码格式错误
400 BUSINESS_LIMIT_CONTROL The number has exceeded the limit for the day. 触发号码天级流控
400 FREQUENCY_FAIL Check frequency fail. 频控校验未通过
400 INVALID_PARAMETERS parameter is not valid. 非法参数
400 FUNCTION_NOT_OPENED You have not opened this function. 没有开通融合认证功能