API 接口防刷

僤鯓⒐⒋嵵緔 提交于 2019-12-24 13:35:48

【推荐】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

各位大佬,去下载示例代码的时候,如果感觉有点用的话,就动动您可爱的小手,关注一下我的公众号、收藏一下这篇文章,没准啥时候领导就甩一个这样的需求就用得上,然后分享给你的朋友们,一起学习,共同进步呗

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!