topfans/docs/superpowers/specs/2026-05-22-sms-register-design.md
zheng020 cc5565ffaf docs: 简化验证后处理流程
删除验证码 -> 创建 verify:register:{mobile} 记录 -> 注册时比对

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:43:43 +08:00

334 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 注册短信验证码功能设计文档
- **创建日期**: 2026-05-22
- **状态**: 待评审
- **目标**: 在用户注册流程中加入阿里云短信验证码验证
---
## 1. 背景与需求
当前注册流程:`手机号 + 密码 → 设置昵称 → 完成注册`
需求:在注册前增加短信验证码验证,确保手机号真实有效,防止恶意注册。
---
## 2. 交互流程设计
### 2.1 前端页面变化
修改 `frontend/pages/register/register.vue`,在当前页面嵌入短信验证码输入区:
1. 用户输入手机号后,点击"发送验证码"按钮
2. 按钮变为倒计时状态60 秒),期间不可点击
3. 用户输入收到的验证码
4. 点击"验证验证码"按钮
5. 验证通过后解锁密码输入框,继续原有注册流程
### 2.2 验证码状态
| 状态 | 描述 |
|------|------|
| 未发送 | 默认状态,输入框禁用 |
| 已发送(倒计时中) | 输入框启用,倒计时按钮显示剩余秒数 |
| 已发送(可重发) | 倒计时结束,按钮恢复可点击 |
| 验证成功 | 输入框显示绿色对勾,继续注册流程 |
| 验证失败 | 显示错误提示,用户可重新发送 |
### 2.3 后端流程
```
用户输入手机号 → 发送验证码请求 → 后端生成6位随机码 → 存入Redis60秒有效期 → 调用阿里云SMS API → 返回前端
用户输入验证码 → 验证请求 → 后端从Redis取出比对 → 一致则标记验证成功 → 继续注册流程
```
---
## 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 配置原本就在 gatewayOSS 用的是同一套),直接复用
- 前端请求在网关层就能触发短信,不用穿透到后端服务
- 网关是单点,处理短信很方便
**缺点:**
- 短信发送成功后的后续注册流程还在 userService逻辑割裂
- 网关承担了越来越多的职责路由、认证、OSS上传、现在又加短信越来越重
- 如果 SMS 认证配置要从 gateway 复用,需要确保 userService 能访问到同一份配置,增加了部署耦合
**适用场景**:适合短信作为独立操作、不属于用户注册主流程的场景。
---
## 4. 方案对比
| | 方案一userService | 方案二(独立微服务) | 方案三gateway |
|--|--|--|--|
| 部署复杂度 | 低 | 高 | 低 |
| 延迟 | 低 | 中 | 低 |
| 逻辑内聚性 | **高**(注册+短信在一起) | 高(短信独立) | 差(分散在两处) |
| 可扩展性 | 中 | **高** | 低 |
| 维护成本 | 低 | 中 | 低 |
---
## 5. 技术选型
| 项目 | 选择 |
|------|------|
| 短信 SDK | `github.com/aliyundysms/dysmsapi-go-sdk`(阿里官方 Go SDK |
| 认证方式 | AccessKeyID/Secret与 OSS 配置复用同一套) |
| 验证码存储 | 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要求用户重新获取
- **发送频率限制**:记录 `sms:limit:{mobile}:hour`,每小时最多发送 10 次,超限返回 429
- **IP 维度限流**(可选):记录 `sms:limit:{ip}:hour`,每 IP 每小时最多请求 30 次
### 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
# 阿里云短信配置
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
# 阿里云 OSS 配置(复用同一套凭证)
OSS_ACCESS_KEY_ID=your_access_key_id
OSS_ACCESS_KEY_SECRET=your_access_key_secret
OSS_STS_ROLE_ARN=acs:ram::1387642798143585:role/top-fans-oss-user
```
3. 重启 userService 服务
---
## 11. 安全注意事项
1. **验证码日志脱敏**:日志中禁止记录明文验证码,只能记录手机号和发送状态
2. **verify_token 安全**token 有效期 5 分钟,只能使用一次,验证后立即删除
3. **防暴力破解**:验证失败 3 次后强制删除验证码,要求用户重新获取
4. **限流保护**:从手机号和 IP 两个维度限制请求频率
---
## 12. 待确定事项
- [ ] 阿里云短信签名和模板CODE需在阿里云控制台创建
- [ ] 验证码有效期(默认 60 秒是否合适)
- [ ] 每小时同一手机号发送次数上限(默认 10 次)
- [ ] 验证失败次数上限(默认 3 次后强制重新获取)