1. 首页
  2. >
  3. 编程技术
  4. >
  5. Java

手写一个注解实现接口单位时间内的访问频率的拦截器

目标

相信大家都听过接口安全,接口限流等这些词语,那么本篇文章就是从最基本的问题开始,带大家手写一个控制接口单位时间内访问频率的demo。好了,下面开始上代码。

环境+依赖

spring boot工程就不在此搭建了,小编直接贴出核心依赖

 <dependency>    <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-data-redis</artifactId>   <version>2.3.0.RELEASE</version>  </dependency>  <dependency>   <groupId>org.apache.commons</groupId>   <artifactId>commons-pool2</artifactId>   <version>2.4.2</version>  </dependency>  <dependency> <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-web</artifactId> </dependency>

里面有用到redis,具体的配置可以参考小编之前的文章springboot整合redis,此处不再单独列出来了。

自定义注解 + 拦截器

  • 自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiLimit {     //time时间内请求的最大次数     int count();      //时间段内,单位:s     int time(); }
  • 拦截器
public class ApiInterceptor implements HandlerInterceptor {      @Autowired     private RedisTemplate redisTemplate;      @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         if(handler instanceof HandlerMethod) {             HandlerMethod handlerMethod = (HandlerMethod) handler;             Method method = handlerMethod.getMethod();             ApiLimit apiLimit = method.getAnnotation(ApiLimit.class);             if(!ObjectUtils.isEmpty(apiLimit)) {                 int count = apiLimit.count();                 int time = apiLimit.time();                 String ip = request.getRemoteAddr();                 String requestURI = request.getRequestURI();                 String redisKey = "apiKey_" + ip + "_" + requestURI;                 System.out.println("redisKey======>" + redisKey);                 if (ObjectUtils.isEmpty(redisTemplate.opsForValue().get(redisKey))) {                     AtomicInteger atomicInteger = new AtomicInteger(1);                     redisTemplate.opsForValue().set(redisKey, atomicInteger, time, TimeUnit.SECONDS);                     System.out.println("当前访问次数:" + ((AtomicInteger) redisTemplate.opsForValue().get(redisKey)).get());                 } else {                     AtomicInteger atomicInteger = (AtomicInteger) redisTemplate.opsForValue().get(redisKey);                     if (atomicInteger.incrementAndGet() > count) {                         throw new RuntimeException("访问太频繁了,请休息一会");                     }                     Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);                     redisTemplate.opsForValue().set(redisKey, atomicInteger, expire ,TimeUnit.SECONDS);                     System.out.println("当前访问次数:" + ((AtomicInteger) redisTemplate.opsForValue().get(redisKey)).get());                 }             } else {                 return true;             }         }         return true;     } }

ip的获取大家可以借鉴网上的工具,此处为测试就简写了。

  • 拦截器配置

拦截器写好之后,需要将它注册到spring中

@Configuration public class WebMvcConfig implements WebMvcConfigurer {      @Bean     public ApiInterceptor apiInterceptor() {         return new ApiInterceptor();     }      @Override     public void addInterceptors(InterceptorRegistry registry) {       	//这里我们添加了一个测试的路径         registry.addInterceptor(apiInterceptor()).addPathPatterns("/test/limit");     } }

测试类

只需要在需要拦截的接口上加上@ApiLimit注解,定义好时间和访问次数即可。

@Controller @RequestMapping("/test") public class TestController {      @GetMapping("/limit")     @ResponseBody     @ApiLimit(count = 5, time = 10)     public String get() {         return "hello world !!!";     } }

测试

我们先连续访问这个url,10s后再次访问。结果如下


手写一个注解实现接口单位时间内的访问频率的拦截器

连续访问


手写一个注解实现接口单位时间内的访问频率的拦截器

10s后再次访问

从测试结果可以看到,当我们10s连续访问这个接口超过5的话,直接抛出异常了。等10s后,再次访问,接口恢复如初。基本上满足了我们的小目标。