当前位置: 首页 > news >正文

阿里云短信发送(工厂模式实现)

文章目录

  • 一、业务场景
    • 1.1 验证码登录
    • 1.2 手机号注册
    • 1.3 忘记密码
    • 1.4 其他场景.....
  • 二. 准备工作
    • 1.注册阿里云账号
    • 2.阿里云申请签名
    • 3.阿里云短信模版
    • 4.阿里云短信控制台测试
  • 三、功能规划
    • 1. 工厂模式根据不同业务进行校验,提高代码的可扩展性
    • 2.不同业务类型使用不同的缓存key
  • 四、业务实现
    • 1.引入jar
    • 2.yam配置信息及对应配置类
    • 3. 业务工厂: 获取对应业务模版
    • 4. 发送短信模版:用来发送短信
    • 5. 业务模版:用来校验
      • 5.1 登录
      • 5.2 注册
      • 5.3 忘记密码
    • 6.短信缓存redis
    • 7. 请求入口:
    • 8. 参数:SmsReqAo


提示:以下是本篇文章正文内容,下面案例可供参考

一、业务场景

每种业务场景对手机号校验方式可能都不相同

1.1 验证码登录

校验:手机号在系统是否存在,存在则发送短信

1.2 手机号注册

校验:手机号在系统是否已经注册,未注册则发送短信

1.3 忘记密码

校验:手机号在系统是否存在,存在则发送短信

1.4 其他场景…

二. 准备工作

1.注册阿里云账号

2.阿里云申请签名

3.阿里云短信模版

4.阿里云短信控制台测试

三、功能规划

1. 工厂模式根据不同业务进行校验,提高代码的可扩展性

2.不同业务类型使用不同的缓存key

四、业务实现

在这里插入图片描述

1.引入jar

		<dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>3.1.0</version>
        </dependency>

2.yam配置信息及对应配置类

在这里插入图片描述

@Configuration
@ConfigurationProperties(prefix = "aliyun.sms")
@Data
public class AliyunSmsConfig {

    // 阿里云短信服务accessKeyId
    private String accessKeyId;

    // 阿里云短信服务accessKeySecret
    private String accessKeySecret;

    // 短信签名
    private String signName;

    // 短信发送地区
    private String regionId;

    // 短信模板
    private Map<String, String> templates;

    public String getTemplateCode(String key) {
        String templateCode = templates.get(key);
        ArgumentAssert.notBlank(templateCode, "短信模板不存在");
        return templateCode;
    }
}

3. 业务工厂: 获取对应业务模版


@Service
@AllArgsConstructor
@Slf4j
public class SmsTemplateFactory {

    private final LoginSmsTemplate loginSmsTemplate;

    private final RegisterSmsTemplate registerSmsTemplate;

    private final ForgotPasswordSmsTemplate forgotPasswordSmsTemplate;

    /**
     * 根据场景类型获取对应的模板
     *
     * @param templateKey 场景类型(login/register/forgotPassword)
     * @return 对应的模板实现类
     */
    public SmsTemplate getTemplate(String templateKey) {
        return switch (templateKey) {
            case "login" -> loginSmsTemplate;
            case "register" -> registerSmsTemplate;
            case "forgotPassword" -> forgotPasswordSmsTemplate;
            default -> throw new IllegalArgumentException("未知的场景类型:" + templateKey);
        };
    }
}

4. 发送短信模版:用来发送短信


@Slf4j
public abstract class SmsTemplate {

    protected abstract void checkBiz(String phoneNumber);

    /**
     * 发送短信验证码
     *
     * @param templateKey 模板key
     * @param phoneNumber 手机号
     * @return 是否发送成功
     */
    public boolean sendSms(String templateKey, String phoneNumber) {
        checkBiz(phoneNumber);

        SmsCodeCache smsCodeCache = SpringUtils.getBean(SmsCodeCache.class);

        // 检查发送频率(60秒内只能发送一次)
        if (!smsCodeCache.checkRateLimit(phoneNumber, 60)) {
            throw new RuntimeException("发送频率过高,请稍后再试");
        }

        String code = this.generateCode(); // 生成验证码
        boolean isSuccess = this.sendSmsToAliyun(templateKey, phoneNumber, code); // 调用阿里云发送短信

        if (isSuccess) {
            // 缓存验证码,有效期5分钟
            smsCodeCache.cacheCode(templateKey, phoneNumber, code, 5);
        }

        return isSuccess;
    }

    /**
     * 调用阿里云短信服务发送短信
     *
     * @param templateKey 模板key
     * @param phoneNumber 手机号
     * @param code        验证码
     * @return 是否发送成功
     */
    private boolean sendSmsToAliyun(String templateKey, String phoneNumber, String code) {
        AliyunSmsConfig smsConfig = SpringUtils.getBean(AliyunSmsConfig.class);
        String templateCode = smsConfig.getTemplateCode(templateKey);
        try {
            // 初始化客户端
            Config config = new Config()
                    .setAccessKeyId(smsConfig.getAccessKeyId())
                    .setAccessKeySecret(smsConfig.getAccessKeySecret())
                    .setRegionId(smsConfig.getRegionId());
            Client client = new Client(config);

            // 构建请求
            SendSmsRequest request = new SendSmsRequest()
                    .setPhoneNumbers(phoneNumber)
                    .setSignName(smsConfig.getSignName())
                    .setTemplateCode(templateCode)
                    .setTemplateParam("{\"code\":\"" + code + "\"}");

            // 发送短信
            SendSmsResponse response = client.sendSms(request);
            return "OK".equals(response.getBody().getCode());
        } catch (Exception e) {
            log.error("发送短信验证码失败,模版:{},手机号:{},验证码:{},错误:{}", templateKey, phoneNumber, code, e.getMessage());
            return false;
        }
    }

