topfans/docs/superpowers/specs/2026-05-22-sms-register-design.md

42 KiB
Raw Blame History

注册短信验证码功能设计文档

  • 创建日期: 2026-05-22
  • 状态: 待评审
  • 目标: 在用户注册流程中加入阿里云短信验证码验证

1. 背景与需求

当前注册流程:手机号 + 密码 → 设置昵称 → 完成注册

需求:在注册前增加短信验证码验证,确保手机号真实有效,防止恶意注册。


2. 交互流程设计

2.1 前端页面变化

修改 frontend/pages/register/register.vue

  1. 用户输入手机号后,点击"发送验证码"按钮
  2. 按钮变为倒计时状态60 秒),期间不可点击
  3. 用户输入收到的验证码,点击"验证验证码"按钮
  4. 验证通过后:解锁密码输入框,按钮变为"注册"
  5. 用户输入密码,点击"注册"按钮
  6. 页面跳转到 /pages/profile/setNickname,携带手机号+密码+verify_token

注册流程图:

register.vue                    setNickname.vue
    │                                  │
    ├── 输入手机号                      │
    ├── 点击"发送验证码" ──→ 发送验证码   │
    ├── 输入验证码                      │
    ├── 点击"验证验证码" ──→ 验证通过    │
    │        返回 verify_token          │
    ├── 输入密码                        │
    ├── 点击"注册" → 存储 temp_*        │
    │        + temp_register_verify_token │
    └── 跳转 setNickname ─────────────→ │
                                     ├── 读取 temp_* + verify_token
                                     ├── 输入昵称 + 选择 star_id
                                     └── 点击"下一步" ──→ register API
                                              (mobile, password, nickname, star_id, verify_token)

注意setNickname.vue 调用 store.dispatch('user/register', ...) 时需传入 verify_token 前端 store 需透传 verify_token 到 registerApi。

2.2 验证码状态

状态 描述
未发送 默认状态,输入框禁用
已发送(倒计时中) 输入框启用,倒计时按钮显示剩余秒数
已发送(可重发) 倒计时结束,按钮恢复可点击
验证成功 输入框显示绿色对勾,继续注册流程
验证失败 显示错误提示,用户可重新发送

2.3 后端流程

发送验证码流程:
用户输入手机号 → 发送验证码请求 → 后端生成6位随机码 → 存入RedisHash结构60秒TTL → 调用阿里云SMS API → 返回前端

验证验证码流程:
用户输入验证码 → 验证请求 → 检查Redis中验证码是否存在 → 比对code字段 → 失败则递增attempts≥3次则删除Key → 验证成功则删除验证码Key并创建verify:register:{mobile}记录300秒TTL → 返回verify_token

注册流程需携带verify_token
前端提交注册信息 → 后端检查verify:register:{mobile}是否存在且匹配 → 不匹配则拒绝 → 匹配则处理注册(创建用户、粉丝档案) → 删除verify:register记录 → 返回注册成功

3. 实现方案

方案一:在 userService 中直接集成 SMS SDK推荐

前端 → 网关 /auth/send-code → userService → 阿里云SMS
前端 → 网关 /auth/verify-code → userService → Redis验证 → 继续注册流程

优点:

  • 注册逻辑集中在一个服务,流程一目了然
  • userService 已有完整注册流程,加 SMS 只是顺带的事
  • Redis 已在 userService 中可用,不用跨服务调用
  • 部署简单,只增加一个 SDK 依赖

