1. 接入阿里云短信服务

登录阿里云服务器官网,然后点击搜索输入短信服务,点击免费开通

点击快速学习与测试

测试用的所以点击专用测试签名模板,并点击调用 API 测试

2. 添加阿里云 SDK

然后进入到另一个页面后,点击 SDK 详情,这里使用的是 SpringBoot,所以点击 java 实例

在旁边有个 SDK,点击进去,就可以看到你们的 maven 情况,然后把这个 maven 依赖引入到 SpringBoot 项目中的 pom.xml 文件中

// 省略...

<dependencies>
  // 省略...

  <!-- 阿里云短信发送 -->
  <dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>dysmsapi20170525</artifactId>
  </dependency>
</dependencies>

// 省略...

3. 添加 AccessKey

查看发送短信的示例代码,你会发现需要填写阿里云的 Access Key,它是接入凭证。点击回到阿里云首页,将鼠标移动到登录用户的头像上,即可看到 AccessKey 选项,点击即可查看:

记得给你的账号充值一点钱,比如 1 块钱,因为等会发送测试短信需要费用。

然后编辑你的 application.yml文件,把这些 accesskey填写进去

aliyun:# 接入阿里云(发送短信使用)
  accessKeyId:xxx# 填写你自己的
  accessKeySecret:xxx# 填写你自己的

写个配置类来读取yml中的配置信息,AliyunAccessKeyProperties 配置类

@ConfigurationProperties(prefix = "aliyun")
@Component
@Data
public class AliyunAccessKeyProperties {
    private String accessKeyId;
    private String accessKeySecret;
}

然后,新建 AliyunSmsClientConfig 配置类,用于初始化一个短信发送客户端,注入到 Spring 容器中,以便后续使用,这里对官方的实例代码进行了改造:

@Configuration
@Slf4j
public class AliyunSmsClientConfig {

    @Resource
    private AliyunAccessKeyProperties aliyunAccessKeyProperties;

    @Bean
    public Client smsClient() {
        try {
            Config config = new Config()
                    // 必填
                    .setAccessKeyId(aliyunAccessKeyProperties.getAccessKeyId())
                    // 必填
                    .setAccessKeySecret(aliyunAccessKeyProperties.getAccessKeySecret());

            // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
            config.endpoint = "dysmsapi.aliyuncs.com";

            return new Client(config);
        } catch (Exception e) {
            log.error("初始化阿里云短信发送客户端错误: ", e);
            return null;
        }
    }
}

最后,再创建一个 AliyunSmsHelper 短信发送工具类,代码如下:

@Component
@Slf4j
public class AliyunSmsHelper {

    @Resource
    private Client client;

    /**
     * 发送短信
     * @param signName
     * @param templateCode
     * @param phone
     * @param templateParam
     * @return
     */
    public boolean sendMessage(String signName, String templateCode, String phone, String templateParam) {
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
                .setSignName(signName)
                .setTemplateCode(templateCode)
                .setPhoneNumbers(phone)
                .setTemplateParam(templateParam);
        RuntimeOptions runtime = new RuntimeOptions();

        try {
            log.info("==> 开始短信发送, phone: {}, signName: {}, templateCode: {}, templateParam: {}", phone, signName, templateCode, templateParam);

            // 发送短信
            SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, runtime);

            log.info("==> 短信发送成功, response: {}", JsonUtils.toJsonString(response));
            return true;
        } catch (Exception error) {
            log.error("==> 短信发送错误: ", error);
            return false;
        }
    }
}

4. 业务层异步发送短信

一般情况下,在用户调用发送短信接口后,在业务代码不出错的情况下都会先提示验证码已经发送成功,所以一般都会先返回信息,然后用多线程中的异步去给用户发送短信

1、多线程的自定义


@Configuration
public class ThreadPoolConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(50);
        // 队列容量
        executor.setQueueCapacity(200);
        // 线程活跃时间(秒)
        executor.setKeepAliveSeconds(30);
        // 线程名前缀
        executor.setThreadNamePrefix("AuthExecutor-");

        // 拒绝策略:由调用线程处理(一般为主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
        executor.setAwaitTerminationSeconds(60);

        executor.initialize();
        return executor;
    }
}

2、异步发送短信,这里未来自适应我的项目中的短信登录接口,所以额外加了一些逻辑判断

/**
     * 发送短信验证码
     *
     * @param sendVerificationCodeReqVO
     * @return
     */
    @Override
    public Response<?> send(SendVerificationCodeReqVO sendVerificationCodeReqVO) {
        // 手机号
        String phone = sendVerificationCodeReqVO.getPhone();

        // 构建验证码 redis key
        String key = RedisKeyConstants.buildVerificationCodeKey(phone);

        // 判断是否已发送验证码
        boolean isSent = redisTemplate.hasKey(key);
        if (isSent) {
            // 若之前发送的验证码未过期,则提示发送频繁
            throw new BizException(ResponseCodeEnum.VERIFICATION_CODE_SEND_FREQUENTLY);
        }

        // 生成 6 位随机数字验证码
        String verificationCode = RandomUtil.randomNumbers(6);

        log.info("==> 手机号: {}, 已生成验证码:【{}】", phone, verificationCode);

        // 调用第三方短信发送服务
        threadPoolTaskExecutor.submit(() -> {
            String signName = "阿里云短信测试";
            String templateCode = "SMS_154950909";
            String templateParam = String.format("{"code":"%s"}", verificationCode);
            aliyunSmsHelper.sendMessage(signName, templateCode, phone, templateParam);
        });

        // 存储验证码到 redis, 并设置过期时间为 3 分钟
        redisTemplate.opsForValue().set(key, verificationCode, 3, TimeUnit.MINUTES);

        return Response.success();
    }