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>
</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;
public class IPUtil {
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;
@Component
public class RedisOperator {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean keyIsExist(String key) {
return redisTemplate.hasKey(key);
}
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
public long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
public long incrementHash(String name, String key, long delta) {
return redisTemplate.opsForHash().increment(name, key, delta);
}
public long decrementHash(String name, String key, long delta) {
delta = delta * (-1);
return redisTemplate.opsForHash().increment(name, key, delta);
}
public void setHashValue(String name, String key, String value) {
redisTemplate.opsForHash().put(name, key, value);
}
public String getHashValue(String name, String key) {
return (String)redisTemplate.opsForHash().get(name, key);
}
public long decrement(String key, long delta) {
return redisTemplate.opsForValue().decrement(key, delta);
}
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
public void del(String key) {
redisTemplate.delete(key);
}
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
public void setnx60s(String key, String value) {
redisTemplate.opsForValue().setIfAbsent(key, value, 60, TimeUnit.SECONDS);
}
public void setnx(String key, String value) {
redisTemplate.opsForValue().setIfAbsent(key, value);
}
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
public List<String> mget(List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
public List<Object> batchGet(List<String> keys) {
List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection src = (StringRedisConnection)connection;
for (String k : keys) {
src.get(k);
}
return null;
}
});
return result;
}
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
public String lpop(String key) {
return (String)redisTemplate.opsForList().leftPop(key);
}
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 {
@Autowired
private TencentCloudProperties tencentCloudProperties;
public void sendSMS(String phone, String code) throws Exception {
try {
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("sms.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
SmsClient client = new SmsClient(cred, "ap-nanjing", clientProfile);
SendSmsRequest req = new SendSmsRequest();
String[] phoneNumberSet1 = {"+86" + phone};
req.setPhoneNumberSet(phoneNumberSet1);
req.setSmsSdkAppId("1400782012");
req.setSignName("tgywata公众号");
req.setTemplateId("1646128");
String[] templateParamSet1 = {code};
req.setTemplateParamSet(templateParamSet1);
SendSmsResponse resp = client.SendSms(req);
} catch (TencentCloudSDKException e) {
System.out.println(e.toString());
}
}
}
3 整合Redis
3.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 配置yml
spring:
redis:
host: 192.168.1.61
port: 6379
database: 0
password: itzixi
3.3 创建父类
这一步可以省略,主要是让实际的Controller类没那么复杂
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseInfoProperties {
@Autowired
public 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 {
@Autowired
private SMSUtils smsUtils;
@PostMapping("getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request) throws Exception {
if (StringUtils.isBlank(mobile)) {
return GraceJSONResult.ok();
}
String userIp = IPUtil.getRequestIp(request);
redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);
String code = (int) ((Math.random() * 9 + 1) * 100000) + "";
smsUtils.sendSMS(mobile, code);
log.info(code);
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 {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userIp = IPUtil.getRequestIp(request);
boolean keyIsExist = redis.keyIsExist(MOBILE_SMSCODE + ":" + userIp);
if (keyIsExist) {
log.info("短信发送频率太大!");
return false;
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public 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 {
@Bean
public PassportInterceptor passportInterceptor() {
return new PassportInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor()).
addPathPatterns("/passport/getSMSCode");
}
}
5 实验
|