1.引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 腾讯云 Java SDK 依赖 -->
<dependency>
    <groupId>com.tencentcloudapi</groupId>
    <artifactId>tencentcloud-sdk-java</artifactId>
    <version>3.1.297</version>
</dependency>
<!-- Spring Data Redis 起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lombok插件 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

2.编写配置类

用于读取配置文件中的自定义腾讯云短信配置的配置类:

package com.jinghang.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.config.SmsConfig
 * @description 腾讯云短信配置类
 * @date 2021/6/25 16:21
 */
@ConfigurationProperties(prefix = "tencent.sms")
@Configuration
@Data
public class SmsConfig {
    /**
     * 腾讯云API密钥的SecretId
     */
    private String secretId;
    /**
     * 腾讯云API密钥的SecretKey
     */
    private String secretKey;
    /**
     * 短信应用的SDKAppID
     */
    private String appId;
    /**
     * 签名内容
     */
    private String sign;
    /**
     * 模板ID
     */
    private String templateId;
    /**
     * 过期时间
     */
    private String expireTime;
    /**
     * redis存储的key的前缀
     */
    private String phonePrefix;
}

Redis配置类:

package com.jinghang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.config.RedisConfig
 * @description Redis配置类
 * @date 2021/6/26 12:24
 */
@Configuration
public class RedisConfig {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * RedisTemplate实例
     *
     * @return RedisTemplate实例
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initRedisTemplate(redisTemplate, redisConnectionFactory);
        return redisTemplate;
    }

    /**
     * 设置数据存入redis的序列化方式
     *
     * @param redisTemplate RedisTemplate对象
     * @param factory       RedisConnectionFactory对象
     */
    private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
    }
}

3.编写配置文件

spring:
  redis:
    host: 127.0.0.1
    port: 6379

# 自定义腾讯云短信配置
tencent:
  sms:
    # 配置腾讯云API密钥的SecretId,在访问管理菜单的访问密钥下的API密钥管理中可以新建和查看API密钥,在请求腾讯云短信服务发送短信时需要传入该密钥
    secretId: 这里填腾讯云API密钥的SecretId
    # 配置腾讯云API密钥的SecretKey,在访问管理菜单的访问密钥下的API密钥管理中可以新建和查看API密钥,在请求腾讯云短信服务发送短信时需要传入该密钥
    secretKey: 这里填腾讯云API密钥的SecretKey
    # 配置短信应用的SDKAppID
    appId: 这里填短信应用的SDKAppID
    # 配置签名内容
    sign: "这里填签名内容"
    # 配置模板ID
    templateId: 这里填模板ID
    # 配置过期时间
    expireTime: 5
    # 配置redis存储的key的前缀
    phonePrefix: REGIST

4.编写工具类

腾讯云短信工具类:

package com.jinghang.util;

import com.jinghang.config.SmsConfig;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.util.SmsUtil
 * @description 腾讯云短信工具类
 * @date 2021/6/25 16:21
 */
public class SmsUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsUtil.class);

    /**
     * 发送短信
     *
     * @param smsConfig      腾讯云短信配置对象
     * @param templateParams 模板参数
     * @param phoneNumbers   手机号数组
     * @return SendStatus[],短信发送状态
     */
    public static SendStatus[] sendSms(SmsConfig smsConfig, String[] templateParams, String[] phoneNumbers) {
        try {
            // 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
            Credential cred = new Credential(smsConfig.getSecretId(), smsConfig.getSecretKey());
            // 实例化一个http选项,可选,没有特殊需求可以跳过
            HttpProfile httpProfile = new HttpProfile();
            // SDK默认使用POST方法
            httpProfile.setReqMethod("POST");
            // SDK有默认的超时时间,非必要请不要进行调整
            httpProfile.setConnTimeout(60);
            // 非必要步骤:实例化一个客户端配置对象,可以指定超时时间等配置
            ClientProfile clientProfile = new ClientProfile();
            // SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要修改这个字段
            clientProfile.setSignMethod("HmacSHA256");
            clientProfile.setHttpProfile(httpProfile);
            // 实例化要请求产品(以sms为例)的client对象,第二个参数是地域信息,可以直接填写字符串ap-guangzhou,或者引用预设的常量
            SmsClient smsClient = new SmsClient(cred, "ap-guangzhou", clientProfile);
            // 实例化一个请求对象
            SendSmsRequest req = new SendSmsRequest();
            // 设置短信应用ID:短信SdkAppId在[短信控制台]添加应用后生成的实际SdkAppId
            req.setSmsSdkAppId(smsConfig.getAppId());
            // 设置短信签名内容:使用UTF-8编码,必须填写已审核通过的签名,签名信息可登录[短信控制台]查看
            req.setSignName(smsConfig.getSign());
            // 设置国际/港澳台短信SenderId:国内短信填空,默认未开通
            req.setSenderId("");
            // 设置模板ID:必须填写已审核通过的模板ID。模板ID可登录[短信控制台]查看
            req.setTemplateId(smsConfig.getTemplateId());
            // 设置下发手机号码,采用E.164标准,+[国家或地区码][手机号]
            req.setPhoneNumberSet(phoneNumbers);
            // 设置模板参数:若无模板参数,则设置为空
            req.setTemplateParamSet(templateParams);
            // 通过client对象调用SendSms方法发起请求。注意请求方法名与请求对象是对应的,返回的res是一个SendSmsResponse类的实例,与请求对象对应
            SendSmsResponse res = smsClient.SendSms(req);
            // 控制台打印日志输出json格式的字符串回包
            LOGGER.info(SendSmsResponse.toJsonString(res));
            return res.getSendStatusSet();
        } catch (TencentCloudSDKException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
}

Redis工具类:

package com.jinghang.util;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.util.RedisUtil
 * @description Redis工具类
 * @date 2021/6/26 12:24
 */
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 缓存基本对象
     *
     * @param key    键
     * @param value  值
     * @param expire 键的过期时间
     */
    public void setCacheObject(String key, Object value, long expire) {
        redisTemplate.opsForValue().set(key, value);
        if (expire > 0) {
            redisTemplate.expire(key, expire, TimeUnit.MINUTES);
        }
    }

    /**
     * 获取指定键的缓存对象
     *
     * @param key 键
     * @return 缓存对象
     */
    public Object getCacheObject(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 判断键是否存在并且未过期
     *
     * @param key 键
     * @return true,键存在并且未过期;false,键不存在或存在但过期
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key) && getExpire(key) > 0 ? true : false;
    }

    /**
     * 获取键的过期时间
     *
     * @param key 键
     * @return 过期时间
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.MINUTES);
    }

    /**
     * 删除指定键的缓存
     *
     * @param key 键
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 创建缓存的键
     *
     * @param prefix      前缀
     * @param phoneNumber 手机号
     * @return 键
     */
    public static String createCacheKey(String prefix, String phoneNumber) {
        return prefix + phoneNumber;
    }
}