    /**
     * 生成6位随机验证码
     *
     * @return 验证码
     */
    private String generateCode() {
        Random random = new Random();
        return String.format("%06d", random.nextInt(1000000));
    }
}

5. 业务模版:用来校验

5.1 登录

@Component
@AllArgsConstructor
public class LoginSmsTemplate extends SmsTemplate {

    private final BaseStaffService baseStaffService;

    @Override
    public void checkBiz(String phoneNumber) {
        BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
        ArgumentAssert.notNull(staff, "该手机号不存在");
    }
}


5.2 注册

@Component
@AllArgsConstructor
public class RegisterSmsTemplate extends SmsTemplate {

    private final BaseStaffService baseStaffService;

    @Override
    public void checkBiz(String phoneNumber) {
        BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
        ArgumentAssert.isNull(staff, "该手机号已注册");
    }
}

5.3 忘记密码

@Component
@AllArgsConstructor
public class ForgotPasswordSmsTemplate extends SmsTemplate {

    private final BaseStaffService baseStaffService;
    @Override
    public void checkBiz(String phoneNumber) {
        BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
        ArgumentAssert.notNull(staff, "该手机号不存在");
    }
}


6.短信缓存redis


@Component
@AllArgsConstructor
public class SmsCodeCache {

    private final RedisTemplate<String, String> redisTemplate;

    // 验证码缓存前缀
    private static final String CODE_PREFIX = "sms:code:";
    // 发送频率限制前缀
    private static final String RATE_LIMIT_PREFIX = "sms:rate:";

    /**
     * 缓存验证码
     *
     * @param templateKey 模板key
     * @param phoneNumber 手机号
     * @param code        验证码
     * @param expireTime  过期时间(分钟)
     */
    public void cacheCode(String templateKey, String phoneNumber, String code, long expireTime) {
        String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
        redisTemplate.opsForValue().set(key, code, expireTime, TimeUnit.MINUTES);
    }

    /**
     * 获取缓存的验证码
     *
     * @param templateKey 模板key
     * @param phoneNumber 手机号
     * @return 验证码
     */
    public String getCachedCode(String templateKey, String phoneNumber) {
        String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除缓存的验证码
     *
     * @param templateKey 模板key
     * @param phoneNumber 手机号
     */
    public void deleteCode(String templateKey, String phoneNumber) {
        String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
        redisTemplate.delete(key);
    }

    /**
     * 检查发送频率
     *
     * @param phoneNumber 手机号
     * @param interval    间隔时间(秒)
     * @return 是否可以发送
     */
    public boolean checkRateLimit(String phoneNumber, long interval) {
        String key = RATE_LIMIT_PREFIX + phoneNumber;
        Long lastSentTime = redisTemplate.opsForValue().getOperations().getExpire(key);
        if (lastSentTime != null && lastSentTime > 0) {
            return false; // 还在限制时间内
        }
        redisTemplate.opsForValue().set(key, "1", interval, TimeUnit.SECONDS);
        return true;
    }
}

7. 请求入口:

@PostMapping("getSmsCode")
    @ApiOperation(value = "获取短信验证码")
    public DataResponse<Boolean> getSmsCode(@RequestBody @Validated SmsReqAo ao) {
        String templateKey = ao.getTemplateKey();
        return DataResponse.builderSuccess(smsTemplateFactory.getTemplate(templateKey).sendSms(templateKey, ao.getTelephone()));
    }

8. 参数:SmsReqAo

@Data
@ApiModel("短信求入参")
public class SmsReqAo implements Serializable {

    private static final long serialVersionUID = 1L;

    @NotBlank(message = "模版KEY不能为空")
    @ApiModelProperty(value = "模版KEY", required = true)
    String templateKey;

    /**
     * 手机号
     */
    @ApiModelProperty(value = "手机号", required = true)
    @NotBlank(message = "手机号不能为为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String telephone;
}

相关文章:

  • C++:二分习题
  • never_give_up
  • 【C++ 系列文章 基础 01 -- std::string 与 fmt::format】
  • Java线程安全
  • Vue3 深度解析:构建现代Web应用的全新范式
  • 【PCIe 总线及设备入门学习专栏 3 -- PCIe 三种路由方式详细介绍】
  • 淘晶驰 屏幕 应用 之 esp8266/arduino 简约时钟 2025/3/12
  • sql靶场-时间盲注(第九、十关)保姆级教程
  • Trae AI IDEA安装与使用
  • 【机器学习】主成分分析法(PCA)
  • 数组总和 (leetcode 40
  • MySql索引下推(ICP)是什么?有什么用?
  • logback希望特定的error日志写入到特定文件
  • Qt/C++音视频开发82-系统音量值获取和设置/音量大小/静音
  • leetcode_字符串 49. 字母异位词分组
  • DeepSeek赋能智慧环保:为环境资源保护提供决策支持,开启绿色智能新时代
  • MAVEN解决版本依赖冲突
  • Mybatis 注解(详细版)
  • UE小:UE5.5 PixelStreamingInfrastructure 使用时注意事项
  • 15 | 定义简洁架构 Store 层的数据类型
  • 国家统计局向多省份反馈统计督察意见
  • 万科再获深铁集团借款,今年已累计获股东借款近120亿元
  • 陕西旱情实探:大型灌区农业供水有保障,大旱之年无旱象
  • 白玉兰奖征片综述丨动画的IP生命力
  • 法治日报整版聚焦:儿童能否成为短视频主角?该如何监管?
  • 中保协发布《保险机构适老服务规范》,全面规范保险机构面向老年人提供服务的统一标准