问题:在前端使用云服务实现发送短信业务时,若未启用验证码防刷机制,会导致他人恶意或者无意刷新验证码,导致服务器压力变大,以及短信服务超额。
解决方案:采用redis存储短信验证码,在前端以及后端同时采用防刷机制,双重防刷,步骤如下;
前端:使用js代码检测上次点击的时间多久(如果刷新页面,会被重新清空,需要后端服务器读取redis中的数据确定发送时间)
<a id="sendCode">发送验证码</a>
//全局定义一个时间,这里设置成60s
var num = 60;
function timeoutChangeStyle(){//使用jQuery获取元素属性,设置成不可点击$("#sendCode").attr("class","disabled")if(num>0){var str = num+"s 后再次发送"$("#sendCode").text(str)//设置1s改变一次样式setTimeout("timeoutChangeStyle()",1000) }else{num=60$("#sendCode").text("发送验证码")$("#sendCode").attr("class","")}num--;
}
后端:使用阿里云短信服务,操作api获取到验证码,在redis存储验证码时,加入前缀,来辨别验证码的来源,同时将验证码与记录时间合并,以“_”隔开,用于检测验证码是否超过60s。
具体步骤如下:
1)存储时,使用key-value键值对存储,这里对key与value分别做处理,使其具有防刷的特征;
2)存储key时,将手机号作为key,同时加上常量前缀,作为命名空间,格式:常量+手机号
3)存储value时,将验证码与存储的时间节点同时存储(这里用UUID模仿阿里云服务的验证码),然后以“_”符号隔开,格式:验证码 + “_” + 时间节点
@ResponseBody@GetMapping("/sms/sendcode")public R sendCode(@RequestParam("phone") String phone){//接口防刷String redisCode = redisTemplate.opsForValue().get("LOGINCONSTANT" + phone);if(!StringUtils.isEmpty(redisCode)){long l = Long.parseLong(redisCode.split("_")[1]);if(System.currentTimeMillis()-l < 60000){return R.error("10002", "短信验证码频率太高,稍后再试");}}//用UUID模仿云服务传来的验证码String code = UUID.randomUUID().toString().substring(0, 5);//格式:验证码 + “_” + 时间节点String codeWithTime = code + "_" + System.currentTimeMillis();redisTemplate.opsForValue().set("LOGINCONSTANT" + phone,codeRedis,10, TimeUnit.MINUTES);return R.ok();}
如何检测验证码是否有60s?
存储在redis的数据里的结构是 常量+手机号(key)-验证码 + “_” + 时间节点(value) ,当前端点击发送验证码按钮时:
1)先从redis中检测该手机号是否存储相应的验证码,若不存在,就按上面格式存储一份验证码。
2)若存在,按照存储的key取出相应的值,用split方法将验证码与时间戳分隔开,将存储的时间戳与当前时间做减法,若未超过60s,前端返回相应提示信息,提醒用户时间未到。