265 lines
5.5 KiB
Markdown
265 lines
5.5 KiB
Markdown
# 短信服务架构说明
|
||
|
||
## 设计理念
|
||
|
||
短信服务采用**通用化、可扩展**的设计:
|
||
- ✅ 配置保持通用,不绑定特定服务商
|
||
- ✅ 支持多种短信服务商切换
|
||
- ✅ 易于扩展新的服务商
|
||
- ✅ 统一的对外接口
|
||
|
||
## 架构设计
|
||
|
||
### 1. 配置层(通用化)
|
||
|
||
```yaml
|
||
sms:
|
||
enabled: false # 是否启用
|
||
provider: aliyun # 服务商选择
|
||
access-key-id: xxx # 通用配置
|
||
access-key-secret: xxx
|
||
sign-name: xxx
|
||
template-code: xxx
|
||
region-id: xxx
|
||
```
|
||
|
||
**设计优势**:
|
||
- 配置不带服务商前缀,保持通用性
|
||
- 通过 `provider` 灵活切换服务商
|
||
- 未来增加服务商无需修改配置结构
|
||
|
||
### 2. 工具类层(SmsUtils)
|
||
|
||
```java
|
||
@Component
|
||
public class SmsUtils {
|
||
// 通用配置
|
||
@Value("${sms.provider:aliyun}")
|
||
private String provider;
|
||
|
||
// 对外统一接口
|
||
public boolean sendVerificationCode(String phone, String code) {
|
||
switch (provider) {
|
||
case "aliyun":
|
||
return sendByAliyun(phone, code);
|
||
case "tencent":
|
||
return sendByTencent(phone, code);
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 各服务商的私有实现
|
||
private boolean sendByAliyun(String phone, String code) { ... }
|
||
private boolean sendByTencent(String phone, String code) { ... }
|
||
}
|
||
```
|
||
|
||
**设计优势**:
|
||
- 对外统一接口:`sendVerificationCode()`
|
||
- 各服务商只是内部的私有方法
|
||
- 调用方无需关心使用哪个服务商
|
||
- 新增服务商只需添加一个私有方法
|
||
|
||
### 3. 调用层(AuthController)
|
||
|
||
```java
|
||
@Autowired
|
||
private SmsUtils smsUtils;
|
||
|
||
public ResultDomain<Boolean> sendSmsCode(...) {
|
||
// 直接调用统一接口,无需关心服务商
|
||
boolean success = smsUtils.sendVerificationCode(phone, code);
|
||
...
|
||
}
|
||
```
|
||
|
||
## 支持的服务商
|
||
|
||
### 当前已实现
|
||
- ✅ **阿里云**(dysmsapi20170525)
|
||
|
||
### 待实现
|
||
- ⏳ **腾讯云**(预留接口)
|
||
- ⏳ **华为云**(预留接口)
|
||
|
||
## 如何添加新服务商
|
||
|
||
### 步骤1:添加Maven依赖
|
||
|
||
在 `common-util/pom.xml` 中添加对应SDK:
|
||
|
||
```xml
|
||
<!-- 例如:腾讯云短信 -->
|
||
<dependency>
|
||
<groupId>com.tencentcloudapi</groupId>
|
||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||
<version>3.x.x</version>
|
||
</dependency>
|
||
```
|
||
|
||
### 步骤2:实现私有方法
|
||
|
||
在 `SmsUtils.java` 中添加私有实现方法:
|
||
|
||
```java
|
||
/**
|
||
* 使用腾讯云发送短信验证码
|
||
*/
|
||
private boolean sendByTencent(String phone, String code) {
|
||
try {
|
||
// 腾讯云SDK调用逻辑
|
||
...
|
||
return true;
|
||
} catch (Exception e) {
|
||
logger.error("腾讯云短信发送失败", e);
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤3:添加到分发逻辑
|
||
|
||
在 `sendVerificationCode()` 方法中添加case:
|
||
|
||
```java
|
||
switch (provider.toLowerCase()) {
|
||
case "aliyun":
|
||
return sendByAliyun(phone, code);
|
||
case "tencent":
|
||
return sendByTencent(phone, code); // 新增
|
||
default:
|
||
logger.error("未知的短信服务商: {}", provider);
|
||
return false;
|
||
}
|
||
```
|
||
|
||
### 步骤4:配置切换
|
||
|
||
修改 `application.yml`:
|
||
|
||
```yaml
|
||
sms:
|
||
provider: tencent # 切换到腾讯云
|
||
```
|
||
|
||
完成!无需修改调用方代码。
|
||
|
||
## 开发模式
|
||
|
||
### 模拟模式(推荐用于开发)
|
||
|
||
```yaml
|
||
sms:
|
||
enabled: false # 关闭真实发送
|
||
```
|
||
|
||
**特点**:
|
||
- 不会实际发送短信
|
||
- 验证码输出到日志
|
||
- 不产生任何费用
|
||
- 便于本地调试
|
||
|
||
### 真实发送模式
|
||
|
||
```yaml
|
||
sms:
|
||
enabled: true
|
||
provider: aliyun
|
||
access-key-id: xxx
|
||
access-key-secret: xxx
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 环境隔离
|
||
|
||
**开发环境**:
|
||
```yaml
|
||
sms:
|
||
enabled: false # 模拟模式
|
||
```
|
||
|
||
**生产环境**:
|
||
```yaml
|
||
sms:
|
||
enabled: true
|
||
provider: aliyun
|
||
access-key-id: ${SMS_ACCESS_KEY_ID} # 从环境变量读取
|
||
access-key-secret: ${SMS_ACCESS_KEY_SECRET}
|
||
```
|
||
|
||
### 2. 安全性
|
||
|
||
- ❌ 不要将 AccessKey 写在代码中
|
||
- ❌ 不要将 AccessKey 提交到 Git
|
||
- ✅ 使用环境变量或配置中心
|
||
- ✅ 使用 RAM 子账号而非主账号
|
||
- ✅ 定期更换密钥
|
||
|
||
### 3. 可靠性
|
||
|
||
```java
|
||
// 系统已实现:
|
||
- 60秒发送频率限制(防刷)
|
||
- 10分钟验证码有效期
|
||
- Redis存储验证码
|
||
- 手机号格式验证
|
||
|
||
// 建议增加:
|
||
- 图形验证码前置
|
||
- IP限流
|
||
- 黑名单机制
|
||
- 发送失败重试
|
||
```
|
||
|
||
### 4. 监控告警
|
||
|
||
建议监控指标:
|
||
- 短信发送成功率
|
||
- 短信发送量(防异常消耗)
|
||
- 验证码验证成功率
|
||
- 单个手机号发送频率
|
||
|
||
## 优势总结
|
||
|
||
### 对比旧方案
|
||
|
||
**旧方案**(绑定服务商):
|
||
```yaml
|
||
aliyun:
|
||
sms:
|
||
enabled: true
|
||
```
|
||
- ❌ 配置绑定服务商
|
||
- ❌ 切换服务商需要大量修改
|
||
- ❌ 扩展性差
|
||
|
||
**新方案**(通用化):
|
||
```yaml
|
||
sms:
|
||
provider: aliyun
|
||
enabled: true
|
||
```
|
||
- ✅ 配置保持通用
|
||
- ✅ 切换服务商只需修改 `provider`
|
||
- ✅ 扩展性强,易于维护
|
||
|
||
### 符合设计原则
|
||
|
||
- **开闭原则**:对扩展开放,对修改关闭
|
||
- **单一职责**:每个服务商实现独立
|
||
- **依赖倒置**:调用方依赖抽象接口,不依赖具体实现
|
||
- **里氏替换**:各服务商实现可以互相替换
|
||
|
||
## 总结
|
||
|
||
这个架构设计的核心理念是:
|
||
1. **配置通用化**:不绑定特定服务商
|
||
2. **实现私有化**:服务商只是一个方法
|
||
3. **接口统一化**:对外提供统一接口
|
||
4. **扩展简单化**:新增服务商仅需3步
|
||
|
||
这样的设计既满足当前需求,又具备良好的扩展性,是一个**优雅、实用、易维护**的架构方案。
|
||
|