1.需求分析
在编写一个插入数据的接口,这个接口是对接前端的的,前端的功能大致就是用户浏览和留言,因为这个页面是无登录的,只要是使用者都可以提交这个数据,这个接口是公开的,为了避免有恶意的数据提交请求导致大量的脏数据保存,造成程序卡顿甚至宕机,对这个接口进行优化改造。
2.初级构思
在用户发送请求时,传过来的同时有HttpServletRequest,从这个里面可以获取用户的IP地址,利用Redis可以设置过期Key的方式,以ration加上用户IP为key,插入的value为可以提交数据的次数,然后设置过期时间。
redisService.set("rate:" + userIp,50,10,TimeUnit.MINUTES);
这句代码的意思是设置一个过期时间为10分钟的键值对,这样每次插入的时候,先查询一下value,如果这个key不存在的时候就set一次设置key的过期时间,如果value大于0的时候可以继续插入,插入的同时给key设置一个原值减一的值。如果value小于等于0的时候,就不能继续插入,返回一个提示。
if (rate>0){
customerService.save(entity);
redisService.set("rate:" + userIp,(rate-1));
} else if (rate<=0){
return R.error().data("msg","提交次数超过限制,请勿重复提交!");
}
表面看这样没毛病,当时间到了,这个key就不存在了,程序便会新set一个过期时间为10分钟的key。但程序经过测试之后,数据的大量插入任然可以,用循环100次进行数据插入,结果都是success。这样结果不没有达到效果的。
3.原因分析
在经过Debug断点后发现,在每次插入之后对key重新set的操作,就相当于覆盖原有键值对,连同过期时间也覆盖掉,这样新set的键值对是没有过期时间的,另外在插入之前也没有新获取rate的值,导致rate的值一直不变,这样就产生了两个bug,key一直不会过期,数据也能无限次插入。
4.升级构思
在CSDN翻了半天发现没有一种很直观的方法,不过在经过60000毫秒的思考之后,发现可以另外再设置一个键值对,key为time加上用户的ip地址,value值为当前时间毫秒值加上过期时间毫秒值,就能算出key过期的时间了。代码设计如下:
Integer rate = getRate("rate:" + userIp,userIp);
if (rate==null){
redisService.set("rate:" + userIp,50);
redisService.set("time:"+userIp,System.currentTimeMillis()+GlobalConstant.TEN_MINUTES);
}
这样的设计之外我们要封装一个新的get key的方法:
private Integer getRate(String key, String userIp){
Long expires = redisService.get("time:" + userIp);
if (expires==null){
return null;
}else {
if (System.currentTimeMillis()>=expires){
redisService.del("time:" + userIp);
redisService.del(key);
return null;
}else {
return redisService.get(key);
}
}
}
在过期时间为空的时候,rate的值也一定为空,所以直接返回空,否则就要对比当前时间和过期时间,如果当前时间大于过期时间,说明这个rate的时间已经到了,就需要删除rate和expires,当两个都删除后同样返回空,如果当前时间小于过期时间,就说明rate还在有效时间内,这个时候就正常get值就行,将获取的value返回即可,判断逻辑依旧为rate小于或者等于0的时候不能进行数据的插入。 当rate为空的时候,就继续创建新的rate和time键值对。
利用Redis实现一个简单的提交请求限制。
|