新增第12章:价格计费、模板类型、签名、开通流程、相关文档链接。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
411 lines
14 KiB
Markdown
411 lines
14 KiB
Markdown
# 注册短信验证码功能设计文档
|
||
|
||
- **创建日期**: 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位随机码 → 存入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验证
|
||
```
|
||
|
||
**优点:**
|
||
- 阿里云 credentials 配置原本就在 gateway(OSS 用的是同一套),直接复用
|
||
- 前端请求在网关层就能触发短信,不用穿透到后端服务
|
||
- 网关是单点,处理短信很方便
|
||
|
||
**缺点:**
|
||
- 短信发送成功后的后续注册流程还在 userService,逻辑割裂
|
||
- 网关承担了越来越多的职责(路由、认证、OSS上传、现在又加短信),越来越重
|
||
- 如果 SMS 认证配置要从 gateway 复用,需要确保 userService 能访问到同一份配置,增加了部署耦合
|
||
|
||
**适用场景**:适合短信作为独立操作、不属于用户注册主流程的场景。
|
||
|
||
---
|
||
|
||
## 4. 方案对比
|
||
|
||
| | 方案一(userService) | 方案二(独立微服务) | 方案三(gateway) |
|
||
|--|--|--|--|
|
||
| 部署复杂度 | 低 | 高 | 低 |
|
||
| 延迟 | 低 | 中 | 低 |
|
||
| 逻辑内聚性 | **高**(注册+短信在一起) | 高(短信独立) | 差(分散在两处) |
|
||
| 可扩展性 | 中 | **高** | 低 |
|
||
| 维护成本 | 低 | 中 | 低 |
|
||
|
||
---
|
||
|
||
## 5. 技术选型
|
||
|
||
| 项目 | 选择 |
|
||
|------|------|
|
||
| 短信 SDK | `github.com/aliyundysms/dysmsapi-go-sdk`(阿里官方 Go SDK,需确认最新包名) |
|
||
| 认证方式 | AccessKeyID/Secret(短信服务使用直接 AccessKey,非 OSS 的 STS 方式) |
|
||
| 验证码存储 | Redis(已部署,过期自动失效) |
|
||
| 推荐方案 | **方案一**(userService) |
|
||
|
||
---
|
||
|
||
## 6. 配置项
|
||
|
||
在 `backend/services/userService/config/` 下新增 SMS 配置(采用方案一,配置跟随服务):
|
||
|
||
```go
|
||
// SMSConfig 短信配置
|
||
type SMSConfig struct {
|
||
AccessKeyID string
|
||
AccessKeySecret string
|
||
SignName string // 短信签名
|
||
TemplateCode string // 短信模板CODE
|
||
Region string // 区域(默认 cn-hangzhou)
|
||
}
|
||
```
|
||
|
||
通过环境变量注入(与 gateway 的 OSS 配置分开,避免部署耦合):
|
||
|
||
```bash
|
||
# 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 防暴力破解策略
|
||
|
||
- **失败计数**:每次验证失败递增 attempts 字段,≥ 3 次后删除 Key,要求用户重新获取
|
||
- **发送频率限制**:每小时最多发送 10 次,超限返回 429
|
||
- 使用独立 Key `sms:limit:register:{mobile}` 计数(String 类型,TTL=3600 秒)
|
||
- **IP 维度限流**(可选):每 IP 每小时最多请求 30 次
|
||
- 使用独立 Key `sms:limit:ip:{ip}` 计数(String 类型,TTL=3600 秒)
|
||
|
||
### 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"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "发送成功",
|
||
"data": {
|
||
"expires_in": 60
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 验证验证码
|
||
|
||
**请求**
|
||
```
|
||
POST /api/v1/auth/verify-code
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"mobile": "13800138000",
|
||
"code": "123456"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
```json
|
||
{
|
||
"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 请求频率超限 | 429 | 提示"请求过于频繁,请稍后再试" |
|
||
|
||
---
|
||
|
||
## 10. 部署说明
|
||
|
||
1. 在阿里云短信服务控制台创建签名和模板,获取 `SignName` 和 `TemplateCode`
|
||
2. 在 `deploy/envs/user.env` 中配置阿里云短信 AccessKey:
|
||
|
||
```bash
|
||
# 阿里云短信配置(直接使用 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
|
||
```
|
||
|
||
3. 重启 userService 服务
|
||
|
||
---
|
||
|
||
## 11. 安全注意事项
|
||
|
||
1. **验证码日志脱敏**:日志中禁止记录明文验证码,只能记录手机号和发送状态
|
||
2. **verify_token 安全**:token 有效期 5 分钟,只能使用一次,验证后立即删除
|
||
3. **防暴力破解**:验证失败 3 次后强制删除验证码,要求用户重新获取
|
||
4. **限流保护**:从手机号和 IP 两个维度限制请求频率
|
||
|
||
---
|
||
|
||
## 12. 阿里云短信服务详情
|
||
|
||
### 12.1 价格计费
|
||
|
||
| 计费项 | 说明 |
|
||
|--------|------|
|
||
| 国内短信 | 0.045 元/条(正常模板) |
|
||
| 国际/港澳台短信 | 按区域定价,详见 [阿里云短信定价页](https://www.aliyun.com/product/sms) |
|
||
|
||
> ⚠️ **待确认**:请在实际使用前登录阿里云控制台确认最新价格,本文档价格仅供参考。
|
||
|
||
### 12.2 短信模板类型
|
||
|
||
| 模板类型 | 说明 | 审核时间 |
|
||
|----------|------|----------|
|
||
| 验证码模板 | 仅含变量(如 ${code}),用于发送随机验证码 | 通常 2 小时内 |
|
||
| 通知模板 | 含固定文本 + 少量变量 | 通常 2 小时内 |
|
||
| 推广模板 | 营销类内容,审核更严格 | 通常 4 小时以上 |
|
||
|
||
**验证码模板示例:**
|
||
```
|
||
您的注册验证码是${code},5分钟内有效。如非本人操作,请忽略此短信。
|
||
```
|
||
|
||
### 12.3 签名
|
||
|
||
- 签名是短信发送者的标识,位于短信内容开头
|
||
- 一个账号可创建多个签名,需审核通过后才能使用
|
||
- 签名名称示例:`TopFans`、`TOPFANS官方`
|
||
|
||
### 12.4 开通流程
|
||
|
||
1. 登录阿里云控制台 → 产品 → 短信服务 SMS
|
||
2. 完成企业认证(如尚未认证)
|
||
3. 创建短信签名:控制台 → 短信签名管理 → 新增签名
|
||
4. 创建短信模板:控制台 → 短信模板管理 → 新增模板
|
||
5. 获取 AccessKey:RAM 控制台 → 访问密钥 → 创建 AccessKey
|
||
6. 记录以下信息填入配置:
|
||
- AccessKey ID
|
||
- AccessKey Secret
|
||
- 签名名称(SignName)
|
||
- 模板 CODE(TemplateCode)
|
||
|
||
### 12.5 相关文档
|
||
|
||
- [阿里云短信服务帮助文档](https://help.aliyun.com/zh/sms)
|
||
- [短信 API 调用指南](https://help.aliyun.com/zh/sms/developer-reference/sendSms)
|
||
- [Go SDK 文档](https://help.aliyun.com/zh/sms/developer-reference/aliyun-java-sdk-sms)
|
||
|
||
---
|
||
|
||
## 13. 待确定事项
|
||
|
||
- [ ] 阿里云短信签名和模板CODE(需在阿里云控制台创建)
|
||
- [ ] 验证码有效期(默认 60 秒是否合适)
|
||
- [ ] 每小时同一手机号发送次数上限(默认 10 次)
|
||
- [ ] 验证失败次数上限(默认 3 次后强制重新获取) |