903 lines
21 KiB
Markdown
903 lines
21 KiB
Markdown
|
|
# 短信验证系统使用指南
|
|||
|
|
|
|||
|
|
**版本:** v1.0.0
|
|||
|
|
**更新时间:** 2025-11-03
|
|||
|
|
**系统名称:** 1818AI 用户服务短信验证模块
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 目录
|
|||
|
|
|
|||
|
|
- [系统概述](#系统概述)
|
|||
|
|
- [配置说明](#配置说明)
|
|||
|
|
- [API接口文档](#api接口文档)
|
|||
|
|
- [业务场景](#业务场景)
|
|||
|
|
- [安全机制](#安全机制)
|
|||
|
|
- [错误处理](#错误处理)
|
|||
|
|
- [使用示例](#使用示例)
|
|||
|
|
- [常见问题](#常见问题)
|
|||
|
|
- [维护指南](#维护指南)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📖 系统概述
|
|||
|
|
|
|||
|
|
### 功能说明
|
|||
|
|
|
|||
|
|
短信验证系统基于**阿里云短信服务**实现,提供验证码的发送和校验功能。主要用于用户注册、登录、密码重置等关键业务场景。
|
|||
|
|
|
|||
|
|
### 技术架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ 前端应用 │
|
|||
|
|
└──────────────────┬──────────────────────────────┘
|
|||
|
|
│ HTTP/HTTPS
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────┐
|
|||
|
|
│ Spring Boot 应用 │
|
|||
|
|
│ ┌──────────────┐ ┌──────────────┐ │
|
|||
|
|
│ │MsmController │─────▶│ MsmService │ │
|
|||
|
|
│ └──────────────┘ └──────┬───────┘ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ ▼ ▼ │
|
|||
|
|
│ ┌──────────────┐ ┌──────────────┐ │
|
|||
|
|
│ │Redis缓存 │ │阿里云SMS API │ │
|
|||
|
|
│ │(验证码存储) │ │(短信发送) │ │
|
|||
|
|
│ └──────────────┘ └──────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 核心特性
|
|||
|
|
|
|||
|
|
- ✅ **6位数字验证码** - 简单易输入
|
|||
|
|
- ✅ **5分钟有效期** - 自动过期保护
|
|||
|
|
- ✅ **一次性使用** - 验证后立即失效
|
|||
|
|
- ✅ **防重复发送** - 验证码存在时拒绝重发
|
|||
|
|
- ✅ **强制发送模式** - 支持覆盖已存在的验证码
|
|||
|
|
- ✅ **完整日志记录** - 便于追踪和调试
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚙️ 配置说明
|
|||
|
|
|
|||
|
|
### 配置文件位置
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/main/resources/application.yml
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 配置内容
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# --- 短信配置 ---
|
|||
|
|
ly:
|
|||
|
|
sms:
|
|||
|
|
accessKeyId: LTAI5t68do3qVXx5Rufugt3X # 阿里云AccessKey ID
|
|||
|
|
accessKeySecret: 2vD9ToIff49Vph4JQXsn0Cy8nXQfzA # 阿里云AccessKey Secret
|
|||
|
|
signName: 星洋智慧 # 短信签名
|
|||
|
|
verifyTemplateCode: SMS_491985030 # 验证码短信模板编号
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 配置项说明
|
|||
|
|
|
|||
|
|
| 配置项 | 类型 | 必填 | 说明 |
|
|||
|
|
|--------|------|------|------|
|
|||
|
|
| `accessKeyId` | String | 是 | 阿里云访问密钥ID,从阿里云控制台获取 |
|
|||
|
|
| `accessKeySecret` | String | 是 | 阿里云访问密钥Secret,从阿里云控制台获取 |
|
|||
|
|
| `signName` | String | 是 | 短信签名,需在阿里云短信服务中申请 |
|
|||
|
|
| `verifyTemplateCode` | String | 是 | 短信模板编号,需在阿里云短信服务中申请 |
|
|||
|
|
|
|||
|
|
### 阿里云短信服务配置步骤
|
|||
|
|
|
|||
|
|
#### 1. 开通短信服务
|
|||
|
|
|
|||
|
|
1. 登录 [阿里云控制台](https://www.aliyun.com/)
|
|||
|
|
2. 开通"短信服务"产品
|
|||
|
|
3. 完成实名认证
|
|||
|
|
|
|||
|
|
#### 2. 创建短信签名
|
|||
|
|
|
|||
|
|
1. 进入"短信服务控制台" → "国内消息" → "签名管理"
|
|||
|
|
2. 点击"添加签名"
|
|||
|
|
3. 填写签名信息:
|
|||
|
|
- **签名名称**:星洋智慧(或您的公司/产品名)
|
|||
|
|
- **签名来源**:企事业单位的全称或简称
|
|||
|
|
- **适用场景**:验证码
|
|||
|
|
4. 提交审核(通常1-2个工作日)
|
|||
|
|
|
|||
|
|
#### 3. 创建短信模板
|
|||
|
|
|
|||
|
|
1. 进入"模板管理" → "添加模板"
|
|||
|
|
2. 填写模板信息:
|
|||
|
|
- **模板类型**:验证码
|
|||
|
|
- **模板名称**:验证码通知
|
|||
|
|
- **模板内容**:`您的验证码为:${code},5分钟内有效,请勿泄露给他人。`
|
|||
|
|
3. 提交审核(通常1-2个工作日)
|
|||
|
|
4. 审核通过后获得**模板CODE**(如:SMS_491985030)
|
|||
|
|
|
|||
|
|
#### 4. 获取AccessKey
|
|||
|
|
|
|||
|
|
1. 进入"AccessKey管理"
|
|||
|
|
2. 创建AccessKey(如果没有)
|
|||
|
|
3. 记录 `AccessKey ID` 和 `AccessKey Secret`
|
|||
|
|
|
|||
|
|
⚠️ **安全提示**:
|
|||
|
|
- AccessKey Secret 请妥善保管,不要泄露
|
|||
|
|
- 建议使用子账号AccessKey,并授予最小权限
|
|||
|
|
- 定期轮换AccessKey
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔌 API接口文档
|
|||
|
|
|
|||
|
|
### 1. 发送短信验证码
|
|||
|
|
|
|||
|
|
#### 接口信息
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /user/msm/send/{phone}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 请求参数
|
|||
|
|
|
|||
|
|
**路径参数**:
|
|||
|
|
|
|||
|
|
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
|||
|
|
|--------|------|------|------|------|
|
|||
|
|
| phone | String | 是 | 手机号(11位) | 13800138000 |
|
|||
|
|
|
|||
|
|
**Query参数**:
|
|||
|
|
|
|||
|
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|||
|
|
|--------|------|------|--------|------|
|
|||
|
|
| force | Boolean | 否 | false | 是否强制发送新验证码 |
|
|||
|
|
|
|||
|
|
#### 响应示例
|
|||
|
|
|
|||
|
|
**成功响应**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**失败响应**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 400,
|
|||
|
|
"message": "验证码已存在,请稍后再试",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 500,
|
|||
|
|
"message": "短信发送失败,请稍后重试",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 业务逻辑
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. 检查Redis中是否已有验证码
|
|||
|
|
├─ 有验证码 且 force=false → 返回错误(400)
|
|||
|
|
└─ 无验证码 或 force=true → 继续
|
|||
|
|
↓
|
|||
|
|
2. 生成6位随机数字验证码(100000-999999)
|
|||
|
|
↓
|
|||
|
|
3. 调用阿里云短信API发送验证码
|
|||
|
|
↓
|
|||
|
|
4. 发送成功
|
|||
|
|
├─ 是 → 存入Redis(5分钟过期)→ 返回成功(200)
|
|||
|
|
└─ 否 → 返回错误(500)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 使用示例
|
|||
|
|
|
|||
|
|
**普通发送**:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X GET "http://localhost:8081/user/msm/send/13800138000"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**强制发送**(覆盖已存在的验证码):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 注意事项
|
|||
|
|
|
|||
|
|
- ⏱️ 验证码5分钟内有效
|
|||
|
|
- 🔒 同一手机号在验证码未过期前不能重复发送(除非force=true)
|
|||
|
|
- 💰 每次发送会产生短信费用
|
|||
|
|
- 📝 所有操作都会记录详细日志
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💼 业务场景
|
|||
|
|
|
|||
|
|
### 1. 短信登录
|
|||
|
|
|
|||
|
|
**接口**:`POST /user/auth/sms-login`
|
|||
|
|
|
|||
|
|
**流程**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户输入手机号
|
|||
|
|
↓
|
|||
|
|
调用发送验证码接口
|
|||
|
|
↓
|
|||
|
|
用户收到短信
|
|||
|
|
↓
|
|||
|
|
用户输入验证码
|
|||
|
|
↓
|
|||
|
|
调用登录接口(验证码校验)
|
|||
|
|
↓
|
|||
|
|
校验成功 → 生成JWT Token → 返回登录成功
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**请求示例**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"code": "123456"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应示例**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": {
|
|||
|
|
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
|||
|
|
"tokenExpiresAt": "2025-11-10T12:00:00",
|
|||
|
|
"userInfo": {
|
|||
|
|
"userId": 123456,
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"username": "用户昵称"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 用户注册
|
|||
|
|
|
|||
|
|
**接口**:`POST /user/auth/register`
|
|||
|
|
|
|||
|
|
**流程**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户输入手机号和密码
|
|||
|
|
↓
|
|||
|
|
调用发送验证码接口
|
|||
|
|
↓
|
|||
|
|
用户输入验证码
|
|||
|
|
↓
|
|||
|
|
调用注册接口(验证码校验)
|
|||
|
|
↓
|
|||
|
|
校验成功 → 创建用户 → 自动登录 → 返回Token
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**请求示例**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"code": "123456",
|
|||
|
|
"password": "Abc123456",
|
|||
|
|
"inviteCode": "ABC123" // 可选
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 重置密码
|
|||
|
|
|
|||
|
|
**接口**:`POST /user/auth/reset-password`
|
|||
|
|
|
|||
|
|
**流程**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户输入手机号
|
|||
|
|
↓
|
|||
|
|
调用发送验证码接口
|
|||
|
|
↓
|
|||
|
|
用户输入验证码和新密码
|
|||
|
|
↓
|
|||
|
|
调用重置密码接口(验证码校验)
|
|||
|
|
↓
|
|||
|
|
校验成功 → 更新密码 → 返回成功
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**请求示例**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"code": "123456",
|
|||
|
|
"newPassword": "NewPass123"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 修改手机号
|
|||
|
|
|
|||
|
|
**接口**:`PUT /user/users/info`
|
|||
|
|
|
|||
|
|
**流程**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户输入新手机号
|
|||
|
|
↓
|
|||
|
|
向新手机号发送验证码
|
|||
|
|
↓
|
|||
|
|
用户输入验证码
|
|||
|
|
↓
|
|||
|
|
调用修改接口(验证码校验)
|
|||
|
|
↓
|
|||
|
|
校验成功 → 更新手机号 → 返回成功
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 修改密码(需要验证码)
|
|||
|
|
|
|||
|
|
**接口**:`PUT /user/users/info`
|
|||
|
|
|
|||
|
|
**流程**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户输入当前手机号
|
|||
|
|
↓
|
|||
|
|
发送验证码
|
|||
|
|
↓
|
|||
|
|
用户输入验证码和新密码
|
|||
|
|
↓
|
|||
|
|
调用修改接口(验证码校验)
|
|||
|
|
↓
|
|||
|
|
校验成功 → 更新密码 → 返回成功
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔒 安全机制
|
|||
|
|
|
|||
|
|
### 1. 验证码有效期控制
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 存储到Redis,5分钟后自动过期
|
|||
|
|
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 验证码在Redis中存储5分钟
|
|||
|
|
- 5分钟后自动删除,无法继续使用
|
|||
|
|
- 防止验证码长期有效带来的安全风险
|
|||
|
|
|
|||
|
|
### 2. 一次性使用机制
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 验证成功后立即删除
|
|||
|
|
redisTemplate.delete(phone);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 验证码验证成功后立即从Redis删除
|
|||
|
|
- 每个验证码只能使用一次
|
|||
|
|
- 防止验证码被重复使用
|
|||
|
|
|
|||
|
|
### 3. 错误清除机制
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 验证失败时清除验证码
|
|||
|
|
if (!cachedCode.equals(code)) {
|
|||
|
|
redisTemplate.delete(phone);
|
|||
|
|
throw new RuntimeException("验证码错误或已过期");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 验证码输入错误时立即清除
|
|||
|
|
- 防止暴力破解攻击
|
|||
|
|
- 用户需重新获取验证码
|
|||
|
|
|
|||
|
|
### 4. 业务失败清除
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 业务逻辑失败时也清除验证码
|
|||
|
|
if (existingUser != null) {
|
|||
|
|
redisTemplate.delete(phone);
|
|||
|
|
throw new RuntimeException("手机号已注册");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 即使验证码正确,但业务逻辑失败时也清除验证码
|
|||
|
|
- 例如:注册时手机号已存在、登录时用户不存在等
|
|||
|
|
- 确保一个验证码只用于一次完整的业务操作
|
|||
|
|
|
|||
|
|
### 5. 重复发送限制
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 检查是否已有验证码
|
|||
|
|
String existingCode = redisTemplate.opsForValue().get(phone);
|
|||
|
|
if (existingCode != null && !force) {
|
|||
|
|
return Result.error(400, "验证码已存在,请稍后再试");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明**:
|
|||
|
|
- 验证码存在时拒绝重复发送
|
|||
|
|
- 防止短信轰炸和资源浪费
|
|||
|
|
- 特殊情况可使用`force=true`强制发送
|
|||
|
|
|
|||
|
|
### 安全建议
|
|||
|
|
|
|||
|
|
#### ⚠️ 当前缺少的安全措施
|
|||
|
|
|
|||
|
|
1. **频率限制**
|
|||
|
|
```
|
|||
|
|
建议:同一手机号1分钟内最多发送1次
|
|||
|
|
建议:同一IP每小时最多发送10次
|
|||
|
|
建议:单个手机号每天最多发送5次
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **图形验证码**
|
|||
|
|
```
|
|||
|
|
建议:发送短信前先验证图形验证码
|
|||
|
|
防止:自动化机器人攻击
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **手机号归属地验证**
|
|||
|
|
```
|
|||
|
|
建议:检查手机号归属地是否为国内
|
|||
|
|
防止:国际短信费用损失
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. **黑名单机制**
|
|||
|
|
```
|
|||
|
|
建议:维护恶意手机号黑名单
|
|||
|
|
防止:滥用和攻击
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ❌ 错误处理
|
|||
|
|
|
|||
|
|
### 错误码说明
|
|||
|
|
|
|||
|
|
| 错误码 | 错误信息 | 原因 | 解决方法 |
|
|||
|
|
|--------|---------|------|----------|
|
|||
|
|
| 400 | 验证码已存在,请稍后再试 | Redis中已有未过期的验证码 | 等待5分钟或使用force=true |
|
|||
|
|
| 400 | 验证码错误或已过期 | 验证码不存在或不匹配 | 重新获取验证码 |
|
|||
|
|
| 500 | 短信发送失败,请稍后重试 | 阿里云短信服务调用失败 | 检查配置和网络,查看日志 |
|
|||
|
|
| 500 | 发送短信验证码失败 | 系统异常 | 查看服务器日志 |
|
|||
|
|
|
|||
|
|
### 阿里云短信服务错误码
|
|||
|
|
|
|||
|
|
| 阿里云错误码 | 说明 | 处理方法 |
|
|||
|
|
|-------------|------|----------|
|
|||
|
|
| OK | 发送成功 | - |
|
|||
|
|
| isv.MOBILE_NUMBER_ILLEGAL | 手机号格式错误 | 检查手机号格式 |
|
|||
|
|
| isv.BUSINESS_LIMIT_CONTROL | 业务限流 | 降低发送频率 |
|
|||
|
|
| isv.AMOUNT_NOT_ENOUGH | 账户余额不足 | 充值阿里云短信服务 |
|
|||
|
|
| isv.TEMPLATE_MISSING_PARAMETERS | 模板参数缺失 | 检查模板参数 |
|
|||
|
|
| isv.INVALID_PARAMETERS | 参数无效 | 检查所有参数 |
|
|||
|
|
|
|||
|
|
### 日志查询
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看短信发送日志
|
|||
|
|
sudo journalctl -u spring_1818_user_server | grep "短信"
|
|||
|
|
|
|||
|
|
# 查看特定手机号的日志
|
|||
|
|
sudo journalctl -u spring_1818_user_server | grep "13800138000"
|
|||
|
|
|
|||
|
|
# 查看错误日志
|
|||
|
|
sudo journalctl -u spring_1818_user_server | grep -E "ERROR|WARN" | grep "短信"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📘 使用示例
|
|||
|
|
|
|||
|
|
### 完整的登录流程示例
|
|||
|
|
|
|||
|
|
#### 步骤1:发送验证码
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X GET "http://localhost:8081/user/msm/send/13800138000" \
|
|||
|
|
-H "Accept: application/json"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤2:用户收到短信
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
【星洋智慧】您的验证码为:123456,5分钟内有效,请勿泄露给他人。
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤3:使用验证码登录
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X POST "http://localhost:8081/user/auth/sms-login" \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"code": "123456"
|
|||
|
|
}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": {
|
|||
|
|
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYiLCJwaG9uZSI6IjEzODAwMTM4MDAwIiwiaWF0IjoxNjk5MDA4MDAwLCJleHAiOjE2OTk2MTI4MDB9.xxx",
|
|||
|
|
"tokenExpiresAt": "2025-11-10T12:00:00",
|
|||
|
|
"userInfo": {
|
|||
|
|
"userId": 123456,
|
|||
|
|
"phone": "13800138000",
|
|||
|
|
"username": "测试用户"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 强制发送验证码示例
|
|||
|
|
|
|||
|
|
**场景**:用户点击"重新发送"时,即使验证码未过期也要发送新的
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true" \
|
|||
|
|
-H "Accept: application/json"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### JavaScript/TypeScript 示例
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 发送验证码
|
|||
|
|
async function sendSmsCode(phone: string, force: boolean = false): Promise<boolean> {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(
|
|||
|
|
`http://localhost:8081/user/msm/send/${phone}?force=${force}`,
|
|||
|
|
{
|
|||
|
|
method: 'GET',
|
|||
|
|
headers: {
|
|||
|
|
'Accept': 'application/json'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 200) {
|
|||
|
|
console.log('验证码发送成功');
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
console.error('验证码发送失败:', result.message);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('网络错误:', error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 短信登录
|
|||
|
|
async function smsLogin(phone: string, code: string): Promise<LoginResponse> {
|
|||
|
|
const response = await fetch('http://localhost:8081/user/auth/sms-login', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({ phone, code })
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 200) {
|
|||
|
|
// 保存token到localStorage
|
|||
|
|
localStorage.setItem('token', result.data.token);
|
|||
|
|
return result.data;
|
|||
|
|
} else {
|
|||
|
|
throw new Error(result.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用示例
|
|||
|
|
async function handleLogin() {
|
|||
|
|
const phone = '13800138000';
|
|||
|
|
const code = '123456';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const loginData = await smsLogin(phone, code);
|
|||
|
|
console.log('登录成功:', loginData);
|
|||
|
|
// 跳转到主页
|
|||
|
|
window.location.href = '/home';
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('登录失败:', error.message);
|
|||
|
|
alert(error.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ❓ 常见问题
|
|||
|
|
|
|||
|
|
### Q1: 验证码收不到怎么办?
|
|||
|
|
|
|||
|
|
**A**: 检查以下几点:
|
|||
|
|
1. 手机号格式是否正确(11位数字)
|
|||
|
|
2. 查看服务器日志,确认是否发送成功
|
|||
|
|
3. 检查阿里云短信服务余额是否充足
|
|||
|
|
4. 确认短信签名和模板是否已审核通过
|
|||
|
|
5. 检查手机是否有信号,是否被拦截为垃圾短信
|
|||
|
|
|
|||
|
|
### Q2: 提示"验证码已存在"怎么办?
|
|||
|
|
|
|||
|
|
**A**: 两种解决方法:
|
|||
|
|
1. 等待5分钟后重试(验证码自动过期)
|
|||
|
|
2. 使用`force=true`参数强制发送新验证码
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q3: 验证码输入错误后无法重试?
|
|||
|
|
|
|||
|
|
**A**: 这是安全机制设计,验证码输入错误后会立即失效。需要重新获取新的验证码。
|
|||
|
|
|
|||
|
|
### Q4: 如何查看短信发送记录?
|
|||
|
|
|
|||
|
|
**A**: 查看服务器日志:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 查看所有短信相关日志
|
|||
|
|
sudo journalctl -u spring_1818_user_server | grep "短信"
|
|||
|
|
|
|||
|
|
# 查看特定手机号的发送记录
|
|||
|
|
sudo journalctl -u spring_1818_user_server | grep "phone: 13800138000"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q5: 短信费用如何计算?
|
|||
|
|
|
|||
|
|
**A**:
|
|||
|
|
- 国内短信:约0.045元/条
|
|||
|
|
- 计费由阿里云短信服务收取
|
|||
|
|
- 可在阿里云控制台查看详细账单
|
|||
|
|
|
|||
|
|
### Q6: 如何修改验证码有效期?
|
|||
|
|
|
|||
|
|
**A**: 修改代码中的过期时间:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 当前是5分钟
|
|||
|
|
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
|
|||
|
|
|
|||
|
|
// 修改为3分钟
|
|||
|
|
redisTemplate.opsForValue().set(phone, code, 3, TimeUnit.MINUTES);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q7: 如何修改验证码位数?
|
|||
|
|
|
|||
|
|
**A**: 修改生成验证码的代码:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 当前是6位(100000-999999)
|
|||
|
|
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
|
|||
|
|
|
|||
|
|
// 修改为4位(1000-9999)
|
|||
|
|
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q8: 如何添加发送频率限制?
|
|||
|
|
|
|||
|
|
**A**: 需要在代码中添加额外的Redis计数器:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 伪代码示例
|
|||
|
|
String countKey = "sms:count:" + phone;
|
|||
|
|
Integer count = redisTemplate.opsForValue().get(countKey);
|
|||
|
|
|
|||
|
|
if (count != null && count >= 5) {
|
|||
|
|
return Result.error(429, "发送次数过多,请明天再试");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送成功后增加计数
|
|||
|
|
redisTemplate.opsForValue().increment(countKey);
|
|||
|
|
// 设置24小时过期
|
|||
|
|
redisTemplate.expire(countKey, 24, TimeUnit.HOURS);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛠️ 维护指南
|
|||
|
|
|
|||
|
|
### 监控指标
|
|||
|
|
|
|||
|
|
#### 1. 短信发送成功率
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 查看今日短信发送情况(需要添加短信发送记录表)
|
|||
|
|
SELECT
|
|||
|
|
DATE(create_time) as date,
|
|||
|
|
COUNT(*) as total,
|
|||
|
|
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success,
|
|||
|
|
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
|
|||
|
|
ROUND(SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as success_rate
|
|||
|
|
FROM sms_log
|
|||
|
|
WHERE DATE(create_time) = CURDATE()
|
|||
|
|
GROUP BY DATE(create_time);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. Redis验证码监控
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 连接Redis
|
|||
|
|
redis-cli
|
|||
|
|
|
|||
|
|
# 查看所有手机号的验证码(生产环境不建议执行)
|
|||
|
|
KEYS *
|
|||
|
|
|
|||
|
|
# 查看特定手机号的验证码
|
|||
|
|
GET 13800138000
|
|||
|
|
|
|||
|
|
# 查看验证码剩余过期时间(秒)
|
|||
|
|
TTL 13800138000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 日志监控
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 统计今日短信发送次数
|
|||
|
|
sudo journalctl -u spring_1818_user_server --since today | grep "短信验证码发送成功" | wc -l
|
|||
|
|
|
|||
|
|
# 统计今日短信发送失败次数
|
|||
|
|
sudo journalctl -u spring_1818_user_server --since today | grep "短信验证码发送失败" | wc -l
|
|||
|
|
|
|||
|
|
# 查看最近的错误
|
|||
|
|
sudo journalctl -u spring_1818_user_server -n 100 | grep -E "ERROR.*短信"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 定期检查项
|
|||
|
|
|
|||
|
|
#### 每日检查
|
|||
|
|
|
|||
|
|
- [ ] 查看短信发送成功率
|
|||
|
|
- [ ] 检查阿里云短信服务余额
|
|||
|
|
- [ ] 查看错误日志
|
|||
|
|
|
|||
|
|
#### 每周检查
|
|||
|
|
|
|||
|
|
- [ ] 分析短信发送量趋势
|
|||
|
|
- [ ] 检查异常手机号(发送失败率高的)
|
|||
|
|
- [ ] 审查Redis使用情况
|
|||
|
|
|
|||
|
|
#### 每月检查
|
|||
|
|
|
|||
|
|
- [ ] 审查短信费用
|
|||
|
|
- [ ] 更新AccessKey(建议定期轮换)
|
|||
|
|
- [ ] 检查短信签名和模板有效期
|
|||
|
|
|
|||
|
|
### 故障处理
|
|||
|
|
|
|||
|
|
#### 故障1:大量短信发送失败
|
|||
|
|
|
|||
|
|
**排查步骤**:
|
|||
|
|
1. 检查阿里云短信服务是否正常
|
|||
|
|
2. 检查网络连接是否正常
|
|||
|
|
3. 检查AccessKey是否过期
|
|||
|
|
4. 检查账户余额是否充足
|
|||
|
|
5. 查看详细错误日志
|
|||
|
|
|
|||
|
|
#### 故障2:Redis连接失败
|
|||
|
|
|
|||
|
|
**排查步骤**:
|
|||
|
|
1. 检查Redis服务是否运行
|
|||
|
|
2. 检查Redis连接配置
|
|||
|
|
3. 检查防火墙设置
|
|||
|
|
4. 重启Redis服务
|
|||
|
|
|
|||
|
|
#### 故障3:验证码无法验证
|
|||
|
|
|
|||
|
|
**排查步骤**:
|
|||
|
|
1. 检查Redis中是否存在验证码
|
|||
|
|
2. 检查验证码是否已过期
|
|||
|
|
3. 检查代码逻辑是否正确
|
|||
|
|
4. 查看详细日志
|
|||
|
|
|
|||
|
|
### 性能优化建议
|
|||
|
|
|
|||
|
|
#### 1. Redis连接池优化
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
spring:
|
|||
|
|
redis:
|
|||
|
|
lettuce:
|
|||
|
|
pool:
|
|||
|
|
max-active: 20 # 最大连接数
|
|||
|
|
max-idle: 10 # 最大空闲连接数
|
|||
|
|
min-idle: 5 # 最小空闲连接数
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 短信发送异步化
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 使用@Async异步发送短信
|
|||
|
|
@Async
|
|||
|
|
public CompletableFuture<Boolean> sendAsync(Map<String, Object> param, String phone) {
|
|||
|
|
boolean result = send(param, phone);
|
|||
|
|
return CompletableFuture.completedFuture(result);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 添加缓存预热
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 应用启动时检查Redis连接
|
|||
|
|
@PostConstruct
|
|||
|
|
public void init() {
|
|||
|
|
try {
|
|||
|
|
redisTemplate.opsForValue().set("health_check", "ok", 10, TimeUnit.SECONDS);
|
|||
|
|
log.info("Redis连接正常");
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
log.error("Redis连接失败", e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 更新日志
|
|||
|
|
|
|||
|
|
### v1.0.0 (2025-11-03)
|
|||
|
|
|
|||
|
|
**初始版本**:
|
|||
|
|
- ✅ 实现基础短信验证码发送功能
|
|||
|
|
- ✅ 集成阿里云短信服务
|
|||
|
|
- ✅ 实现Redis验证码存储
|
|||
|
|
- ✅ 实现5分钟过期机制
|
|||
|
|
- ✅ 实现一次性使用机制
|
|||
|
|
- ✅ 添加强制发送模式
|
|||
|
|
- ✅ 完善日志记录
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📞 技术支持
|
|||
|
|
|
|||
|
|
**遇到问题?**
|
|||
|
|
|
|||
|
|
1. 查看本文档的[常见问题](#常见问题)章节
|
|||
|
|
2. 查看服务器日志获取详细错误信息
|
|||
|
|
3. 检查阿里云短信服务控制台
|
|||
|
|
4. 联系技术团队
|
|||
|
|
|
|||
|
|
**相关文档**:
|
|||
|
|
- [阿里云短信服务文档](https://help.aliyun.com/document_detail/101414.html)
|
|||
|
|
- [Redis官方文档](https://redis.io/documentation)
|
|||
|
|
- [Spring Boot Redis文档](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.redis)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档版本**: v1.0.0
|
|||
|
|
**最后更新**: 2025-11-03
|
|||
|
|
**维护团队**: 1818AI技术团队
|
|||
|
|
|