Springboot集成腾讯云短信3.0 实现发送短信验证码和验证码校验

背景

关于Springboot集成腾讯云短信服务,百度出来的写法基本都还是停留在2.0的版本。但是腾讯官方更加推荐使用3.0版本:腾讯云短信服务Java SDK3.0

Springboot集成腾讯云短信3.0

腾讯云短信服务配置

参考:腾讯云-国内短信快速入门

①进入腾讯云控制台的短信模块

在这里插入图片描述

②创建国内短信签名

在这里插入图片描述
根据自己的需求创建签名,在签名管理中就可以看到刚刚创建的短信签名。当显示的状态变为已通过后该签名就可以使用了。
在这里插入图片描述

③创建正文模板

创建短信的模板,以发送短信验证码为例。其中短信内容部分{1}{2}等变量参数为动态参数,需要我们在代码中请求腾讯云短信服务时携带。
在这里插入图片描述
短信内容可填写:

{1}为您的验证码,请于{2}分钟内填写,如非本人操作,请忽略本短信。

创建完成模板状态显示已通过即可使用。

代码编程阶段

①引入腾讯云短信服务的Maven依赖

<dependency>
     <groupId>com.tencentcloudapi</groupId>
     <artifactId>tencentcloud-sdk-java</artifactId>
     <!-- 注:这里只是示例版本号,请获取并替换为 最新的版本号 -->
     <version>3.1.132</version>
</dependency>

注:下文中使用的很多工具类出自hutool,参考文档:https://hutool.cn/docs/#/

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.2</version>
</dependency>

②yml基础配置

下列配置全为自定义配置,这样从配置文件里读取配置,提高可扩展性和可维护性。

# 腾讯云短信服务配置
sms-config:
  # 腾讯云账户密钥对 secretId 和 secretKey
  secretId: 
  secretKey: 
  # 短信应用appId和appKey
  appId: 
  appKey: 
  # 签名
  smsSign: 
  # 过期时间
  expireTime: 10  # 10min
  # redis存储前缀
  phonePrefix: pinganky_sms
  # 模板id
  templateIds:
    code: 721446
secretId 和 secretKey配置

在腾讯云短信服务3.0需要配置secretId 和 secretKey的访问密钥(2.0则不需要),进一步提高安全性。

可以在访问管理——访问密钥——API密钥管理中配置。
在这里插入图片描述

短信应用appId和appKey

可以在短信——应用管理——应用列表中配置
在这里插入图片描述

签名smsSign

smsSign签名,即签名管理中的内容字段
在这里插入图片描述

templateIds

模板id根据业务需求配置,都是自定义。对应的是正文模板管理的ID

③编写SmsProperties类,读取yml中的配置

写Properties类的目的是直接提供JavaBean对象去获取配置,并教由Spring管理,而不是每次都是用@Value去读取(显得比较冗余)。

注:属性可根据实际情况修改。

/**
 * description: 腾讯云短信服务,配置
 *
 * Date: 2020/9/16 10:53
 **/
@Data
@Configuration
@ConfigurationProperties(prefix = "sms-config")
public class SmsProperties {

    /** 腾讯云账户密钥对secretId(在访问管理中配置) */
    private String secretId;

    /** 腾讯云账户密钥对secretKey(在访问管理中配置) */
    private String secretKey;

    /** 短信应用appId */
    private String appId;

    /** 短信应用appKey */
    private String appKey;

    /** 签名 */
    private String smsSign;

    /** 过期时间 */
    private String expireTime;

    /** redis存储前缀 */
    private String phonePrefix;

}

④提供枚举类方便操作

写枚举类的好处是方便校验,而且不允许随意更改(习惯用枚举类,会有非常多的好处)。

SmsLengthEnum 定义短信验证码长度

常用4位或6位。不允许有其他位数出现(可自定义)。

/**
 * description:
 *
 * Date: 2020/9/16 11:59
 **/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsLengthEnum {

    /** 4位短信验证码 */
    SMS_LENGTH_4(4),
    /** 6位短信验证码 */
    SMS_LENGTH_6(6),

    ;

    private int length;
}
SmsResponseCodeEnum 腾讯云短信服务返回状态码的校验

