42 KiB
注册短信验证码功能设计文档
- 创建日期: 2026-05-22
- 状态: 待评审
- 目标: 在用户注册流程中加入阿里云短信验证码验证
1. 背景与需求
当前注册流程:手机号 + 密码 → 设置昵称 → 完成注册
需求:在注册前增加短信验证码验证,确保手机号真实有效,防止恶意注册。
2. 交互流程设计
2.1 前端页面变化
修改 frontend/pages/register/register.vue:
- 用户输入手机号后,点击"发送验证码"按钮
- 按钮变为倒计时状态(60 秒),期间不可点击
- 用户输入收到的验证码,点击"验证验证码"按钮
- 验证通过后:解锁密码输入框,按钮变为"注册"
- 用户输入密码,点击"注册"按钮
- 页面跳转到
/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位随机码 → 存入Redis(Hash结构,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 用直接 AccessKey,OSS 用 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 验证后处理
验证成功后:
- 删除验证码 Key(防止重放):
DEL sms:register:{mobile} - 创建验证通过记录:
verify:register:{mobile}={token},TTL = 300 秒 - 注册接口使用 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
}
后端逻辑:
- 根据 mobile 从 Redis 中获取
verify:register:{mobile}的值 - 与请求中的 verify_token 比对,不一致则拒绝
- 验证通过后删除该记录,防止重复使用
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. 部署说明
- 在阿里云短信服务控制台创建签名和模板,获取
SignName和TemplateCode - 在
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
- 重启 userService 服务
11. 安全注意事项
- 验证码日志脱敏:日志中禁止记录明文验证码,只能记录手机号和发送状态
- verify_token 安全:token 有效期 5 分钟,只能使用一次,验证后立即删除
- 防暴力破解:验证失败 3 次后强制删除验证码,要求用户重新获取
- 限流保护:从手机号和 IP 两个维度限制请求频率
- IP 黑名单:1 小时内触发 3 次限流的 IP,临时拉黑 30 分钟
- 异常告警:同一手机号 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/微信/抖音/钉钉/旺旺/闲鱼/小红书/邮箱等
- 用途为"他用"的签名不支持申请金融相关模板
格式规范
| 项目 | 规范 |
|---|---|
| 模板长度 | 1~500 个字符(含变量) |
| 支持字符 | 中文、英文、数字、符号 |
| 不支持 | 繁体字、特殊符号(如 #、『』、『「」、「」、〖〗、m²、•、①、★、※、→ 等) |
| 禁止 | 错别字、变体字、异体字、各类干扰符号 |
| 模板内容中禁用 | 【】符号(任意位置),首尾禁用[ ] |
变量规范
| 变量类型 | 变量名示例 | 变量规范 |
|---|---|---|
| 单变量(仅数字) | ${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 开通流程
- 登录阿里云控制台 → 产品 → 短信服务 SMS
- 完成企业认证(如尚未认证)
- 创建短信签名:控制台 → 短信签名管理 → 新增签名
- 创建短信模板:控制台 → 短信模板管理 → 新增模板
- 获取 AccessKey:RAM 控制台 → 访问密钥 → 创建 AccessKey
- 记录以下信息填入配置:
- AccessKey ID
- AccessKey Secret
- 签名名称(SignName)
- 模板 CODE(TemplateCode)
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 忘记密码/找回密码
功能概述
用户可通过手机号验证码方式重置密码,与注册流程类似但更简洁:
- 用户输入手机号 → 发送验证码
- 输入验证码 → 验证通过
- 输入新密码 → 完成修改
前端页面变化
新建 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
}
后端逻辑:
- 根据 mobile 从 Redis 获取
verify:password:{mobile}的值 - 与请求中的 verify_token 比对,不一致则拒绝
- 验证通过后删除该记录
- 使用新密码更新用户数据(需加密存储)
- 建议要求用户重新登录
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 次后强制重新获取)
- 找回密码页面是否需要单独创建,还是复用现有页面