SpringBoot腾讯云短信实现验证码

在学习过程中偶然遇见需要实现验证码功能的需求,于是寻思着将功能抽取出来用于分享学习

业务功能:实现验证码60s,且要求防止用户高频刷验证码(即1min一次不多发)

思路:

  • 调用腾讯云短信API实现验证码功能
  • 因为要求1min中一次不多发,不妨从Redis缓存角度入手,存进去1min,再发送时验证Redis是否有,如果有就不发没有就再发(1min要求同一个用户,需要验证用户是否为同一IP)
  • 注意腾讯云每个号码每天有次数限制,测试过程注意换号码

1 腾讯云短信配置

登录腾讯云,搜索短信,点击第一个进入所需界面

点击第一个签名管理

点击创建签名

进入后点击自用,签名类型为公众号,很多操作官方有提示

审核成功后,点击正文模板管理

进入后随便写,会有信息提示

审核完后进入到访问管理-访问密匙这块功能,这一块有大用

2 Spring初步集成

2.1 导入依赖

<!-- 第三方云厂商相关的依赖 -->
<dependency><groupId>com.tencentcloudapi</groupId><artifactId>tencentcloud-sdk-java</artifactId><version>3.1.270</version><!-- 注:这里只是示例版本号(可直接使用),可获取并替换为 最新的版本号,注意不要使用4.0.x版本(非最新版本) -->
</dependency>

2.2 存放密匙信息

创建properties或者yml来存放第一阶段最后一步的密钥信息,可以参照下图来配置文件名

2.3 构建资源类

和秘钥信息做好映射,方便后续获得

@Component
@Data
@PropertySource("classpath:tencentcloud.properties")
@ConfigurationProperties(prefix = "tencent.cloud")
public class TencentCloudProperties {private String secretId;private String secretKey;
}

2.4 准备工具类

创建一个utils包导入这三个类即可,不是最主要的