对应于腾讯云短信服务返回状态码,使用枚举进行校验,做特殊的返回状态的业务处理(可继续完善)。

/**
 * description: 腾讯云短信服务返回状态码
 *
 * Date: 2020/9/16 20:20
 **/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum SmsResponseCodeEnum {

    /**
     * 发送成功
     * 数据结构 "SendStatusSet":[{"Code":"Ok"}]
     */
    OK("Ok", "send success"),

    /*
      失败数据结构
        {
            "Response": {
                "Error": {
                    "Code": "AuthFailure.SignatureFailure",
                    "Message": "The provided credentials could not be validated. Please check your signature is
                    correct."
                },
                "RequestId": "ed93f3cb-f35e-473f-b9f3-0d451b8b79c6"
            }
        }
        详情参考:https://cloud.tencent.com/document/product/382/38780
     */

    ;
    /** 状态码 */
    private String code;

    /** 描述信息 */
    private String message;

}

⑤提供工具类

SmsCodeUtil 短信验证码工具类
/**
 * description: 腾讯云短信服务工具类
 *
 * Date: 2020/9/16 10:53
 **/
@Configuration
public class SmsCodeUtil {

    /**
     * 随机生成指定长度的短信的验证码
     *
     * @param smsLengthEnum 短信验证码长度枚举
     * @return 随机验证码
     * @author RenShiWei
     * Date: 2020/9/16 10:53
     */
    public static String createSmsRandomCode(SmsLengthEnum smsLengthEnum) {
        return RandomUtil.randomNumbers(smsLengthEnum.getLength());
    }

    /**
     * 创建短信验证码,缓存键策略
     * 策略:前缀_业务名_手机号
     *
     * @param prefix      前缀
     * @param phone       手机号
     * @param businessStr 业务名
     * @return 短信验证码,缓存键策略
     * @author RenShiWei
     * Date: 2020/9/16 10:53
     */
    public static String createSmsCacheKey(String prefix, String phone, String businessStr) {
        return prefix + "_" + businessStr + "_" + phone;
    }

}

SmsUtil 短信工具类

发送短信封装起来,方便后续更好的进行扩展。

/**
 * description:
 *
 * @author RenShiWei
 * Date: 2020/10/20 15:03
 **/
public class SmsUtil {

    /**
     * 腾讯云发送短信的基础服务
     *
     * @param smsProperties  腾讯云短信服务配置
     * @param templateId     腾讯云短信模板id
     * @param templateParams 腾讯云模板需要的参数
     * @param phoneNumbers   手机号集合
     * @return 发送短信后的状态的set集合
     */
    public static SendStatus[] sendSms(SmsProperties smsProperties, String templateId, String[] templateParams,
                                       String[] phoneNumbers) {
        /*
          实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId 和 secretKey
          CAM 密钥查询:https://console.cloud.tencent.com/cam/capi
         */
        Credential cred = new Credential(smsProperties.getSecretId(), smsProperties.getSecretKey());
        /*
          实例化要请求产品(以sms为例)的client对象
          第二个参数是地域信息,可以直接填写字符串ap-guangzhou,或者引用预设的常量
         */
        SmsClient client = new SmsClient(cred, "");
        /*
          实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
         */
        SendSmsRequest req = new SendSmsRequest();
        //短信应用ID: 短信appId在 [短信控制台] 添加应用后生成的实际SdkAppid,示例如1400006666
        req.setSmsSdkAppid(smsProperties.getAppId());
        //短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
        req.setSign(smsProperties.getSmsSign());
        //模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
        req.setTemplateID(templateId);
        //下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]  示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
        req.setPhoneNumberSet(phoneNumbers);
        //模板参数: 若无模板参数,则设置为空
        req.setTemplateParamSet(templateParams);

        /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
           返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应
         */
        SendSmsResponse res = null;
        try {
            //发送短信
            res = client.SendSms(req);
            return res.getSendStatusSet();
        } catch (TencentCloudSDKException e) {
            e.printStackTrace();
            //发送短信失败
            throw new BaseException(ResponseEnum.SMS_NOT_SEND);
        }
    }


}

⑥Redis配置

Redis相关可自行配置,目的就是缓存将验证码进行缓存,然后再进行校验,校验一次后,直接删除,使当前验证码失效(验证码可多次使用,则不用删除)。

