# 短信服务架构说明

## 设计理念

短信服务采用**通用化、可扩展**的设计：
- ✅ 配置保持通用，不绑定特定服务商
- ✅ 支持多种短信服务商切换
- ✅ 易于扩展新的服务商
- ✅ 统一的对外接口

## 架构设计

### 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步

这样的设计既满足当前需求，又具备良好的扩展性，是一个**优雅、实用、易维护**的架构方案。