package com.imooc.utils;import javax.servlet.http.HttpServletRequest;/*** 用户获得用户ip的工具类*/
public class IPUtil {/*** 获取请求IP:* 用户的真实IP不能使用request.getRemoteAddr()* 这是因为可能会使用一些代理软件,这样ip获取就不准确了* 此外我们如果使用了多级(LVS/Nginx)反向代理的话,ip需要从X-Forwarded-For中获得第一个非unknown的IP才是用户的有效ip。* @param request* @return*/public static String getRequestIp(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}
package com.imooc.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;/*** @Title: Redis 工具类*/
@Component
public class RedisOperator {@Autowiredprivate StringRedisTemplate redisTemplate;// Key(键),简单的key-value操作/*** 判断key是否存在* @param key* @return*/public boolean keyIsExist(String key) {return redisTemplate.hasKey(key);}/*** 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。* * @param key* @return*/public long ttl(String key) {return redisTemplate.getExpire(key);}/*** 实现命令:expire 设置过期时间,单位秒* * @param key* @return*/public void expire(String key, long timeout) {redisTemplate.expire(key, timeout, TimeUnit.SECONDS);}/*** 实现命令:increment key,增加key一次* * @param key* @return*/public long increment(String key, long delta) {return redisTemplate.opsForValue().increment(key, delta);}/*** 累加,使用hash*/public long incrementHash(String name, String key, long delta) {return redisTemplate.opsForHash().increment(name, key, delta);}/*** 累减,使用hash*/public long decrementHash(String name, String key, long delta) {delta = delta * (-1);return redisTemplate.opsForHash().increment(name, key, delta);}/*** hash 设置value*/public void setHashValue(String name, String key, String value) {redisTemplate.opsForHash().put(name, key, value);}/*** hash 获得value*/public String getHashValue(String name, String key) {return (String)redisTemplate.opsForHash().get(name, key);}/*** 实现命令:decrement key,减少key一次** @param key* @return*/public long decrement(String key, long delta) {return redisTemplate.opsForValue().decrement(key, delta);}/*** 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key*/public Set<String> keys(String pattern) {return redisTemplate.keys(pattern);}/*** 实现命令:DEL key,删除一个key* * @param key*/public void del(String key) {redisTemplate.delete(key);}// String(字符串)/*** 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)* * @param key* @param value*/public void set(String key, String value) {redisTemplate.opsForValue().set(key, value);}/*** 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)* * @param key* @param value* @param timeout*            (以秒为单位)*/public void set(String key, String value, long timeout) {redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);}/*** 如果key不存在,则设置,如果存在,则报错* @param key* @param value*/public void setnx60s(String key, String value) {redisTemplate.opsForValue().setIfAbsent(key, value, 60, TimeUnit.SECONDS);}/*** 如果key不存在,则设置,如果存在,则报错* @param key* @param value*/public void setnx(String key, String value) {redisTemplate.opsForValue().setIfAbsent(key, value);}/*** 实现命令:GET key,返回 key所关联的字符串值。* * @param key* @return value*/public String get(String key) {return (String)redisTemplate.opsForValue().get(key);}/*** 批量查询,对应mget* @param keys* @return*/public List<String> mget(List<String> keys) {return redisTemplate.opsForValue().multiGet(keys);}/*** 批量查询,管道pipeline* @param keys* @return*/public List<Object> batchGet(List<String> keys) {//    nginx -> keepalive
//    redis -> pipelineList<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {@Overridepublic String doInRedis(RedisConnection connection) throws DataAccessException {StringRedisConnection src = (StringRedisConnection)connection;for (String k : keys) {src.get(k);}return null;}});return result;}// Hash(哈希表)/*** 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value* * @param key* @param field* @param value*/public void hset(String key, String field, Object value) {redisTemplate.opsForHash().put(key, field, value);}/*** 实现命令:HGET key field,返回哈希表 key中给定域 field的值* * @param key* @param field* @return*/public String hget(String key, String field) {return (String) redisTemplate.opsForHash().get(key, field);}/*** 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。* * @param key* @param fields*/public void hdel(String key, Object... fields) {redisTemplate.opsForHash().delete(key, fields);}/*** 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。* * @param key* @return*/public Map<Object, Object> hgetall(String key) {return redisTemplate.opsForHash().entries(key);}// List(列表)/*** 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头* * @param key* @param value* @return 执行 LPUSH命令后,列表的长度。*/public long lpush(String key, String value) {return redisTemplate.opsForList().leftPush(key, value);}/*** 实现命令:LPOP key,移除并返回列表 key的头元素。* * @param key* @return 列表key的头元素。*/public String lpop(String key) {return (String)redisTemplate.opsForList().leftPop(key);}/*** 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。* * @param key* @param value* @return 执行 LPUSH命令后,列表的长度。*/public long rpush(String key, String value) {return redisTemplate.opsForList().rightPush(key, value);}}
package com.imooc.utils;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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class SMSUtils {@Autowiredprivate TencentCloudProperties tencentCloudProperties;public void sendSMS(String phone, String code) throws Exception {try {/* 必要步骤:* 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。* 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,* 以免泄露密钥对危及你的财产安全。* CAM密匙查询获取: https://console.cloud.tencent.com/cam/capi*/Credential cred = new Credential(tencentCloudProperties.getSecretId(),tencentCloudProperties.getSecretKey());// 实例化一个http选项,可选的,没有特殊需求可以跳过HttpProfile httpProfile = new HttpProfile();//            httpProfile.setReqMethod("POST"); // 默认使用POST/* SDK会自动指定域名。通常是不需要特地指定域名的,但是如果你访问的是金融区的服务* 则必须手动指定域名,例如sms的上海金融区域名: sms.ap-shanghai-fsi.tencentcloudapi.com */httpProfile.setEndpoint("sms.tencentcloudapi.com");// 实例化一个client选项ClientProfile clientProfile = new ClientProfile();clientProfile.setHttpProfile(httpProfile);// 实例化要请求产品的client对象,clientProfile是可选的SmsClient client = new SmsClient(cred, "ap-nanjing", clientProfile);// 实例化一个请求对象,每个接口都会对应一个request对象SendSmsRequest req = new SendSmsRequest();String[] phoneNumberSet1 = {"+86" + phone};//电话号码req.setPhoneNumberSet(phoneNumberSet1);req.setSmsSdkAppId("1400782012");   // 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppIdreq.setSignName("tgywata公众号");         // 签名req.setTemplateId("1646128");       // 模板id:必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看/* 模板参数(自定义占位变量): 若无模板参数,则设置为空 */String[] templateParamSet1 = {code};req.setTemplateParamSet(templateParamSet1);// 返回的resp是一个SendSmsResponse的实例,与请求对象对应SendSmsResponse resp = client.SendSms(req);// 输出json格式的字符串回包
//            System.out.println(SendSmsResponse.toJsonString(resp));} catch (TencentCloudSDKException e) {System.out.println(e.toString());}}/*public static void main(String[] args) {try {new SMSUtils().sendSMS("18812345612", "7896");} catch (Exception e) {e.printStackTrace();}}*/
}

3 整合Redis

3.1 引入依赖

<!-- 引入 redis 依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.2 配置yml

spring:redis:host: 192.168.1.61port: 6379database: 0password: itzixi

3.3 创建父类

这一步可以省略,主要是让实际的Controller类没那么复杂

import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;public class BaseInfoProperties {@Autowiredpublic RedisOperator redis;public static final String MOBILE_SMSCODE = "mobile:smscode";public static final String REDIS_USER_TOKEN = "redis_user_token";public static final String REDIS_USER_INFO = "redis_user_info";
}

3.4 创建测试API

package com.imooc.controller;import com.imooc.grace.result.GraceJSONResult;
import com.imooc.utils.IPUtil;
import com.imooc.utils.SMSUtils;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;@Slf4j
@Api(tags = "PassportController 通行证接口模块")
@RequestMapping("passport")
@RestController
public class PassportController extends BaseInfoProperties {// /passport/getSMSCode?mobile=@Autowiredprivate SMSUtils smsUtils;@PostMapping("getSMSCode")public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request) throws Exception {// 手机号为空不做操作if (StringUtils.isBlank(mobile)) {return GraceJSONResult.ok();}// 获得用户ipString userIp = IPUtil.getRequestIp(request);// 根据用户ip进行限制 限制用户在60秒之内只能获得一次验证码redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);// 随机生成验证码String code = (int) ((Math.random() * 9 + 1) * 100000) + "";smsUtils.sendSMS(mobile, code);// 控制台打印号码日志 防止接收不到log.info(code);// 把验证码放入到redis中 用于后续的验证redis.set(MOBILE_SMSCODE + ":" + mobile, code, 30 * 60); // 三十分钟失效return GraceJSONResult.ok();}}

4 优化API

4.1 创建拦截器

创建拦截器主要用于实现==同一用户1min之内验证频率过高的情况

package com.imooc.intercepter;import com.imooc.controller.BaseInfoProperties;
import com.imooc.utils.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
public class PassportInterceptor extends BaseInfoProperties implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获得用户的ipString userIp = IPUtil.getRequestIp(request);// 得到是否存在的判断boolean keyIsExist = redis.keyIsExist(MOBILE_SMSCODE + ":" + userIp);if (keyIsExist) {log.info("短信发送频率太大!");return false; // 拒绝通行 请求拦截}return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

4.2 配置拦截器

package com.imooc;import com.imooc.intercepter.PassportInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Beanpublic PassportInterceptor passportInterceptor() {return new PassportInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(passportInterceptor()).addPathPatterns("/passport/getSMSCode");}
}

5 实验

SpringBoot腾讯云短信实现验证码相关推荐

  1. 基于SpringBoot+腾讯云短信服务实现接收手机验证码功能

    在腾讯云中配置需要的服务 申请签名和短信模板 创建应用 在项目application.yml中配置相关参数 在项目中安装腾讯云短信服务的依赖包 <!--腾讯云--><dependen ...

  2. 腾讯云短信发送验证码(超详细)

    在大部分的短信平台,能免费送的短信条数也就是几条,太少了,测试都不够,基本上都是要充钱够买的.我也注册了好几个平台,发现腾讯云免费赠送200条试用短信,就很不错.在我们学生党项目测试来说的话,就已经绰 ...

  3. springboot 最新腾讯云短信接入的坑(包含所有操作流程)

    腾讯云接入短信,需要填写备案好的域名,且通过域名能正常访问到你的网站.如果你的服务器,域名全部都在腾讯云,请确保你的服务器可达,且域名已完成备案,域名的某个A记录正常(通常是www),通过这个记录值可 ...

  4. 腾讯云短信服务实现 Java 发送手机验证码(SpringBoot+Redis 实现)

    文章目录 腾讯云短信服务实现 Java 发送手机验证码(SpringBoot+Redis 实现) 1.打开腾讯云短信服务 2.创建短信签名 3.创建短信正文模板 4.等待全部审核完毕即可 5.发送短信 ...

  5. java实现短信验证码发送(架子是springboot 服务平台选择腾讯云短信服务)

    业务需求:公司扩展新业务,新增短信验证码提醒服务,负责功能模块完善 暂时只研究了腾讯短信服务的发送(看api谁都能copy出来),短信状态回执(也挺简单,只是自己想复杂了),短信回复回执(暂时没弄明白 ...

  6. Springboot+Redis接入腾讯云短信服务实现验证码发送

    目录 一.开通腾讯云短信服务 二.代码实现 三.测试 申请阿里云短信服务需要以上线APP或已备案网站,腾讯云短信服务可以使用微信公众号申请,注册个人微信公众号比较方便,改用腾讯云短信服务,参考官方SD ...

  7. 接入腾讯云短信服务(史上最详细+该短信服务如何申请成功+发送短信验证码API讲解+相关错误分析)

    2021/8/17/23:01{2021/8/17/23:01}2021/8/17/23:01 文章目录 前言 一.如何成功申请到腾讯云短信服务 1.签名申请 2.正文模板申请 二.发送短信API开发 ...

  8. SpringBoot工程接入腾讯云短信服务平台

    由于业务需要,需要使用第三方短信平台,进行验证码的发送.网上的短信服务平台主要由:百度.腾讯.阿里云:采用官方提供的SDK,调用接口即可. 腾讯云短信服务平台和阿里云短信服务平台,一般步骤为:注册-- ...

  9. 腾讯云短信服务发送验证码

    腾讯云短信服务发送验证码 1.前言 2.进入短信服务控制台 3.创建签名 4.创建模板 5.短信-应用管理-应用列表 6.创建密钥 7.SpringBoot实现短信验证码发送 7.1 引入依赖 7.2 ...

最新文章

  1. web个人主页制作代码_关于嵌入式web服务器
  2. 大咖 | 斯坦福教授骆利群:为何人脑比计算机慢1000万倍,却如此高效?
  3. linux定时关机命令_电脑设置定时关机你会吗?Windows自带的这行命令真好用
  4. ThreadPoolExecutor 的八种拒绝策略 | 含番外!
  5. 递归调用cl_crm_oi_docx_transform_rt=process_node_cc
  6. SAP CRM WebClient UI directly navigate to given UI via url
  7. FrameLayout 测量过程
  8. mpvue中使用小程序云开发总结
  9. 安卓逆向系列教程 4.8 去广告 II
  10. React学习笔记一 JSX语法组件
  11. 七日掌握设计配色基础pdf_零基础到底该如何学习室内设计!怎么才能掌握核心知识!...
  12. editplus配置刷新
  13. mysql数据库基操,都坐下!
  14. Java Foundation serial ( 一 )
  15. 有什么软件方便画er图_数据库ER图绘制工具(DbSchema)
  16. 富士通Fujitsu DPK1786T 打印机驱动
  17. word顶部有一道线_word文档上方总有一条线怎样去掉?
  18. 网络共享计算机怎么连接,两台电脑连接成局域网如何设置_两台电脑连接局域网共享的设置步骤-win7之家...
  19. 软件公司是如何招聘人才的?
  20. python 期货策略_Python版商品期货跨期布林对冲策略.md

热门文章

  1. 果蝇优化算法(FOA)原理
  2. 选择vray Next for SketchUp创建具启发性的、逼真的渲染的8大理由!
  3. html怎么搞一个微信图标,微信图标怎么点亮 两步搞定!
  4. c语言列车信息管理系统,C语言火车票信息管理系统.doc
  5. (智力题)一个屋子有一个门(门是关闭的)和3盏电灯。屋外有3个开关,分别与这3盏灯相连。确定每个开关具体管哪盏灯?
  6. 计算机视觉基础系列(python与opencv的操作与运用/tensorflow的基础介绍)(八)---小例子(神经网络逼近股票收盘价格)
  7. 【前端企微开发流程】-企业微信-创建应用-开发-调试-发布
  8. CF1660C Get an Even String(贪心)
  9. 经常说的 CPU 上下文切换是什么意思?
  10. win10安装Andorid Studio常见问题