[Claude Workbench] Initial commit - preserving existing code

This commit is contained in:
Claude Workbench
2025-11-14 17:41:15 +08:00
commit 0f7bc05697
587 changed files with 103215 additions and 0 deletions

902
SMS_VERIFICATION_GUIDE.md Normal file
View File

@@ -0,0 +1,902 @@
# 短信验证系统使用指南
**版本:** 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. 发送成功
├─ 是 → 存入Redis5分钟过期→ 返回成功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
// 存储到Redis5分钟后自动过期
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用户收到短信
```
【星洋智慧】您的验证码为1234565分钟内有效请勿泄露给他人。
```
#### 步骤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. 查看详细错误日志
#### 故障2Redis连接失败
**排查步骤**
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技术团队