缺点:

  • userService 多了一个外部依赖(阿里云 SMS SDK升级 SDK 时需重新编译
  • 如果未来短信需求变复杂userService 会越来越臃肿

适用场景:当前阶段最合适,注册流程本来就在 userService不需要为了"未来的扩展性"增加复杂度。


方案二SMS 单独作为一个微服务

前端 → 网关 /auth/send-code → 短信Service → 阿里云SMS
前端 → 网关 /auth/verify-code → userService → Redis验证

优点:

  • 职责单一,短信服务可以独立迭代
  • 未来如果加邮件、推送通知,继续扩展这个服务即可
  • userService 保持干净,不感知第三方服务细节

缺点:

  • 需要额外部署一个服务,增加运维复杂度
  • 注册流程跨两个服务,一次注册调用两次 Dubbo延迟增加
  • 目前项目没有独立的通知服务,新增一个微服务成本偏高

适用场景:适合未来有多渠道通知需求的大规模系统。


方案三:在 gateway 层调用 SMS

前端 → 网关 /auth/send-code → 阿里云SMS网关直接调用
前端 → 网关 /auth/verify-code → userService → Redis验证

优点:

  • 网关已有阿里云 AccessKey 配置,可直接用于 SMS短信使用直接 AccessKey非 STS
  • 前端请求在网关层就能触发短信,不用穿透到后端服务
  • 网关是单点,处理短信很方便

缺点:

  • 短信发送成功后的后续注册流程还在 userService逻辑割裂
  • 网关承担了越来越多的职责路由、认证、OSS上传签名、现在又加短信越来越重
  • 网关当前 OSS 相关接口:
    • GET /oss/signature - 获取 OSS 上传签名
    • GET /oss/presigned-url - 获取 OSS 预签名URL读取用
    • GET /oss/batch-presigned-urls - 批量获取 OSS 预签名URL
  • SMS 和 OSS 认证方式不同SMS 用直接 AccessKeyOSS 用 STS Role ARN实际上并不能完全复用同一套配置

适用场景:适合短信作为独立操作、不属于用户注册主流程的场景。


4. 方案对比

方案一userService 方案二(独立微服务) 方案三gateway
部署复杂度
延迟
逻辑内聚性 (注册+短信在一起) 高(短信独立) 差(分散在两处)
可扩展性
维护成本

5. 技术选型

项目 选择
短信 SDK github.com/alibabacloud-go/dysmsapi-20180501/v2/client(阿里官方 V2 Go SDK
环境要求 Go 1.10.x 或更高
安装方式 go get github.com/alibabacloud-go/dysmsapi-20180501/v2/client
依赖包 还需 github.com/alibabacloud-go/darabonba-openapi/v2/client
认证方式 阿里云默认凭据链AccessKey 等)自动查找
API Endpoint dysmsapi.aliyuncs.com
验证码存储 Redis已部署过期自动失效
推荐方案 方案一userService

6. 配置项

backend/services/userService/config/ 下新增 SMS 配置(采用方案一,配置跟随服务):

// SMSConfig 短信配置
type SMSConfig struct {
    AccessKeyID     string
    AccessKeySecret string
    SignName        string        // 短信签名
    TemplateCode    string        // 短信模板CODE
    Region          string        // 区域(默认 cn-hangzhou
}

通过环境变量注入(与 gateway 的 OSS 配置分开,避免部署耦合):

# userService 环境变量
SMS_ACCESS_KEY_ID=
SMS_ACCESS_KEY_SECRET=
SMS_SIGN_NAME=TopFans
SMS_TEMPLATE_CODE=SMS_xxxxxxx
SMS_REGION=cn-hangzhou

7. Redis 存储设计

7.1 Key 结构(按场景区分)

场景 Key 格式 说明
注册 sms:register:{mobile} 注册时发送的验证码
登录 sms:login:{mobile} 登录时发送的验证码(未来扩展)
修改密码 sms:password:{mobile} 忘记密码时发送的验证码(未来扩展)

7.2 Value 结构Hash 类型,支持更多元数据)

Key: sms:register:13800138000
Value (Hash):
  code: "123456"           // 6位验证码
  created_at: "1700000000"  // 发送时间Unix timestamp
  attempts: "0"             // 验证失败次数超过3次需重新获取
  used: "false"             // 是否已使用
TTL: 60 秒

7.3 防暴力破解与限流策略

限制规则总览

限制维度 限制规则 TTL 触发动作
同一手机号(发送) 60 秒内不能重复发送 60s 返回 429
同一手机号(发送) 每小时最多 10 次 3600s 返回 429
同一手机号(验证) 最多失败 3 次 60s 删除验证码,要求重新获取
同一 IP发送 每小时最多 30 次 3600s 返回 429
同一 IP验证 每分钟最多 10 次 60s 返回 429
验证码有效期 60 秒 60s 过期自动失效

Redis Key 设计

# 验证码存储Hash
sms:register:{mobile}
  code: "123456"
  created_at: "1700000000"
  attempts: "0"
TTL: 60秒

# 手机号发送频率String每小时清一次
sms:limit:mobile:register:{mobile}
  value: "1" (计数器,每次发送 INCR)
TTL: 3600秒

# IP 发送频率String每小时清一次
sms:limit:ip:send:{ip}
  value: "1"
TTL: 3600秒

# IP 验证频率String每分钟清一次
sms:limit:ip:verify:{ip}
  value: "1"
TTL: 60秒

防御措施详情

1. 发送频率限制

  • 60 秒内同一手机号只能发送 1 次(防止轰炸)
  • 每小时同一手机号最多发送 10 次(结合套餐包成本控制)
  • 每小时同一 IP 最多发送 30 次(防止 IP 轮询攻击)

2. 验证失败限制

  • 验证码错误后递增 attempts 计数器
  • 失败 3 次后强制删除验证码,要求用户重新获取
  • 60 秒内同一 IP 最多发起 10 次验证请求

3. IP 黑名单机制

  • 触发限流时记录来源 IP
  • 1 小时内触发 3 次限流的 IP临时拉黑 30 分钟
  • 黑名单 Key: sms:blacklist:ip:{ip}TTL=1800 秒

4. 验证码安全

  • 验证码为 6 位纯数字,共 100 万种组合
  • 有效期 60 秒,暴力破解窗口极小
  • 验证成功后立即删除,防止重放

5. 异常检测

  • 同一手机号在 5 分钟内连续失败 5 次,触发告警(可考虑临时冻结)
  • 同一 IP 在 5 分钟内请求超过 50 次,触发告警