package com.jinghang.util;

import java.util.Random;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.util.RandomUtil
 * @description Random工具类
 * @date 2021/6/26 11:39
 */
public class RandomUtil {

    private static final Random RANDOM = new Random();

    /**
     * 生成指定位数的随机数字字符串
     *
     * @param length 字符串长度
     * @return 随机数字字符串
     */
    public static String randomNumbers(int length) {
        StringBuilder randomNumbers = new StringBuilder();
        for (int i = 0; i < length; i++) {
            randomNumbers.append(RANDOM.nextInt(10));
        }
        return randomNumbers.toString();
    }
}

5.Controller层

package com.jinghang.controller;

import com.jinghang.service.SmsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.controller.SmsController
 * @description SmsController
 * @date 2021/6/25 16:20
 */
@RequestMapping("/sms")
@RestController
public class SmsController {

    @Resource
    private SmsService smsService;

    @PostMapping("/send")
    public String sendSmsCode(@RequestParam(value = "phoneNumber") String phoneNumber) {
        return smsService.sendSmsCode(phoneNumber);
    }

    @PostMapping("/verify")
    public String verifySmsCode(@RequestParam(value = "phoneNumber") String phoneNumber,
        @RequestParam(value = "smsCode") String smsCode) {
        return smsService.verifySmsCode(phoneNumber, smsCode);
    }
}

6.Service层

package com.jinghang.service;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.service.SmsService
 * @description SmsService
 * @date 2021/6/25 16:20
 */
public interface SmsService {

    /**
     * 发送短信验证码
     *
     * @param phoneNumber 手机号
     * @return
     */
    String sendSmsCode(String phoneNumber);

    /**
     * 验证短信验证码
     *
     * @param phoneNumber 手机号
     * @param smsCode     短信验证码
     * @return
     */
    String verifySmsCode(String phoneNumber, String smsCode);
}

package com.jinghang.service.impl;

import com.jinghang.config.SmsConfig;
import com.jinghang.service.SmsService;
import com.jinghang.util.RandomUtil;
import com.jinghang.util.RedisUtil;
import com.jinghang.util.SmsUtil;
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author jinghang
 * @version 1.0.0
 * @name com.jinghang.service.impl.SmsServiceImpl
 * @description SmsService实现类
 * @date 2021/6/25 16:20
 */
@Service
public class SmsServiceImpl implements SmsService {

    @Resource
    private SmsConfig smsConfig;
    @Resource
    private RedisUtil redisUtil;

    @Override
    public String sendSmsCode(String phoneNumber) {
        // 下发手机号码,采用e.164标准,+[国家或地区码][手机号]
        String[] phoneNumbers = {"+86" + phoneNumber};
        // 生成6位随机数字字符串
        String smsCode = RandomUtil.randomNumbers(6);
        // 模板参数:若无模板参数,则设置为空(参数1为随机验证码,参数2为有效时间)
        String[] templateParams = {smsCode, smsConfig.getExpireTime()};
        // 发送短信验证码
        SendStatus[] sendStatuses = SmsUtil.sendSms(smsConfig, templateParams, phoneNumbers);
        if ("Ok".equals(sendStatuses[0].getCode())) {
            // 创建缓存的key
            String key = RedisUtil.createCacheKey(smsConfig.getPhonePrefix(), phoneNumber);
            // 将验证码缓存到redis并设置过期时间
            redisUtil.setCacheObject(key, smsCode, Long.parseLong(smsConfig.getExpireTime()));
            return "验证码发送成功";
        } else {
            return "验证码发送失败:" + sendStatuses[0].getMessage();
        }
    }

    @Override
    public String verifySmsCode(String phoneNumber, String smsCode) {
        // 创建key
        String key = RedisUtil.createCacheKey(smsConfig.getPhonePrefix(), phoneNumber);
        // 判断指定key是否存在并且未过期
        if (redisUtil.hasKey(key)) {
            // 验证输入的验证码是否正确
            if (smsCode.equals(redisUtil.getCacheObject(key))) {
                // 验证成功后删除验证码缓存
                redisUtil.delete(key);
                return "验证成功";
            } else {
                return "验证码错误";
            }
        } else {
            return "验证码已失效";
        }
    }
}

Q.E.D.





莫道君行早,更有早行人。