目标
相信大家都听过接口安全,接口限流等这些词语,那么本篇文章就是从最基本的问题开始,带大家手写一个控制接口单位时间内访问频率的demo。好了,下面开始上代码。
环境+依赖
spring boot工程就不在此搭建了,小编直接贴出核心依赖
org.springframework.boot spring-boot-starter-data-redis 2.3.0.RELEASEorg.apache.commons commons-pool2 2.4.2org.springframework.boot spring-boot-starter-thymeleaforg.springframework.boot spring-boot-starter-web
里面有用到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中
@Configurationpublic 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后再次访问。结果如下
![f666a8d4e844a5887693886b9ac738ad.png](https://i-blog.csdnimg.cn/blog_migrate/40b053645bbca1038c6a88b5c00a3b7b.jpeg)
连续访问
![5d0e0c5675f3544a32ebf96e9285a1a1.png](https://i-blog.csdnimg.cn/blog_migrate/6c494fa5f948199dfedfeec042fd436e.jpeg)
10s后再次访问
从测试结果可以看到,当我们10s连续访问这个接口超过5的话,直接抛出异常了。等10s后,再次访问,接口恢复如初。基本上满足了我们的小目标。
结尾
以上就是小编今天分享的内容了,喜欢小编的小伙伴可以关注小编。下篇文章小编将带领大家用spring boot整合开源项目来重新写一个接口限流的demo。好了,转发+评论+点赞 这篇文章,私信小编【接口限流】可获得源码。