7.4 验证后处理

验证成功后:

  1. 删除验证码 Key防止重放DEL sms:register:{mobile}
  2. 创建验证通过记录:verify:register:{mobile} = {token}TTL = 300 秒
  3. 注册接口使用 verify_token 比对,一致则处理注册,注册成功后删除该记录

8. API 设计

8.1 发送验证码

请求

POST /api/v1/auth/send-code
Content-Type: application/json

{
    "mobile": "13800138000",
    "scene": "register"
}

scene 可选值:register(注册)、password(找回密码)。

响应

{
    "code": 200,
    "message": "发送成功",
    "data": {
        "expires_in": 60
    }
}

8.2 验证验证码

请求

POST /api/v1/auth/verify-code
Content-Type: application/json

{
    "mobile": "13800138000",
    "code": "123456",
    "scene": "register"
}

响应

{
    "code": 200,
    "message": "验证成功",
    "data": {
        "verified": true,
        "verify_token": "vtf_abc123xyz789...",
        "expires_in": 300
    }
}

说明

  • verify_token 是长度为 32 位的随机字符串(格式:vtf_ + 29 位随机字符),不是 JWT
  • 存储在 Redis 中:verify:register:{mobile}vtf_abc123xyz789...TTL = 300 秒
  • 注册接口需携带此 token验证通过后才处理注册请求
  • 验证成功后删除该记录,防止重复使用

注册接口携带 token

POST /api/v1/auth/register
Content-Type: application/json

{
    "mobile": "13800138000",
    "verify_token": "vtf_abc123xyz789...",
    "password": "xxx",
    "nickname": "xxx",
    "star_id": 1
}

后端逻辑:

  1. 根据 mobile 从 Redis 中获取 verify:register:{mobile} 的值
  2. 与请求中的 verify_token 比对,不一致则拒绝
  3. 验证通过后删除该记录,防止重复使用

9. 错误处理

