Files
schoolNews/schoolNewsServ/auth/短信服务架构说明.md

5.5 KiB
Raw Blame History

短信服务架构说明

设计理念

短信服务采用通用化、可扩展的设计:

  • 配置保持通用,不绑定特定服务商
  • 支持多种短信服务商切换
  • 易于扩展新的服务商
  • 统一的对外接口

架构设计

1. 配置层(通用化)

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

@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

@Autowired
private SmsUtils smsUtils;

public ResultDomain<Boolean> sendSmsCode(...) {
    // 直接调用统一接口,无需关心服务商
    boolean success = smsUtils.sendVerificationCode(phone, code);
    ...
}

支持的服务商

当前已实现

  • 阿里云dysmsapi20170525

待实现

  • 腾讯云(预留接口)
  • 华为云(预留接口)

如何添加新服务商

步骤1添加Maven依赖

common-util/pom.xml 中添加对应SDK

<!-- 例如:腾讯云短信 -->
<dependency>
    <groupId>com.tencentcloudapi</groupId>
    <artifactId>tencentcloud-sdk-java</artifactId>
    <version>3.x.x</version>
</dependency>

步骤2实现私有方法

SmsUtils.java 中添加私有实现方法:

/**
 * 使用腾讯云发送短信验证码
 */
private boolean sendByTencent(String phone, String code) {
    try {
        // 腾讯云SDK调用逻辑
        ...
        return true;
    } catch (Exception e) {
        logger.error("腾讯云短信发送失败", e);
        return false;
    }
}

步骤3添加到分发逻辑

sendVerificationCode() 方法中添加case

switch (provider.toLowerCase()) {
    case "aliyun":
        return sendByAliyun(phone, code);
    case "tencent":
        return sendByTencent(phone, code);  // 新增
    default:
        logger.error("未知的短信服务商: {}", provider);
        return false;
}

步骤4配置切换

修改 application.yml

sms:
  provider: tencent  # 切换到腾讯云

完成!无需修改调用方代码。

开发模式

模拟模式(推荐用于开发)

sms:
  enabled: false  # 关闭真实发送

特点

  • 不会实际发送短信
  • 验证码输出到日志
  • 不产生任何费用
  • 便于本地调试

真实发送模式

sms:
  enabled: true
  provider: aliyun
  access-key-id: xxx
  access-key-secret: xxx

最佳实践

1. 环境隔离

开发环境

sms:
  enabled: false  # 模拟模式

生产环境

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. 可靠性

// 系统已实现:
- 60秒发送频率限制防刷
- 10分钟验证码有效期
- Redis存储验证码
- 手机号格式验证

// 建议增加:
- 图形验证码前置
- IP限流
- 黑名单机制
- 发送失败重试

4. 监控告警

建议监控指标:

  • 短信发送成功率
  • 短信发送量(防异常消耗)
  • 验证码验证成功率
  • 单个手机号发送频率

优势总结

对比旧方案

旧方案(绑定服务商):

aliyun:
  sms:
    enabled: true
  • 配置绑定服务商
  • 切换服务商需要大量修改
  • 扩展性差

新方案(通用化):

sms:
  provider: aliyun
  enabled: true
  • 配置保持通用
  • 切换服务商只需修改 provider
  • 扩展性强,易于维护

符合设计原则

  • 开闭原则:对扩展开放,对修改关闭
  • 单一职责:每个服务商实现独立
  • 依赖倒置:调用方依赖抽象接口,不依赖具体实现
  • 里氏替换:各服务商实现可以互相替换

总结

这个架构设计的核心理念是:

  1. 配置通用化:不绑定特定服务商
  2. 实现私有化:服务商只是一个方法
  3. 接口统一化:对外提供统一接口
  4. 扩展简单化新增服务商仅需3步

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