问题:在前端使用云服务实现发送短信业务时,若未启用验证码防刷机制,会导致他人恶意或者无意刷新验证码,导致服务器压力变大,以及短信服务超额。

解决方案:采用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,前端返回相应提示信息,提醒用户时间未到。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