SpringBoot项目中快速集成腾讯云短信SDK实现手机验证码功能

大家春节好!我是程序员阿福,今天过年的日子祝大家在新的一年里健康平安、步步高升、虎年大吉大利、财源滚滚! 今天分享一篇简短一点的文章,希望在将来工作中需要的时候能够用得到,如果将来工作中需要实现短信验证码功能时可以打开我的公众号并翻到这篇文章再仔细参考我的实现思路,那么笔者分享这篇文章的用意也就达到了。

前言

几乎每一个新项目中都会涉及到手机验证码的动能,用户登录采用手机验证码登录方式、用户忘记密码需要密码修改密码时以及用户进行支付确认时也需要用到手机验证码。可以说手机验证码在各种项目中用的非常多,因此在我们的项目中集成一个短信通知服务是非常有必要的。

笔者为啥选择了腾讯云短信服务?市面上的短信服务其实是非常多的,包括阿里云短信服务、七牛云短信服务一起其他各大短信服务厂商等等。一是因为笔者平时买的腾讯云产品比较多,包括我的云服务器也是买的腾讯云产品,对腾讯云的产品比较熟悉,用起来也容易上手;二是发现腾讯云的产品相比较阿里的产品要更便宜些,购买金额的门槛也要低一些,50元就可以购买1000条短信服务,而阿里的短信服务购买金额门槛要100元以上。对于个人开发者而言1000条短信服务以及足够了。最后就是发现腾讯云短信服务的SDK API简单易用,各个版本客户端语言的示例都非常详细,跟着示例一步一步来很容易就能实现自己的需求。

废话不多说,直接介绍在我们的项目集成短信通知服务的详细步骤与演示代码。

腾讯云短信服务SDK

SDK 3.0是云 API 3.0平台的配套工具,您可以通过 SDK 使用所有 短信 API[1]。新版 SDK 实现了统一化,具有各个语言版本的 SDK 使用方法相同,接口调用方式相同,错误码相同以及返回包格式相同等优点。

前提条件

  • 已开通短信服务,具体操作请参见 国内短信快速入门[2]
  • 如需发送国内短信,需要先 购买国内短信套餐包[3]
  • 已准备依赖环境:JDK 7 及以上版本。
  • 已在访问管理控制台 >API密钥管理页面获取 SecretID 和 SecretKey。
    • SecretID 用于标识 API 调用者的身份。
    • SecretKey 用于加密签名字符串和服务器端验证签名字符串的密钥,SecretKey 需妥善保管,避免泄露
  • 短信的调用地址为sms.tencentcloudapi.com

相关资料

  • 各个接口及其参数的详细介绍请参见 API 文档[4]
  • 下载 SDK 源码请访问 Java SDK 源码[5]

安装 SDK

通过Maven安装

Maven是 Java 的依赖管理工具,支持您项目所需的依赖项,并将其安装到项目中。

1 访问 Maven 官网[6] 下载对应系统 Maven 安装包进行安装

2 添加 Maven 依赖项,只需在 Maven pom.xml 添加以下依赖项即可:

代码语言:javascript
复制
<dependency>
     <groupId>com.tencentcloudapi</groupId>
     <artifactId>tencentcloud-sdk-java</artifactId>
     <version>3.1.270</version><!-- 注:这里只是示例版本号(可直接使用),可获取并替换为 最新的版本号,注意不要使用4.0.x版本(非最新版本) -->
</dependency>

注意事项:

1 版本号仅为示例,请在 Maven 仓库[7]获取最新的版本号并替换

2 Maven 仓库中显示的 4.0.11 是废弃版本,由于 Maven 索引更新问题尚未完全删除

通过源码包安装

1 下载源码压缩包[8]

2 解压源码包到您项目中合适的位置

3 将 vendor 目录下的 jar 包放在 Java 可找到的路径中

4 引用方法可参考 示例代码[9]

示例代码

说明:所有示例代码仅作参考,无法直接编译和运行,需根据实际情况进行修改,您也可以根据实际需求使用 API 3.0 Explorer[10]自动化生成 Demo 代码。

每个接口都有一个对应的 Request 结构和一个 Response 结构。本文仅列举几个常用功能的示例代码,如下所示。

这里我们只展示发送短信的示例代码

代码语言:javascript
复制
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;

//导入可选配置类
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;

// 导入对应SMS模块的client
import com.tencentcloudapi.sms.v20210111.SmsClient;

// 导入要请求接口对应的request response类
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;

