博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
API 接口防刷
阅读量:6832 次
发布时间:2019-06-26

本文共 5871 字,大约阅读时间需要 19 分钟。

API 接口防刷

顾名思义,想让某个接口某个人在某段时间内只能请求N次。
在项目中比较常见的问题也有,那就是连点按钮导致请求多次,以前在web端有表单重复提交,可以通过token 来解决。
除了上面的方法外,前后端配合的方法。现在全部由后端来控制。
原理
在你请求的时候,服务器通过redis 记录下你请求的次数,如果次数超过限制就不给访问。
在redis 保存的key 是有时效性的,过期就会删除。
代码实现:
为了让它看起来逼格高一点,所以以自定义注解的方式实现

@RequestLimit 注解

import java.lang.annotation.*;/** * 请求限制的自定义注解 * * @Target 注解可修饰的对象范围,ElementType.METHOD 作用于方法,ElementType.TYPE 作用于类 * (ElementType)取值有: *     1.CONSTRUCTOR:用于描述构造器 *     2.FIELD:用于描述域 *     3.LOCAL_VARIABLE:用于描述局部变量 *     4.METHOD:用于描述方法 *     5.PACKAGE:用于描述包 *     6.PARAMETER:用于描述参数 *     7.TYPE:用于描述类、接口(包括注解类型) 或enum声明 * @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃; * 而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略, * 而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。 * 使用这个meta-Annotation可以对 Annotation的“生命周期”限制。 * (RetentionPoicy)取值有: *     1.SOURCE:在源文件中有效(即源文件保留) *     2.CLASS:在class文件中有效(即class保留) *     3.RUNTIME:在运行时有效(即运行时保留) * * @Inherited * 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 * 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 */@Documented@Inherited@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface RequestLimit {    // 在 second 秒内,最大只能请求 maxCount 次    int second() default 1;    int maxCount() default 1;}

RequestLimitIntercept 拦截器

自定义一个拦截器,请求之前,进行请求次数校验

import com.alibaba.fastjson.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import top.lrshuai.limit.annotation.RequestLimit;import top.lrshuai.limit.common.ApiResultEnum;import top.lrshuai.limit.common.Result;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.util.concurrent.TimeUnit;/** * 请求拦截 */@Slf4j@Componentpublic class RequestLimitIntercept extends HandlerInterceptorAdapter {    @Autowired    private RedisTemplate
redisTemplate; @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)){ resonseOut(response,Result.error(ApiResultEnum.REQUST_LIMIT)); return false; } } } return super.preHandle(request, response, handler); } //判断请求是否受限 public boolean isLimit(HttpServletRequest request,RequestLimit requestLimit){ // 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。 String limitKey = request.getServletPath()+request.getSession().getId(); // 从缓存中获取,当前这个请求访问了几次 Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey); if(redisCount == null){ //初始 次数 redisTemplate.opsForValue().set(limitKey,1,requestLimit.second(), TimeUnit.SECONDS); }else{ if(redisCount.intValue() >= requestLimit.maxCount()){ return true; } // 次数自增 redisTemplate.opsForValue().increment(limitKey); } return false; } /** * 回写给客户端 * @param response * @param result * @throws IOException */ private void resonseOut(HttpServletResponse response, Result result) throws IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = null ; String json = JSONObject.toJSON(result).toString(); out = response.getWriter(); out.append(json); }}

拦截器写好了,但是还得添加注册

WebMvcConfig 配置类

因为我的是Springboot2. 所以只需实现WebMvcConfigurer
如果是springboot1.
那就继承自 WebMvcConfigurerAdapter
然后重写addInterceptors() 添加自定义拦截器即可。

@Slf4j@Componentpublic class WebMvcConfig implements WebMvcConfigurer {    @Autowired    private RequestLimitIntercept requestLimitIntercept;    @Override    public void addInterceptors(InterceptorRegistry registry) {        log.info("添加拦截");        registry.addInterceptor(requestLimitIntercept);    }}

Controller

控制层测试接口,
使用方式:

第一种:直接在类上使用注解@RequestLimit(maxCount = 5,second = 1)

第二种:在方法上使用注解@RequestLimit(maxCount = 5,second = 1)

maxCount 最大的请求数、second 代表时间,单位是秒

默认1秒内,每个接口只能请求一次

@RestController@RequestMapping("/index")@RequestLimit(maxCount = 5,second = 1)public class IndexController {    /**     * @RequestLimit 修饰在方法上,优先使用其参数     * @return     */    @GetMapping("/test1")    @RequestLimit    public Result test(){        //TODO ...        return Result.ok();    }    /**     * @RequestLimit 修饰在类上,用的是类的参数     * @return     */    @GetMapping("/test2")    public Result test2(){        //TODO ...        return Result.ok();    }}

如果在类和方法上同时有@RequestLimit注解 ,以方法上的参数为准,好像注释有点多了。

转载于:https://blog.51cto.com/13981400/2393248

你可能感兴趣的文章
codeforces Gym 100500C D.Hall of Fame 排序
查看>>
约瑟夫环问题
查看>>
yum
查看>>
c++指针存储应用程序和释放内存的问题
查看>>
LPC43xx SGPIO Slice 示意图
查看>>
NUMA的取舍与优化设置
查看>>
uboot源码整体框架
查看>>
编译命令行终端 swift
查看>>
Swift - 使用UISearchController实现带搜索栏的表格
查看>>
web接口测试之GET与POST请求
查看>>
关于LR中的EXTRARES
查看>>
转:如何转换Android打包用jks格式keystore证书为Air用pkcs12格式p12证书
查看>>
光伏发电系列:关于光伏发电站的建设成本和资金回收周期
查看>>
Linux内核源代码目录树结构
查看>>
js常用正则表达式
查看>>
jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容续
查看>>
数据仓库专题18-数据建模语言IDEF(转载)
查看>>
GridView自带分页 1总页数 首页 下一页 上一页 尾页 X 页 go 实现方法 .
查看>>
Caffe学习系列(9):运行caffe自带的两个简单例子
查看>>
android:EditText控件
查看>>