21 KiB
21 KiB
短信验证系统使用指南
版本: v1.0.0
更新时间: 2025-11-03
系统名称: 1818AI 用户服务短信验证模块
📋 目录
📖 系统概述
功能说明
短信验证系统基于阿里云短信服务实现,提供验证码的发送和校验功能。主要用于用户注册、登录、密码重置等关键业务场景。
技术架构
┌─────────────────────────────────────────────────┐
│ 前端应用 │
└──────────────────┬──────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────────┐
│ Spring Boot 应用 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │MsmController │─────▶│ MsmService │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │Redis缓存 │ │阿里云SMS API │ │
│ │(验证码存储) │ │(短信发送) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────┘
核心特性
- ✅ 6位数字验证码 - 简单易输入
- ✅ 5分钟有效期 - 自动过期保护
- ✅ 一次性使用 - 验证后立即失效
- ✅ 防重复发送 - 验证码存在时拒绝重发
- ✅ 强制发送模式 - 支持覆盖已存在的验证码
- ✅ 完整日志记录 - 便于追踪和调试
⚙️ 配置说明
配置文件位置
src/main/resources/application.yml
配置内容
# --- 短信配置 ---
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. 开通短信服务
- 登录 阿里云控制台
- 开通"短信服务"产品
- 完成实名认证
2. 创建短信签名
- 进入"短信服务控制台" → "国内消息" → "签名管理"
- 点击"添加签名"
- 填写签名信息:
- 签名名称:星洋智慧(或您的公司/产品名)
- 签名来源:企事业单位的全称或简称
- 适用场景:验证码
- 提交审核(通常1-2个工作日)
3. 创建短信模板
- 进入"模板管理" → "添加模板"
- 填写模板信息:
- 模板类型:验证码
- 模板名称:验证码通知
- 模板内容:
您的验证码为:${code},5分钟内有效,请勿泄露给他人。
- 提交审核(通常1-2个工作日)
- 审核通过后获得模板CODE(如:SMS_491985030)
4. 获取AccessKey
- 进入"AccessKey管理"
- 创建AccessKey(如果没有)
- 记录
AccessKey ID和AccessKey Secret
⚠️ 安全提示:
- AccessKey Secret 请妥善保管,不要泄露
- 建议使用子账号AccessKey,并授予最小权限
- 定期轮换AccessKey
🔌 API接口文档
1. 发送短信验证码
接口信息
GET /user/msm/send/{phone}
请求参数
路径参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| phone | String | 是 | 手机号(11位) | 13800138000 |
Query参数:
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| force | Boolean | 否 | false | 是否强制发送新验证码 |
响应示例
成功响应:
{
"code": 200,
"message": "success",
"data": true
}
失败响应:
{
"code": 400,
"message": "验证码已存在,请稍后再试",
"data": null
}
{
"code": 500,
"message": "短信发送失败,请稍后重试",
"data": null
}
业务逻辑
1. 检查Redis中是否已有验证码
├─ 有验证码 且 force=false → 返回错误(400)
└─ 无验证码 或 force=true → 继续
↓
2. 生成6位随机数字验证码(100000-999999)
↓
3. 调用阿里云短信API发送验证码
↓
4. 发送成功
├─ 是 → 存入Redis(5分钟过期)→ 返回成功(200)
└─ 否 → 返回错误(500)
使用示例
普通发送:
curl -X GET "http://localhost:8081/user/msm/send/13800138000"
强制发送(覆盖已存在的验证码):
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true"
注意事项
- ⏱️ 验证码5分钟内有效
- 🔒 同一手机号在验证码未过期前不能重复发送(除非force=true)
- 💰 每次发送会产生短信费用
- 📝 所有操作都会记录详细日志
💼 业务场景
1. 短信登录
接口:POST /user/auth/sms-login
流程:
用户输入手机号
↓
调用发送验证码接口
↓
用户收到短信
↓
用户输入验证码
↓
调用登录接口(验证码校验)
↓
校验成功 → 生成JWT Token → 返回登录成功
请求示例:
{
"phone": "13800138000",
"code": "123456"
}
响应示例:
{
"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
请求示例:
{
"phone": "13800138000",
"code": "123456",
"password": "Abc123456",
"inviteCode": "ABC123" // 可选
}
3. 重置密码
接口:POST /user/auth/reset-password
流程:
用户输入手机号
↓
调用发送验证码接口
↓
用户输入验证码和新密码
↓
调用重置密码接口(验证码校验)
↓
校验成功 → 更新密码 → 返回成功
请求示例:
{
"phone": "13800138000",
"code": "123456",
"newPassword": "NewPass123"
}
4. 修改手机号
接口:PUT /user/users/info
流程:
用户输入新手机号
↓
向新手机号发送验证码
↓
用户输入验证码
↓
调用修改接口(验证码校验)
↓
校验成功 → 更新手机号 → 返回成功
5. 修改密码(需要验证码)
接口:PUT /user/users/info
流程:
用户输入当前手机号
↓
发送验证码
↓
用户输入验证码和新密码
↓
调用修改接口(验证码校验)
↓
校验成功 → 更新密码 → 返回成功
🔒 安全机制
1. 验证码有效期控制
// 存储到Redis,5分钟后自动过期
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
说明:
- 验证码在Redis中存储5分钟
- 5分钟后自动删除,无法继续使用
- 防止验证码长期有效带来的安全风险
2. 一次性使用机制
// 验证成功后立即删除
redisTemplate.delete(phone);
说明:
- 验证码验证成功后立即从Redis删除
- 每个验证码只能使用一次
- 防止验证码被重复使用
3. 错误清除机制
// 验证失败时清除验证码
if (!cachedCode.equals(code)) {
redisTemplate.delete(phone);
throw new RuntimeException("验证码错误或已过期");
}
说明:
- 验证码输入错误时立即清除
- 防止暴力破解攻击
- 用户需重新获取验证码
4. 业务失败清除
// 业务逻辑失败时也清除验证码
if (existingUser != null) {
redisTemplate.delete(phone);
throw new RuntimeException("手机号已注册");
}
说明:
- 即使验证码正确,但业务逻辑失败时也清除验证码
- 例如:注册时手机号已存在、登录时用户不存在等
- 确保一个验证码只用于一次完整的业务操作
5. 重复发送限制
// 检查是否已有验证码
String existingCode = redisTemplate.opsForValue().get(phone);
if (existingCode != null && !force) {
return Result.error(400, "验证码已存在,请稍后再试");
}
说明:
- 验证码存在时拒绝重复发送
- 防止短信轰炸和资源浪费
- 特殊情况可使用
force=true强制发送
安全建议
⚠️ 当前缺少的安全措施
-
频率限制
建议:同一手机号1分钟内最多发送1次 建议:同一IP每小时最多发送10次 建议:单个手机号每天最多发送5次 -
图形验证码
建议:发送短信前先验证图形验证码 防止:自动化机器人攻击 -
手机号归属地验证
建议:检查手机号归属地是否为国内 防止:国际短信费用损失 -
黑名单机制
建议:维护恶意手机号黑名单 防止:滥用和攻击
❌ 错误处理
错误码说明
| 错误码 | 错误信息 | 原因 | 解决方法 |
|---|---|---|---|
| 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 | 参数无效 | 检查所有参数 |
日志查询
# 查看短信发送日志
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:发送验证码
curl -X GET "http://localhost:8081/user/msm/send/13800138000" \
-H "Accept: application/json"
响应:
{
"code": 200,
"message": "success",
"data": true
}
步骤2:用户收到短信
【星洋智慧】您的验证码为:123456,5分钟内有效,请勿泄露给他人。
步骤3:使用验证码登录
curl -X POST "http://localhost:8081/user/auth/sms-login" \
-H "Content-Type: application/json" \
-d '{
"phone": "13800138000",
"code": "123456"
}'
响应:
{
"code": 200,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYiLCJwaG9uZSI6IjEzODAwMTM4MDAwIiwiaWF0IjoxNjk5MDA4MDAwLCJleHAiOjE2OTk2MTI4MDB9.xxx",
"tokenExpiresAt": "2025-11-10T12:00:00",
"userInfo": {
"userId": 123456,
"phone": "13800138000",
"username": "测试用户"
}
}
}
强制发送验证码示例
场景:用户点击"重新发送"时,即使验证码未过期也要发送新的
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true" \
-H "Accept: application/json"
JavaScript/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: 检查以下几点:
- 手机号格式是否正确(11位数字)
- 查看服务器日志,确认是否发送成功
- 检查阿里云短信服务余额是否充足
- 确认短信签名和模板是否已审核通过
- 检查手机是否有信号,是否被拦截为垃圾短信
Q2: 提示"验证码已存在"怎么办?
A: 两种解决方法:
- 等待5分钟后重试(验证码自动过期)
- 使用
force=true参数强制发送新验证码
curl -X GET "http://localhost:8081/user/msm/send/13800138000?force=true"
Q3: 验证码输入错误后无法重试?
A: 这是安全机制设计,验证码输入错误后会立即失效。需要重新获取新的验证码。
Q4: 如何查看短信发送记录?
A: 查看服务器日志:
# 查看所有短信相关日志
sudo journalctl -u spring_1818_user_server | grep "短信"
# 查看特定手机号的发送记录
sudo journalctl -u spring_1818_user_server | grep "phone: 13800138000"
Q5: 短信费用如何计算?
A:
- 国内短信:约0.045元/条
- 计费由阿里云短信服务收取
- 可在阿里云控制台查看详细账单
Q6: 如何修改验证码有效期?
A: 修改代码中的过期时间:
// 当前是5分钟
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
// 修改为3分钟
redisTemplate.opsForValue().set(phone, code, 3, TimeUnit.MINUTES);
Q7: 如何修改验证码位数?
A: 修改生成验证码的代码:
// 当前是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计数器:
// 伪代码示例
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. 短信发送成功率
-- 查看今日短信发送情况(需要添加短信发送记录表)
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验证码监控
# 连接Redis
redis-cli
# 查看所有手机号的验证码(生产环境不建议执行)
KEYS *
# 查看特定手机号的验证码
GET 13800138000
# 查看验证码剩余过期时间(秒)
TTL 13800138000
3. 日志监控
# 统计今日短信发送次数
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:大量短信发送失败
排查步骤:
- 检查阿里云短信服务是否正常
- 检查网络连接是否正常
- 检查AccessKey是否过期
- 检查账户余额是否充足
- 查看详细错误日志
故障2:Redis连接失败
排查步骤:
- 检查Redis服务是否运行
- 检查Redis连接配置
- 检查防火墙设置
- 重启Redis服务
故障3:验证码无法验证
排查步骤:
- 检查Redis中是否存在验证码
- 检查验证码是否已过期
- 检查代码逻辑是否正确
- 查看详细日志
性能优化建议
1. Redis连接池优化
spring:
redis:
lettuce:
pool:
max-active: 20 # 最大连接数
max-idle: 10 # 最大空闲连接数
min-idle: 5 # 最小空闲连接数
2. 短信发送异步化
// 使用@Async异步发送短信
@Async
public CompletableFuture<Boolean> sendAsync(Map<String, Object> param, String phone) {
boolean result = send(param, phone);
return CompletableFuture.completedFuture(result);
}
3. 添加缓存预热
// 应用启动时检查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分钟过期机制
- ✅ 实现一次性使用机制
- ✅ 添加强制发送模式
- ✅ 完善日志记录
📞 技术支持
遇到问题?
- 查看本文档的常见问题章节
- 查看服务器日志获取详细错误信息
- 检查阿里云短信服务控制台
- 联系技术团队
相关文档:
文档版本: v1.0.0
最后更新: 2025-11-03
维护团队: 1818AI技术团队