API 接口防刷
顾名思义,想让某个接口某个人在某段时间内只能请求N次。
在项目中比较常见的问题也有,那就是连点按钮导致请求多次,以前在web端有表单重复提交,可以通过token 来解决。
除了上面的方法外,前后端配合的方法。现在全部由后端来控制。
原理
在你请求的时候,服务器通过redis 记录下你请求的次数,如果次数超过限制就不给访问。
在redis 保存的key 是有时效性的,过期就会删除。
代码实现:
为了让它看起来逼格高一点,所以以自定义注解的方式实现
@RequestLimit
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import java.lang.annotation.*;
@Documented @Inherited @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestLimit { int second() default 1; int maxCount() default 1; }
|
RequestLimitIntercept
拦截器
自定义一个拦截器,请求之前,进行请求次数校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| 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 @Component public class RequestLimitIntercept extends HandlerInterceptorAdapter {
@Autowired private RedisTemplate<String,Object> redisTemplate;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler.getClass().isAssignableFrom(HandlerMethod.class)){ HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class); 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){ 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; }
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()
添加自定义拦截器即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @Component public 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秒内,每个接口只能请求一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @RestController @RequestMapping("/index") @RequestLimit(maxCount = 5,second = 1) public class IndexController {
@GetMapping("/test1") @RequestLimit public Result test(){ return Result.ok(); }
@GetMapping("/test2") public Result test2(){ return Result.ok(); } }
|
如果在类和方法上同时有@RequestLimit
注解 ,以方法上的参数为准,好像注释有点多了。
代码地址
完整的代码,如下地址