【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
API 接口防刷,顾名思义,想让某个接口某个人在某段时间内只能请求N次,
在项目中比较常见的问题也有,那就是连点按钮导致请求多次,以前在web端有表单重复提交,可以通过token 来解决。
除了上面的方法外,前后端配合的方法。现在全部由后端来控制。
举个例子,http://localhost:8080/test, 这个接口我要求他10秒钟内只能调3次,如果10秒内调用超过了3次就限制调用,在下一个10秒开始的时候解除限制重新限用。实际上就每10秒限制调用 3次,如果超过限制,冷却到下一个10秒,解除限制。
原理过程
在你请求的时候,服务器通过服务器内存中记录下你请求的次数,如果次数超过限制就不给访问,返回一个提示,这里记录请求次数最好是在redis里记录,并设置一个过期时间。我在本机写一个例子,算是一种思路吧,本机懒得搭redis,就用session代替一下,这里我会在代码设置好session过期时间。sesssion的过期时间默认是30分钟。这里是用spring的拦截器实现 的。
代码实现:
自定义注解的方式实现
写一个测试类的控制器,在测试方法上加上自定义的注解:
然后实现一个自定义的拦截器并把它注册到配置中去,拦截器主要实现了拦截加有自定义注解@RequestLimit 的请求,在请求被处理前监测一下这一次的请求是否超限,如果没有超限,请求继续,如果超限,中断请求处理,返回超限提示。
package com.sq.gaox.interceptor;
/**
* @author gaox
* @description
* @date 2019/6/4 11:30
*/
import com.sq.gaox.annoation.RequestLimit;
import com.sq.gaox.bean.TimeBean;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
/**
* 请求拦截
*/
@Component
public class RequestLimitIntercept extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* isAssignableFrom() 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
* isAssignableFrom()方法是判断是否为某个类的父类
* instanceof关键字是判断是否某个类的子类
*/
if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
//HandlerMethod 封装方法定义相关的信息,如类,方法,参数等
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 获取方法中是否包含注解
RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class);
//获取 类中是否包含注解,也就是controller 是否有注解
RequestLimit classAnnotation = method.getDeclaringClass().getAnnotation(RequestLimit.class);
// 如果 方法上有注解就优先选择方法上的参数,否则类上的参数
RequestLimit requestLimit = methodAnnotation != null?methodAnnotation:classAnnotation;
if(requestLimit != null){
if(isLimit(request,requestLimit)){
Object tempObj= request.getSession().getAttribute( request.getServletPath()+request.getSession().getId());
TimeBean timeBean= (TimeBean) tempObj;
resonseOut(response,timeBean);
return false;
}
}
}
return super.preHandle(request, response, handler);
}
/**
* 判断请求是否受限
* @param request
* @param requestLimit
* @return
* @deprecated 受限的条件:
* 一、当限制时间内调用次数超过预设值,调用会受限。
* 二、当限制时间的调用次数已经达到预设值,须等限制时间结束后才能重新调用
*/
public boolean isLimit(HttpServletRequest request,RequestLimit requestLimit){
String limitKey = request.getServletPath()+request.getSession().getId();
Object tempObj= request.getSession().getAttribute(limitKey);
if(tempObj==null){
TimeBean timeBean=new TimeBean();
timeBean.setApiName(request.getServletPath());
timeBean.setCount(1);
Long curTime=System.currentTimeMillis();
timeBean.setStartTime(curTime);
timeBean.setCurTime(curTime);
timeBean.setNextTime(curTime+requestLimit.second()*1000);
request.getSession().setAttribute(limitKey,timeBean);
request.getSession().setMaxInactiveInterval(60);
}else{
TimeBean temp= (TimeBean) tempObj;
Long curTime2=System.currentTimeMillis();
Long timeR=curTime2-temp.getStartTime();
boolean con1=temp.getCount()+1>requestLimit.maxCount()&&timeR<requestLimit.second()*1000;
boolean con2=temp.getCount()+1>requestLimit.maxCount()&&temp.getNextTime()>curTime2;
if(con1||con2){
return true;
}else if(temp.getCount()+1>requestLimit.maxCount()&&temp.getNextTime()<=curTime2){
temp.setCount(1);
temp.setCurTime(System.currentTimeMillis());
temp.setStartTime(temp.getNextTime());
temp.setNextTime(temp.getNextTime()+requestLimit.second()*1000);
}
else {
temp.setCount(temp.getCount()+1);
temp.setCurTime(System.currentTimeMillis());
request.getSession().setAttribute(limitKey,temp);
}
}
return false;
}
/**
* 回写给客户端
* @param response
* @throws IOException
*/
private void resonseOut(HttpServletResponse response, TimeBean timeBean) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null ;
out = response.getWriter();
out.append(timeBean.getApiName()+"接口调用次数超限"+"\n 调用解除限制的时间为"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timeBean.getNextTime()));
}
}
注册自定义的拦截器
然后启动项目,看一下效果。上面的我配置的是每60秒只能调用3次,如果60秒内超过了3次,就会受限,只有过完60秒,再会解除限制继续正常调用。这里我只是举个例子,事实上真正生产中,基本不可能一个接口一分钟只让调三次。




注意一下调用的时间可以发现,如果不停的在调用的话,那么调用解除限制的时间与下一次调用的开始时间相同,基本达到了文章开头设想的需求。
代码地址 完整的代码,如下地址 Gitee地址:https://gitee.com/fox9916/api_demo
各位大佬,去下载示例代码的时候,如果感觉有点用的话,就动动您可爱的小手,关注一下我的公众号、收藏一下这篇文章,没准啥时候领导就甩一个这样的需求就用得上,然后分享给你的朋友们,一起学习,共同进步呗
来源:oschina
链接:https://my.oschina.net/u/2968081/blog/3146592