场景 错误码 处理方式
手机号格式错误 400 提示"手机号格式不正确"
短信发送失败(阿里云超时) 500 提示"服务暂不可用,请稍后重试"
短信发送失败(配额不足) 500 提示"发送失败,请联系客服"
Redis 连接失败 500 提示"验证服务暂不可用"
验证码已过期 400 提示"验证码已过期,请重新获取"
验证码错误 400 提示"验证码错误剩余N次"
验证码错误超过3次 400 提示"验证失败次数过多,请重新获取"
验证码已使用 400 提示"验证码已使用,请重新获取"
发送频率超限60秒内重复发送 429 提示"发送过于频繁,请稍后再试"
每小时发送次数超限(>10次 429 提示"当前手机号发送次数超限,请稍后再试"
IP 发送频率超限(>30次/小时) 429 提示"请求过于频繁,请稍后再试"
IP 验证频率超限(>10次/分钟) 429 提示"请求过于频繁,请稍后再试"
IP 被临时拉黑 403 提示"暂时无法操作,请稍后再试"

10. 部署说明

  1. 在阿里云短信服务控制台创建签名和模板,获取 SignNameTemplateCode
  2. deploy/envs/user.env 中配置阿里云短信 AccessKey
# 阿里云短信配置(直接使用 AccessKey非 STS
SMS_ACCESS_KEY_ID=your_access_key_id
SMS_ACCESS_KEY_SECRET=your_access_key_secret
SMS_SIGN_NAME=TopFans
SMS_TEMPLATE_CODE=SMS_xxxxxxx
SMS_REGION=cn-hangzhou
  1. 重启 userService 服务

11. 安全注意事项

  1. 验证码日志脱敏:日志中禁止记录明文验证码,只能记录手机号和发送状态
  2. verify_token 安全token 有效期 5 分钟,只能使用一次,验证后立即删除
  3. 防暴力破解:验证失败 3 次后强制删除验证码,要求用户重新获取
  4. 限流保护:从手机号和 IP 两个维度限制请求频率
  5. IP 黑名单1 小时内触发 3 次限流的 IP临时拉黑 30 分钟
  6. 异常告警:同一手机号 5 分钟内连续失败 5 次,或同一 IP 5 分钟内请求超 50 次,触发告警

12. 阿里云短信服务详情

12.1 价格计费

阿里云短信采用套餐包计费方式,按条扣费。国内通用短信套餐包价格如下:

资源包规格 原价 官网折扣价 折合每条
200 条 ¥10.00/1年 ¥10.00/1年 0.050 元/条
1,500 条 ¥73.50/6个月 ¥73.50/6个月 0.049 元/条
5,000 条 ¥250.00/2年 ¥170.00/2年 0.034 元/条
15,000 条 ¥705.00/2年 ¥507.60/2年 0.034 元/条

💡 推荐5,000 条或 15,000 条套餐性价比最高,适合中等规模业务使用。

12.2 短信模板规范

模板类型

模板类型 说明 审核时间
验证码模板 仅含变量(如 ${code}),用于发送随机验证码 通常 2 小时内
通知模板 含固定文本 + 少量变量 通常 2 小时内
推广模板 营销类内容,审核更严格 通常 4 小时以上

内容规范

必须包含:

  • 必须包含"验证码、注册码、校验码、动态码"之一
  • 必须体现"使用平台、用途、失效时间"之一

禁止包含:

  • 不能含有通知、营销、广告词语、退订方式
  • 结尾不能包含"拒收请回复R"
  • 不能含任何联系方式:手机/固话/QQ/微信/抖音/钉钉/旺旺/闲鱼/小红书/邮箱等
  • 用途为"他用"的签名不支持申请金融相关模板

格式规范

项目 规范
模板长度 1500 个字符(含变量)
支持字符 中文、英文、数字、符号
不支持 繁体字、特殊符号(如 #『』、『「」、「」、〖〗 等)
禁止 错别字、变体字、异体字、各类干扰符号
模板内容中禁用 【】符号(任意位置),首尾禁用[ ]

变量规范

变量类型 变量名示例 变量规范
单变量(仅数字) ${code}${time} 长度限制 4~6 位
单变量(数字+字母) ${code} 长度限制 4~6 位
双变量 ${code}${time} 仅支持通过控制台"常用模板推荐"申请,不支持自定义

变量格式要求:

  • 变量格式为 ${变量名},其中 $ 符号在 { } 外面
  • 变量名首字母必须为英文字母
  • 变量名只能由字母、数字和下划线组成
  • 变量名不能为email、mobile、id、nick、site、ip 等

验证码模板示例

应用场景 模板内容
登录 您的验证码${code}该验证码5分钟内有效请勿泄露给他人
登录 验证码为:${code},您正在登录,若非本人操作,请勿泄露。
注册 您正在申请手机注册,验证码为:${code}5分钟内有效
注册 尊敬的用户,您的注册会员动态密码为:${code},请勿泄露于他人!
注册 您的注册码:${code},如非本人操作,请忽略本短信!
重置密码 您的动态码为:${code},您正在进行密码重置操作,如非本人操作,请忽略本短信!

审核时长

  • 预计 2 个小时 内审核完成
  • 工作时间:周一至周日 9:00~21:00法定节假日顺延

常见审核失败原因

类别 驳回原因 处理建议
内容模糊 模板没有体现业务内容,含义不明 使用实际业务内容
场景不详 模板使用场景不详 在场景说明中填写业务场景或线上链接
含退订方式 验证码模板包含退订方式 删除退订方式后重新提交
多变量 验证码模板包含多变量 去掉多余变量,仅支持单变量或双变量
关键词不全 缺少"验证码/注册码/校验码/动态码"之一 添加验证码关键词之一
变量格式错误 变量格式不符合规范 变量格式为 ${code},首字母必须为英文字母
模板类型错误 模板类型选择与实际需求不匹配 根据场景选择验证码/通知/推广模板
模板审核详细失败原因
类别 驳回原因 处理建议
内容模糊 模板没有体现业务内容,含义不明 使用实际业务内容进行测试
内容模糊 模板使用场景不详 填写业务场景或线上链接,提供测试账号密码
内容模糊 应用市场中未核实到App/公众号/小程序 填写正确链接,若产品未上线暂不支持申请
内容模糊 业务内容不明确(如"加微信送礼" 不支持加群内容,删除后重新提交
格式问题 推广短信模板包含变量 将变量内容体现在模板文案中
格式问题 推广短信没包含退订方式 加上 拒收请回复R
格式问题 验证码/通知模板包含退订方式 删除退订方式后重新提交
格式问题 验证码模板包含多变量 去掉多余变量,仅支持单变量或双变量
格式问题 验证码模板包含无关内容 删除与验证码无关的内容
格式问题 验证码关键词不全 必须含验证码/注册码/校验码/动态码之一
格式问题 通知模板包含推广内容 删除推广部分或申请推广模板
格式问题 含错别字/繁体字/特殊符号/中括号 修改为正确内容,【】符号不能在模板内容中使用
内容问题 内容涉及金融/交友/宗教/游戏 交友/游戏仅支持验证码模板;他用签名不支持金融相关
内容问题 内容涉及第三方 需提供第三方企业证件和授权委托书
内容问题 内容涉及营销信息 提供隐私协议截图、会员管理系统截图,备注投诉对接人
变量问题 变量内传入链接/IP地址/App等 变量外使用链接,固定链接用一级域名+变量格式
变量问题 变量内容模糊 修改表达以清晰判断参数内容
变量问题 变量格式错误 变量格式${code}首字母必须为英文字母不能为email/mobile/id/nick/site/ip
变量问题 变量属性选择错误 根据业务场景选择合适的变量属性
链接问题 链接无ICP备案 提供已ICP备案的网址链接
链接问题 短链+变量格式不符合规范 改为一级的域名或官网链接+变量组合
链接问题 链接存在跳转 将原链接压缩成短链
链接问题 链接无法访问 提供公网可访问的链接
SEO推广 涉及数据排名/关键字搜索/精准拓客引流 修改模板内容

12.3 签名规范

什么是短信签名

短信签名是短信发送方属性的一种标识,一条完整的短信由短信签名短信内容组成。短信签名位于短信内容前的【】中,用于标识企业、产品或业务。

示例【阿里云】您的验证码是123456 - 签名阿里云会自动补全【】

签名来源要求

签名来源 说明
企事业单位名(推荐) 企业名称的全称或简称,极大提升签名报备成功率
已注册商标名 需在国家知识产权局商标局可查且注册主体一致

⚠️ 不支持公众号、小程序、电商平台店铺名、已上线APP、测试/学习用途作为签名来源

签名内容规范

项目 规范
签名内容 需能明确辨别发送方公司信息、产品或业务
支持 企事业单位名、已注册商标名
不支持 含义模糊的中性签名(如"客服通知"、"客户您好"、"温馨提示"
不支持 个人姓名
不支持 含"测试"、"test"等字样的签名
不支持 全英文签名、全数字签名、英文+数字组合
不支持 特殊符号(含空格)、繁体字
格式 申请时直接填写签名名称,无需添加【】等符号,系统会自动添加

签名长度限制

  • 长度:2~12个字符
  • 中文、英文、数字按1个字符计算

签名用途

用途 说明
自用 资质对应的企业/个人信息与阿里云账号信息一致
他用 需上传委托授权书,第三方仅支持企业及事业单位,不得为个人

⚠️ 个人认证用户:自用签名无法通过签名实名制报备,请申请他用资质或升级为企业认证账号

签名可申请次数

账户类型 可申请次数
个人认证用户 同阿里云账号一个自然日内支持申请 1个 签名
企业认证用户 无数量限制

签名审核时长

  • 预计 2个小时内 审核完成
  • 工作时间:周一至周日 9:00~21:00法定节假日顺延
  • 运营商实名报备:5-10个工作日(可能更长)

签名状态异常原因

状态 异常原因
可用-异常 签名来源不合规、未关联资质、资质信息不全、实名制报备异常、长期未使用等
不可用 审批未通过、被禁用、被冻结
签名审核详细失败原因
类别 驳回原因 处理建议
内容模糊 App/业务内容较少,无法核实业务场景 完善线上业务信息后再提交,不支持未上线产品
内容模糊 签名过于宽泛,如"客服通知"、"客户您好" 申请与已上线应用名称或企事业单位名称作为签名
内容模糊 已提供信息,因无关联性被驳回 提供关联后台认证截图,备注关联性
内容模糊 未核实到商标信息 在场景说明中提供商标注册号及完整商标名
链接问题 链接无法访问 提供正确或公网可访问的链接
链接问题 链接与签名无关联 申请企业名称作为签名
链接问题 下载/内测链接无法核实业务主体 提供应用商城链接以便核实
链接问题 IP地址无法核实企业所属性 提供App或其他链接以便核实
授权书 授权书未签字 在授权书右下角落款处让法定代表人或负责人签字
授权书 授权书盖章错误 盖授权方(签名归属方)的企事业单位公章或合同章
授权书 授权书无日期/有效期短/过期 填写完整有效期限建议1~3年
授权书 授权书内容变更 委托授权书经法务评估后出具,不支持变更内容
授权书 被授权方/授权方/风险承担方填写错误 被授权方为账号认证主体名称,授权方和风险承担方为短信内容所属方
资质材料 涉及政企业务但材料未提供完全 需提供授权委托书和组织机构代码证,场景说明备注固话
资质材料 营业执照水印非平台使用 去除水印或改为"仅供云通信备案使用"
资质材料 证件无法查看 支持JPG、PNG、GIF、JPEG格式每张不大于2MB
格式问题 签名名称包含"测试"字样 删除测试字样后重新提交
格式问题 签名字数不符合要求 限制在2~12个字符内
格式问题 签名带符号/繁体字/首字母拼写 国内短信不支持全英文、全数字、繁体字、特殊符号签名
格式问题 签名是个人姓名 签名不能为个人姓名
格式问题 签名是小程序或公众号 不支持申请

签名申请受限内容

禁止包含:违法违规、色情、赌博、毒品、党政、博彩、彩票、暴力、恐吓、走私、成人用品、虚拟货币、烟草酒类、代开发票、代办证件、刷单、贷款催款、法律维权、股票私募、整容医美、宗教迷信、投资理财、房地产推广、游戏推广、交友推广、金融推广(含银行/保险/借贷)、招商加盟等内容的短信。

12.4 开通流程

  1. 登录阿里云控制台 → 产品 → 短信服务 SMS
  2. 完成企业认证(如尚未认证)
  3. 创建短信签名:控制台 → 短信签名管理 → 新增签名
  4. 创建短信模板:控制台 → 短信模板管理 → 新增模板
  5. 获取 AccessKeyRAM 控制台 → 访问密钥 → 创建 AccessKey
  6. 记录以下信息填入配置:
    • AccessKey ID
    • AccessKey Secret
    • 签名名称SignName
    • 模板 CODETemplateCode

12.5 Go SDK 使用说明

环境要求

  • Go 环境版本必须不低于 1.10.x

依赖安装

go get github.com/alibabacloud-go/dysmsapi-20180501/v2/client
go get github.com/alibabacloud-go/darabonba-openapi/v2/client
go get github.com/alibabacloud-go/tea/tea
go get github.com/alibabacloud-go/tea-utils/v2/service

设置访问凭据

阿里云 SDK 支持通过环境变量自动查找凭据,推荐使用 AK 方式:

Linux / macOS

export ALIBABA_CLOUD_ACCESS_KEY_ID=yourAccessKeyID
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=yourAccessKeySecret

Windows CMD

setx ALIBABA_CLOUD_ACCESS_KEY_ID yourAccessKeyID /M
setx ALIBABA_CLOUD_ACCESS_KEY_SECRET yourAccessKeySecret /M

Windows PowerShell

[System.Environment]::SetEnvironmentVariable('ALIBABA_CLOUD_ACCESS_KEY_ID', 'yourAccessKeyID', [System.EnvironmentVariableTarget]::Machine)
[System.Environment]::SetEnvironmentVariable('ALIBABA_CLOUD_ACCESS_KEY_SECRET', 'yourAccessKeySecret', [System.EnvironmentVariableTarget]::Machine)

初始化客户端(推荐单例模式)

import (
    "os"
    openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
    dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20180501/v2/client"
    "github.com/alibabacloud-go/tea/tea"
)

func CreateClient() (_result *dysmsapi20170525.Client, _err error) {
    config := &openapi.Config{
        AccessKeyId:     tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")),
        AccessKeySecret: tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")),
    }
    config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
    _result, _err = dysmsapi20170525.NewClient(config)
    return _result, _err
}

⚠️ 注意:客户端实例线程安全,建议采用单例模式,避免频繁创建。

发送短信(注册验证码场景)

使用 SendMessageWithTemplate API 发送中国内地短信:

import (
    dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20180501/v2/client"
    util "github.com/alibabacloud-go/tea-utils/v2/service"
    "github.com/alibabacloud-go/tea/tea"
)

func SendVerificationCode() (_result *dysmsapi20170525.SendMessageWithTemplateResponse, _err error) {
    client, _err := CreateClient()
    if _err != nil {
        return nil, _err
    }

    // 构造请求
    request := &dysmsapi20170525.SendMessageWithTemplateRequest{
        ToNumber:      tea.String("861503871XXXXX"),           // 接收手机号,格式:国际区号+号码
        FromNumber:    tea.String("TopFans"),                 // 发送方标识(中国内地填签名)
        TemplateCode:  tea.String("SMS_xxxxxxx"),            // 短信模板CODE
        TemplateParam: tea.String(`{"code":"123456"}`),      // 模板变量JSON格式
    }

    // 发送
    runtime := &util.RuntimeOptions{}
    response, _err := client.SendMessageWithTemplateWithOptions(request, runtime)
    if _err != nil {
        return nil, _err
    }
    return response, nil
}

请求参数说明

参数 类型 必填 说明
ToNumber string 接收手机号,格式:86 + 国内手机号,如 861503871XXXXX
FromNumber string 发送方标识,中国内地填短信签名
TemplateCode string 短信模板 CODE
TemplateParam string 模板变量 JSON{"code":"123456"}
SmsUpExtendCode string 上行短信扩展码

返回示例

{
    "MessageId": "10080303003****",
    "NumberDetail": {
        "Carrier": "China Mobile",
        "Country": "China",
        "Region": "Nanjing, Jiangsu"
    },
    "ResponseCode": "OK",
    "ResponseDescription": "The SMS Send Request was accepted",
    "Segments": "1",
    "To": "861503871XXXXX"
}

返回码说明

Code 说明
OK 请求成功,短信发送成功
isv.BUSINESS_LIMIT_CONTROL 触发频率限制,发送过于频繁
isv.DAY_LIMIT_CONTROL 当天发送量已达上限
isv.SMS_CONTENT_ILLEGAL 短信内容包含敏感词
isv.MOBILE_NUMBER_ILLEGAL 手机号格式错误
isv.SMS_SIGNATURE_ILLEGAL 签名格式错误或未审核通过
isv.TEMPLATE_MISSING_PARAMETERS 模板变量缺失
isv.BLACK_KEY_CONTROL_LIMIT 黑名单关键字

异常处理

V2.0 Go SDK 将异常分为两类:

  • error:非业务报错(如文件损坏、解析失败)
  • SDKError:业务报错(如频率超限、配额不足)
response, err := client.SendSmsWithOptions(sendSmsRequest, runtime)
if err != nil {
    // 打印错误诊断
    fmt.Println(tea.StringValue(err.Message))
    return err
}

// 获取请求ID
fmt.Println(tea.StringValue(response.Body.RequestId))
fmt.Println(tea.StringValue(response.Body.Code))

常见问题

错误提示 可能原因 解决方案
"You are not authorized to perform this operation" 无权限调用该 API 检查 RAM 权限策略
"Specified access key is not found" AccessKey ID 错误或已删除 确认环境变量是否正确设置
"dial tcp: lookup xxx: no such host" Endpoint 配置错误 确认 Endpoint 为 dysmsapi.aliyuncs.com

12.6 开通步骤与资质要求

重要提示

⚠️ 个人账号限制:在当前的短信签名实名制要求下,个人账号的自用资质无法通过签名实名制报备。个人用户请使用短信认证产品或升级为企业认证账号

开通步骤

步骤 操作 说明
① 准备工作 注册阿里云账号 + 完成实名认证 + 开通短信服务 + 创建 AccessKey API 调用必需
② 申请资质 提交资质(企业/个人证明)→ 等待审核(预计 2 个工作日) 国内短信必需
③ 申请签名 提交签名 → 等待审核(预计 2 小时)→ 等待运营商报备7-10 工作日) 签名实名制报备
④ 申请模板 提交模板 → 等待审核(预计 2 小时) 验证码模板
⑤ 发送短信 使用已审核通过的签名和模板发送 建议先少量测试
⑥ 查询详情 查询发送状态、获取回执 可选
⑦ 设置预警 配置联系人、验证码防盗刷、套餐包余量预警等 建议配置

资质要求

基本概念
类型 说明
个人认证 阿里云账号认证类型为个人。个人认证自用资质无法通过签名实名制报备,请申请"他用资质"或升级为企业认证账号
企业认证 阿里云账号认证类型为企业
自用资质 资质企业/个人信息与阿里云账号已认证信息完全一致
他用资质 资质企业/个人信息与阿里云账号已认证信息不一致,需提供委托授权书
资质材料清单
材料类型 具体材料 说明 适用对象
企业信息 加载统一社会信用代码的证照 社会信用代码证书、营业执照、事业单位法人证书等(选择一种) 企业认证(自用/他用)、个人认证(他用)
法定代表人信息 姓名 + 身份证件 若证件中无法定代表人信息,需提供负责人或首席代表的身份证件 企业认证(自用/他用)、个人认证(他用)
管理员信息 管理员姓名 + 身份证件 + 手机号 管理员是管理短信业务的运营人员,可与企业法定代表人为同一人。一人一企:同一管理员只能关联一个企业资质,否则报备失败 企业认证(自用/他用)、个人认证(他用)
个人信息 个人身份证明 个人认证自用资质无法通过签名实名制报备 个人认证(自用)
证件要求
  • 彩色原件无需盖章
  • 复印件/黑白照片需加盖企业红章并拍照上传
  • 证件标记建议修改为"仅供短信业务使用"或"仅供运营商报备使用"
资质审核时长
  • 预计 2个工作日 内完成
  • 工作时间:周一至周日 9:00~21:00法定节假日顺延
常见审核失败原因
类别 问题 建议
企业信息 企业经营状态异常 向市场监管部门移除经营异常名录后再提交
企业信息 证件标记非平台使用 修改为"仅供短信业务使用"
法定代表人 非中国国籍 护照、港澳居民来往内地通行证视为有效证件
管理员 非中国国籍或港澳居民 仅支持身份证,否则无法报备成功
资质审核详细失败原因
类别 问题 原因/处理建议
企业信息 企业经营状态异常 企业已被列入经营异常名单。建议向市场监管部门移除经营异常名录
企业信息 证件标记非平台使用 修改为"仅供短信业务使用"或"仅供运营商报备使用"
法定代表人 非中国国籍人士或港澳居民 护照、港澳居民来往内地通行证视为有效证件
法定代表人 工商个体户没有公章 可提供法定代表人签名
管理员 非中国国籍或港澳居民(无身份证) 仅支持身份证,否则无法报备成功
资质复用
  • 跨产品复用:企业账号同时使用语音服务、号码隐私保护等产品时,可复用已审核通过的资质
  • 短信服务内复用:可重复使用同一账号下已审核通过的资质

签名要求

  • 签名需要能明确辨别发送方
  • 建议使用企事业单位名称作为签名
  • 个人账号自用资质无法通过签名实名制报备
  • 未报备的签名会被运营商拦截发送,返回 PORT_NOT_REGISTERED 错误

运营商报备时长

  • 平均 5-7 个工作日
  • 部分运营商可能需要 7-10 个工作日
  • 运营商未承诺此时效,实际可能更长
  • 建议:提前申请资质和签名,预留足够时间完成实名报备后再正式使用

审核时间

审核项 审核时间 工作时间
资质审核 预计 2 个工作日 周一至周日 9:00~21:00法定节假日顺延
签名审核 预计 2 小时内 周一至周日 9:00~21:00
模板审核 预计 2 小时内 周一至周日 9:00~21:00

建议配置项

配置项 说明
联系人设置 设置预警联系人,接收通知
验证码防盗刷预警 建议开启,防止验证码被大量消耗
套餐包余量预警 余额不足时通知
发送频率预警 异常发送时通知

12.7 相关文档


12.8 短信使用记录表

为方便后续资源核算,需记录每次短信发送情况。

方案一PostgreSQL 表记录

CREATE TABLE sms_send_log (
    id BIGSERIAL PRIMARY KEY,
    mobile VARCHAR(20) NOT NULL COMMENT '手机号(脱敏存储)',
    scene VARCHAR(20) NOT NULL DEFAULT 'register' COMMENT '使用场景register/password',
    template_code VARCHAR(50) NOT NULL COMMENT '短信模板CODE',
    sign_name VARCHAR(50) NOT NULL COMMENT '短信签名',
    message_id VARCHAR(64) DEFAULT '' COMMENT '阿里云返回的MessageId',
    response_code VARCHAR(20) DEFAULT '' COMMENT '阿里云返回状态码',
    response_description VARCHAR(255) DEFAULT '' COMMENT '阿里云返回描述',
    status SMALLINT NOT NULL DEFAULT 1 COMMENT '发送状态0=失败1=成功',
    send_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_sms_send_log_mobile ON sms_send_log(mobile);
CREATE INDEX idx_sms_send_log_scene ON sms_send_log(scene);
CREATE INDEX idx_sms_send_log_send_time ON sms_send_log(send_time);

方案二Redis 记录(轻量级)

使用 Redis 哈希记录发送统计,按月汇总:

Key: sms:stat:{year}:{month}
Type: Hash
Fields:
  - total_count: 总发送条数
  - success_count: 成功条数
  - fail_count: 失败条数
  - register_count: 注册场景条数
  - password_count: 密码找回场景条数(未来扩展)

推荐方案一PostgreSQL,便于查询和导出做成本分析。

资源核算查询示例

-- 按月统计各场景短信发送量
SELECT
    TO_CHAR(send_time, 'YYYY-MM') AS month,
    scene,
    COUNT(*) AS total_count,
    SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS success_count,
    SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS fail_count
FROM sms_send_log
GROUP BY TO_CHAR(send_time, 'YYYY-MM'), scene
ORDER BY month DESC;

-- 统计剩余套餐条数(结合阿里云控制台数据)
-- 当前已消耗 = 注册验证成功 + 失败重试 等

12.9 忘记密码/找回密码

功能概述

用户可通过手机号验证码方式重置密码,与注册流程类似但更简洁:

  1. 用户输入手机号 → 发送验证码
  2. 输入验证码 → 验证通过
  3. 输入新密码 → 完成修改

前端页面变化

新建 frontend/pages/password/forget.vue(找回密码页面):

forget.vue
    │
    ├── 输入手机号
    ├── 点击"发送验证码" ──→ 发送验证码
    ├── 输入验证码
    ├── 点击"验证验证码" ──→ 验证通过
    │        返回 verify_token
    ├── 输入新密码
    └── 点击"确认修改" ──→ 修改密码 API
             (mobile, verify_token, new_password)

后端 API 变化

接口 方法 说明
/api/v1/auth/send-code POST 复用注册逻辑scene=password
/api/v1/auth/verify-code POST 复用注册逻辑scene=password
/api/v1/auth/reset-password POST 新接口,重置密码

重置密码接口:

POST /api/v1/auth/reset-password
Content-Type: application/json

{
    "mobile": "13800138000",
    "verify_token": "vtf_abc123xyz789...",
    "new_password": "xxx"
}

响应:

{
    "code": 200,
    "message": "密码修改成功",
    "data": null
}

后端逻辑:

  1. 根据 mobile 从 Redis 获取 verify:password:{mobile} 的值
  2. 与请求中的 verify_token 比对,不一致则拒绝
  3. 验证通过后删除该记录
  4. 使用新密码更新用户数据(需加密存储)
  5. 建议要求用户重新登录

Redis Key 复用

场景 Key 格式 说明
找回密码验证码 sms:password:{mobile} 验证码存储TTL 60秒
找回密码验证通过 verify:password:{mobile} 验证 token 存储TTL 300秒

限流规则与注册场景完全一致,共享 sms:limit:* 规则。

错误码

场景 错误码 提示
密码修改成功 200 "密码修改成功"
verify_token 无效/已过期 400 "验证码已失效,请重新获取"
验证码错误 400 "验证码错误"
同一手机号找回密码频率超限 429 "操作过于频繁,请稍后再试"

前端登录页入口

frontend/pages/login/login.vue 添加"忘记密码"入口:

<view class="forget-password" @click="goToForgetPassword">
    <text>忘记密码</text>
</view>

跳转到找回密码页面:

uni.navigateTo({
    url: '/pages/password/forget'
});

13. 待确定事项

  • 阿里云短信签名和模板CODE需在阿里云控制台创建
  • 验证码有效期(默认 60 秒是否合适)
  • 每小时同一手机号发送次数上限(默认 10 次)
  • 验证失败次数上限(默认 3 次后强制重新获取)
  • 找回密码页面是否需要单独创建,还是复用现有页面