通常我们在做通过手机号检验,发送短信验证码之类接口的时候,在单位时间内,要对发送的次数做限制,防止恶意刷接口,短信类接口毕竟还是收费的。
首先我们需要在springboot 项目中引入Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--springboot2.X默认使用lettuce连接池,需要引入commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
然后在配置文件中配置你redis的地址端口 密码 等
# Redis
spring:
redis:
database: 0
host: localhost
password: Botao@2019
port: 6379
timeout: 300
lettuce:
pool:
max-active: 200 #连接池最大连接数(使用负值表示没有限制)
max-idle: 20 # 连接池中的最大空闲连接
min-idle: 5 #连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
windows 环境安装redis 及配置用户名密码,可以看这
整个流程是,首先我们生成4位的短信验证码,然后把要发送的手机号 验证码 设置过期时间 存入redis ,同时将 手机号,发送的次数同时存入redis 注入两次存入的key 不一样 然后我们设置存入次数 key的过期时间 ,当然我们在发送前,需要查询单位时间内发送的次数有没有超过限制 ,没有限制再发
如图所示
@RestController
@RequestMapping("/app")
public class SendSMSController {
private static final Logger log = LoggerFactory.getLogger(SendSMSController.class);
private static final String SYMBOLS = "0123456789";
private static final Random RANDOM = new SecureRandom();
@Value("${sms_apikey}")
private String apikey;
@Value("${sms_send}")
private String send;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 发送短信验证码
* @param userId
* @param phone
* @return
*/
@PostMapping("sys/sendSms")
public AjaxResult SendSms(String userId, String phone){
try {
log.info("用户发送短信验证码:"+userId+","+phone);
if(StringUtils.isBlank(phone)){
log.info("参数错误");
return AjaxResult.error("参数错误");
}
//设置key 自动 +1
long count=stringRedisTemplate.boundValueOps(AppConstants.REDIS_SMS_USER_PREFIX+phone ).increment(1);
if(count==1){
//设置 30 分钟过期
stringRedisTemplate.expire(AppConstants.REDIS_SMS_USER_PREFIX+phone, AppConstants.REDIS_SMS_USER_EXPIRE , TimeUnit.SECONDS);
}
if(count > 5){
log.info("验证码发送频繁,超过了限定的次数");
return AjaxResult.error("验证码发送频繁,超过了限定的次数");
}
//生成随机验证码
String code=getNonce_str();
//发送短信
/* String sms= SendSMS.sendSms(apikey,send,phone,code);
//查看是否发送成功
JSONObject json=JSONObject.parseObject(sms);
String success=json.getString("status");
if (!success.equals("success")){
//发送失败,次数减回来 -1 表示
stringRedisTemplate.boundValueOps(AppConstants.REDIS_SMS_USER_PREFIX+phone ).increment(-1);
return AjaxResult.error("短信发送失败");
}*/
//发送成功,放入redis
stringRedisTemplate.opsForValue().set(AppConstants.REDIS_SMS_PREFIX+phone, code ,AppConstants.REDIS_SMS_EXPIRE,TimeUnit.SECONDS);
log.info("验证码发送成功");
return AjaxResult.success("操作成功",code);
}catch (Exception e){
log.info("短信验证码发送失败"+ ExceptionUtils.getStackTrace(e));
return AjaxResult.error("短信验证码发送失败");
}
}
@PostMapping("sys/checkSmsCode")
public AjaxResult checkSmsCode(String phone,String code){
try {
log.info("开始验证手机号验证码:"+phone+","+code);
String rediscode=stringRedisTemplate.opsForValue().get(AppConstants.REDIS_SMS_PREFIX+phone);
if (rediscode ==null || !rediscode.equals(code)){
return AjaxResult.error("验证码错误");
}
log.info("验证码验证成功");
return AjaxResult.success();
}catch (Exception e){
log.info("短信验证码验证失败"+ ExceptionUtils.getStackTrace(e));
return AjaxResult.error("短信验证码验证失败");
}
}
/**
* 获取长度为 4 的随机数字
* @return 随机数字
* @date
*/
public static String getNonce_str() {
// 如果需要4位,那 new char[4] 即可,其他位数同理可得
char[] nonceChars = new char[4];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
}
AppConstants 类是放缓存 key 的前缀和过期时间 ,大家可以自己定义,顺便把类也贴一下
public class AppConstants {
public static final String REDIS_KEY_PREFIX = "botao:"; // redis缓存前缀
/** redis session 前缀*/
public static final String REDIS_SESSION_PREFIX = "botao:session:";
/** redis缓存 session失效时间 秒*/
public static final int REDIS_SESSION_EXPIRE = 60*60*24*30;
/** redis token 前缀*/
public static final String REDIS_TOKEN_PREFIX = "botao:token:";
/** redis缓存 token失效时间 秒*/
public static final int REDIS_TOKEN_EXPIRE = 60*60*24*30;
/** redis缓存 验证码 失效时间 秒*/
public static final int REDIS_SMS_EXPIRE = 60*5;
/** redis缓存 验证码 失效时间 秒*/
public static final int REDIS_SMS_USER_EXPIRE = 60*30;
/** redis sms 前缀*/
public static final String REDIS_SMS_PREFIX = "botao:sms:";
/** redis sms 前缀*/
public static final String REDIS_SMS_USER_PREFIX = "botao:count:sms:";
}
上面设置的是半小时内,发送 5 次 ,经过测试,可以对单位时间内发送次数做限制