⑦短信服务类

注:redis相关需要自行配置,不是本文重点,就不在阐述。

/**
 * description: 短信业务
 *
 * @author RenShiWei
 * Date: 2020/9/15 22:06
 **/
public interface ISmsService {

    /**
     * description: 发送短信验证码
     *
     * @param phone 手机号
     * @author RenShiWei
     * Date: 2020/9/16 10:11
     */
    void sendSmsCode(String phone);

    /**
     * description:验证短信验证码
     *
     * @param phone 手机号
     * @param code  验证码
     * @author RenShiWei
     * Date: 2020/9/16 10:11
     */
    void verifyCode(String phone, String code);

}
/**
 * description: 短信业务
 *
 * @author RenShiWei
 * Date: 2020/9/15 22:06
 **/
@Service
@Slf4j
@RequiredArgsConstructor
public class SmsServiceImpl implements ISmsService {

    private final SmsProperties smsProperties;

    private final RedisUtils redisUtils;

    /** 腾讯云短信模板id-短信验证码 */
    @Value("${sms-config.templateIds.code}")
    private String templateIdCode;

    /**
     * description: 发送短信验证码
     *
     * @param phone 手机号
     * @author RenShiWei
     * Date: 2020/9/16 10:11
     */
    @Override
    public void sendSmsCode(String phone) {
        // redis缓存的键
        final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone, "wxUser_sign");

        //下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]  示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
        String[] phoneNumbers = {"+86" + phone};
        //模板参数: 若无模板参数,则设置为空(第一个参数为随机验证码,第二个参数为有效时间)
        final String smsRandomCode = SmsCodeUtil.createSmsRandomCode(SmsLengthEnum.SMS_LENGTH_4);
        String[] templateParams = {smsRandomCode,
                smsProperties.getExpireTime()};

        SendStatus[] sendStatuses = SmsUtil.sendSms(smsProperties, templateIdCode, templateParams, phoneNumbers);
        String resCode = sendStatuses[0].getCode();
        if (resCode.equals(SmsResponseCodeEnum.OK.getCode())) {
            // 返回ok,缓存
            redisUtils.set(smsKey, smsRandomCode, Long.parseLong(smsProperties.getExpireTime()), TimeUnit.MINUTES);
        } else {
            log.error("【短信业务-发送失败】phone:" + phone + "errMsg:" + sendStatuses[0].getMessage());
            throw new BaseException(ResponseEnum.SMS_NOT_SEND);
        }

    }

    /**
     * description:验证短信验证码
     *
     * @param phone 手机号
     * @param code  验证码
     * @author RenShiWei
     * Date: 2020/9/16 10:11
     */
    @Override
    public void verifyCode(String phone, String code) {
        // redis缓存的键
        final String smsKey = SmsCodeUtil.createSmsCacheKey(smsProperties.getPhonePrefix(), phone,
                "wxUser_sign");
        // 如果key存在(存在并且未过期)
        if (redisUtils.hasKey(smsKey)) {
            String cacheCode = redisUtils.get(smsKey).toString();
            if (cacheCode.equals(code)) {
                //验证码正确
                //删除验证码缓存
                redisUtils.del(smsKey);
                log.info("【短信业务-微信公众号手机认证成功】phone:" + phone);
            } else {
                //验证码不正确
                log.error("【短信业务-微信公众号手机认证失败】验证码错误。phone:" + phone);
                throw new BaseException(ResponseEnum.SMS_CODE_VERITY_FAIL);
            }
        } else {
            log.error("【短信业务-微信公众号手机认证失败】验证码失效。phone:" + phone);
            throw new BaseException(ResponseEnum.SMS_CODE_EXPIRE);
        }
    }

}

⑧Controller接口

Controller自定编写,调用Service进行业务处理即可。

可编写Springboot的测试类,或者使用接口调用进行测试

/**
 * description: 测试腾讯云SDK3.0
 *
 * Date: 2020/9/16 9:10
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class SmsTest {

	@Autowired
    private ISmsService smsService;

	@Test
    public void testSendCode() {
        smsService.sendSmsCode("15611111111");
    }
    
}

至此完成!

  • 5
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值