/**

  • Tencent Cloud Sms Sendsms

/
public class SendSms
{
public static void main(String[] args)
{
try {
/
必要步骤:
* 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及你的财产安全。
* CAM密匙查询: https://console.cloud.tencent.com/cam/capi*/
Credential cred = new Credential("secretId", "secretKey");

        // 实例化一个http选项,可选,没有特殊需求可以跳过
        HttpProfile httpProfile = new HttpProfile();
        // 设置代理
        // httpProfile.setProxyHost(&#34;真实代理ip&#34;);
        // httpProfile.setProxyPort(真实代理端口);
        /* SDK默认使用POST方法。
         * 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
        httpProfile.setReqMethod(&#34;POST&#34;);
        /* SDK有默认的超时时间,非必要请不要进行调整
         * 如有需要请在代码中查阅以获取最新的默认值 */
        httpProfile.setConnTimeout(60);
        /* SDK会自动指定域名。通常是不需要特地指定域名的,但是如果你访问的是金融区的服务
         * 则必须手动指定域名,例如sms的上海金融区域名: sms.ap-shanghai-fsi.tencentcloudapi.com */
        httpProfile.setEndpoint(&#34;sms.tencentcloudapi.com&#34;);

        /* 非必要步骤:
         * 实例化一个客户端配置对象,可以指定超时时间等配置 */
        ClientProfile clientProfile = new ClientProfile();
        /* SDK默认用TC3-HMAC-SHA256进行签名
         * 非必要请不要修改这个字段 */
        clientProfile.setSignMethod(&#34;HmacSHA256&#34;);
        clientProfile.setHttpProfile(httpProfile);
        /* 实例化要请求产品(以sms为例)的client对象
         * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,或者引用预设的常量 */
        SmsClient client = new SmsClient(cred, &#34;ap-guangzhou&#34;,clientProfile);
        /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
         * 你可以直接查询SDK源码确定接口有哪些属性可以设置
         * 属性可能是基本类型,也可能引用了另一个数据结构
         * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
        SendSmsRequest req = new SendSmsRequest();

        /* 填充请求参数,这里request对象的成员变量即对应接口的入参
         * 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义
         * 基本类型的设置:
         * 帮助链接:
         * 短信控制台: https://console.cloud.tencent.com/smsv2
         * sms helper: https://cloud.tencent.com/document/product/382/3773 */

        /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
        String sdkAppId = &#34;1400009099&#34;;
        req.setSmsSdkAppId(sdkAppId);

        /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看 */
        String signName = &#34;签名内容&#34;;
        req.setSignName(signName);

        /* 国际/港澳台短信 SenderId: 国内短信填空,默认未开通,如需开通请联系 [sms helper] */
        String senderid = &#34;&#34;;
        req.setSenderId(senderid);

        /* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
        String sessionContext = &#34;xxx&#34;;
        req.setSessionContext(sessionContext);

        /* 短信号码扩展号: 默认未开通,如需开通请联系 [sms helper] */
        String extendCode = &#34;&#34;;
        req.setExtendCode(extendCode);

        /* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */
        String templateId = &#34;400000&#34;;
        req.setTemplateId(templateId);

        /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
         * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */
        String[] phoneNumberSet = {&#34;+8621212313123&#34;, &#34;+8612345678902&#34;, &#34;+8612345678903&#34;};
        req.setPhoneNumberSet(phoneNumberSet);

        /* 模板参数: 若无模板参数,则设置为空 */
        String[] templateParamSet = {&#34;5678&#34;};
        req.setTemplateParamSet(templateParamSet);

        /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
         * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */
        SendSmsResponse res = client.SendSms(req);

        // 输出json格式的字符串回包
        System.out.println(SendSmsResponse.toJsonString(res));

        // 也可以取出单个值,你可以通过官网接口文档或跳转到response对象的定义处查看返回字段的定义
        System.out.println(res.getRequestId());

    } catch (TencentCloudSDKException e) {
        e.printStackTrace();
    }
}

}

SpringBoot项目中集成短信服务

笔者的spring-boot项目用的是2.2.7.RELEASE版本,是之前作者【江南一点雨】开源的一个spring-boot项目叫blog-server。笔者在这个开源项目的基础上进行了二次开发。手机验证码一般具有一定的时效性,过期后就会失效。我们可以借助redis缓存来存储短信验证码,并设置过期时间。等到服务端需要对用户请求里带上的验证码进行核对时就直接从redis缓存里面取就行了, redis的客户端我们用的是jedis。因此我们在集成腾讯云短信服务SDK的同时还要集成redis服务。只需要在项目的pom.xml文件dependencies标签中加入下面三项依赖项即可:

代码语言:javascript
复制
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.222</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>

创建短信签名和短信模板

登录腾讯云控制台后进入国内短信->短信签名管理页面[10]

点击【创建签名】按钮创建短信签名,进入创建签名页面,选择签名用途、签名类型、证明类型、填写签名内容和申请说明,并上传证明。签名内容有一定的限制:长度为2~12字,由中英文、数字组成,内容不包含【】。

签名用途有两种:自用或他用,默认为自用;签名类型有4种,分别是网站、App、公众号和小程序,每种签名类型上传的证明类型与其签名类型一一对应。腾讯云控制台短信签名管理页面都有详细的提示,按照提示上传对应的证明类型截图即可;申请说明填写短信用途即可。填写好之后点击【确定】按钮,等待腾讯云后台审批通过之后才可使用,审批时间一般需要2个小时。

进入短信->正文模板管理点击【创建正文模板】按钮进入创建正文模板界面编辑正文模板

模板名称:自定义;短信类型:个人选择普通类型,营销类型需要企业认证的账户才可以启用;短信内容可以点击使用标准模板,选好后点击右边操作列下的蓝色字体【使用】即可;申请说明:根据申请用途自定义

填写好模板名称和短信内容及申请说明后点击确定会生成模板ID,在正文模板管理页面可以看到,这个模拟ID后面会用到,模板ID为短信模板ID列对应的数字。注意:短信模板需要在状态为已通过之后才可使用。

下面项目中笔者使用之前审核通过的短信签名和短信模板ID

application.properties添加腾讯云短信配置信息

代码语言:javascript
复制
blog.sms.secretId=<你的腾讯云secretId>
blog.sms.secretKey=<你的腾讯云secretKey>
blog.sms.appid=<你的腾讯云appid>
blog.sms.signName=<短信签名内容>
blog.sms.endpoint=sms.tencentcloudapi.com
blog.sms.region=<服务器所在区域,广州为:ap-guangzhou>

新建配置信息类SmsPropperties

代码语言:javascript
复制
@Configuration
@ConfigurationProperties(prefix = "blog.sms")
public class SmsProperty {

private String secretId;

private String secretKey;

private String appid;

private String signName;

private String endpoint;

private String region;
// 省略setter、getter方法

}

启用配置属性类需要在pom.xml文件中添加spring-boot-configuration-processor的依赖

代码语言:javascript
复制
  <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

同时在启动类上加上@EnableConfigurationProperties注解

代码语言:javascript
复制
@SpringBootApplication
@EnableScheduling//开启定时任务支持
@EnableConfigurationProperties
@MapperScan(basePackages={"org.sang.mapper"},annotationClass = Repository.class)
public class BlogserverApplication {

public static void main(String[] args) {
    SpringApplication.run(BlogserverApplication.class, args);
}

}

配置短信发送SmsClient类bean

新建SmsConfig配置类添加SmsClientbean, 参照发送短信SDK API完成

代码语言:javascript
复制
@Configuration
public class SmsConfig {

@Resource
private SmsProperty smsProperty;

@Bean
public SmsClient smsClient() {
    Credential cred = new Credential(smsProperty.getSecretId(), smsProperty.getSecretKey());
    // 实例化一个 http 选项,可选,无特殊需求时可以跳过
    HttpProfile httpProfile = new HttpProfile();
    httpProfile.setReqMethod(&#34;POST&#34;);
    httpProfile.setConnTimeout(60);
    /* SDK 默认使用 POST 方法。
     * 如需使用 GET 方法,可以在此处设置,但 GET 方法无法处理较大的请求 */
    httpProfile.setEndpoint(smsProperty.getEndpoint());
    /* 非必要步骤:
     * 实例化一个客户端配置对象,可以指定超时时间等配置 */
    ClientProfile clientProfile = new ClientProfile();
    /* SDK 默认用 TC3-HMAC-SHA256 进行签名
     * 非必要请不要修改该字段 */
    clientProfile.setSignMethod(&#34;HmacSHA256&#34;);
    clientProfile.setHttpProfile(httpProfile);
    SmsClient client = new SmsClient(cred, smsProperty.getRegion(),clientProfile);
    return client;
}

}

新建短信服务类

新建SmsService类,并在该类中完成生成随机码、参照SDK发送短信API文档组装发送短信请求参数,调用SmsClient类bean完成发送短信和RedisTemplate类bean完成手机验证码限时存储。

代码语言:javascript
复制
@Service
public class SmsService {

private static final Logger logger = LoggerFactory.getLogger(SmsService.class);
@Resource
private SmsProperty smsProperty;
@Resource
private SmsClient smsClient;
@Resource
private RedisTemplate&lt;String, Object&gt; redisTemplate;

public SendSmsResponse sendLoginVeryCodeMessage(String phoneNum) {
    logger.info(&#34;secretId={}, secretKey={}&#34;, smsProperty.getSecretId(), smsProperty.getSecretKey());
    SendSmsRequest req = new SendSmsRequest();
    req.setSenderId(null);
    req.setSessionContext(null);
    // 因从配置属性中获取中文内容出现乱码改为在代码中把签名内容写死
    req.setSign(&#34;阿福谈Java技术栈&#34;);
    req.setSmsSdkAppid(smsProperty.getAppid());
    req.setTemplateID(SmsEnum.PHONE_CODE_LOGIN.getTemplateId());
    req.setPhoneNumberSet(new String[]{phoneNum});
    String verifyCode = getCode();
    String[] params = new String[]{verifyCode, &#34;10&#34;};
    req.setTemplateParamSet(params);
    logger.info(&#34;req={}&#34;, JSON.toJSONString(req));
    try {
        SendSmsResponse res = smsClient.SendSms(req);
        if (&#34;Ok&#34;.equals(res.getSendStatusSet()[0].getCode())) {
            // 发送短信验证码成功则将验证码保存到redis缓存中
            redisTemplate.opsForValue().set(&#34;loginVerifyCode:&#34;+phoneNum, verifyCode, 10, TimeUnit.MINUTES);
        }
        return res;
    } catch (TencentCloudSDKException e) {
        logger.error(&#34;send message failed&#34;, e);
        throw new RuntimeException(&#34;send message failed, caused by &#34; + e.getMessage());
    }
}

/**
 * 生成6位随机数字验证码
 * @return
 */
private String getCode() {
    String numbers = &#34;1234567890&#34;;
    StringBuilder builder = new StringBuilder();
    Random random = new Random();
    for(int i=0; i&lt;6; i++) {
        int index = random.nextInt(10);
        builder.append(numbers.charAt(index));
    }
    return builder.toString();
}

}

完成发送短信验证码控制器方法

UserController类中添加发送短信验证码方法

代码语言:javascript
复制
 @Resource
private SmsService smsService;

@RequestMapping(value="sendLoginVerifyCode", method = RequestMethod.POST)
@ApiOperation(value = "sendLoginVerifyCode", notes = "发送登录短信验证码")
@ApiImplicitParam(name="paramMap", value = "入参Map", required = true, paramType = "body", dataTypeClass=HashMap.class)
public RespBean<Map<String, Object>> sendLoginVerifyCode(@RequestBody Map<String, String> paramMap) {
String phoneNumber = paramMap.get("phoneNumber");
SendSmsResponse response = smsService.sendLoginVeryCodeMessage(phoneNumber);
SendStatus[] statuses = response.getSendStatusSet();
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("code", statuses[0].getCode());
resultMap.put("message", statuses[0].getMessage());
resultMap.put("phoneNumber", statuses[0].getPhoneNumber());
resultMap.put("fee", statuses[0].getFee());
RespBean<Map<String, Object>> respBean = new RespBean<>(200, "success");
respBean.setData(resultMap);
return respBean;
}

测试发送短信验证码效果

为方便测试,在启动项目测试发送短信验证码接口前我们需要在spring-security配置类WebSecurityConfig中放开对这个接口的拦截

代码语言:javascript
复制
 @Override
protected void configure(HttpSecurity http) throws Exception {
// 配置跨域
http.cors().configurationSource(corsConfigurationSource());
// 禁用spring security框架的退出登录,使用自定义退出登录
http.logout().disable();
http.authorizeRequests()
.antMatchers("/user/reg").anonymous()
.antMatchers("/sendLoginVerifyCode").anonymous()
//......省略其他代码
}

启动项目后在postman中测试发送短信验证码接口

代码语言:javascript
复制
POST http://localhost:8081/blog/sendLoginVerifyCode
{
"phoneNumber": "+86-18682244076"
}

注意发送国内短信时,手机号码参数前需要加上+86代表地区为中国大陆

点击postman右上角的Send按钮响应信息如下, data中的code字段为OK代表发送成功

代码语言:javascript
复制
{
"status": 200,
"msg": "success",
"data": {
"code": "Ok",
"phoneNumber": "+8618682244076",
"fee": 1,
"message": "send success"
}
}

本文代码已上传到gitee代码仓库:

https://gitee.com/heshengfu1211/blogserver.git

需要查看完整代码的可以克隆下来阅读

手机号码18682244076的手机上也收到了验证码信息:

参考链接

【1】https://cloud.tencent.com/document/product/382/52077

【2】https://cloud.tencent.com/document/product/382/37745

【3】https://cloud.tencent.com/document/product/382/18060

【4】https://cloud.tencent.com/document/product/382/52077

【5】https://github.com/TencentCloud/tencentcloud-sdk-java

【6】https://maven.apache.org/

【7】https://search.maven.org/search?q=tencentcloud-sdk-java

【8】https://github.com/tencentcloud/tencentcloud-sdk-java

【9】https://cloud.tencent.com/document/product/382/43194#example

【10】https://console.cloud.tencent.com/smsv2/csms-sign

【11】https://cloud.tencent.com/document/sdk/Java

---END---