当前位置:   article > 正文

SpringBoot自定义接口限流注解(拦截器实现,AOP实现)_springboot aop限流器如何放行部分接口

springboot aop限流器如何放行部分接口
  1. 拦截器版

实现思路
1.通过拦截器,读取方法上的注解
2.累计请求数量,进行限流
1.1 定义注解RequestLimit
  1. package com.zhf.model.annotation;
  2. import java.lang.annotation.*;
  3. import java.util.concurrent.TimeUnit;
  4. @Documented
  5. @Inherited
  6. @Target({ElementType.METHOD,ElementType.TYPE})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface RequestLimit {
  9. /**
  10. * 限流的时间单位
  11. */
  12. TimeUnit timeUnit() default TimeUnit.SECONDS;
  13. /**
  14. * 限流的时长
  15. */
  16. int limit() default 1;
  17. /**
  18. * 最大限流量
  19. * @return
  20. */
  21. int maxCount() default 1;
  22. }
1.2 定义拦截器RequestLimitInterceptor
  1. package com.zhf.model.interceptor;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.zhf.model.annotation.RequestLimit;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.method.HandlerMethod;
  6. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.io.IOException;
  10. import java.io.PrintWriter;
  11. import java.lang.annotation.Annotation;
  12. import java.lang.reflect.Method;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. import java.util.Timer;
  16. import java.util.TimerTask;
  17. import java.util.concurrent.TimeUnit;
  18. @Component
  19. public class RequestLimitInterceptor extends HandlerInterceptorAdapter {
  20. /**
  21. * 限流Map,懒得搭建Redis,暂时放在Map里面,然后通过定时任务实现限流
  22. * 实际业务中可以放在Redis,利用过期时间限流
  23. */
  24. private final Map<String,Integer> map = new HashMap<>();
  25. /**
  26. * 拦截请求执行的方法
  27. * @param request
  28. * @param response
  29. * @param handler
  30. * @return
  31. * @throws Exception
  32. */
  33. @Override
  34. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  35. System.out.println("限流的拦截器!");
  36. //判断处理类是否为HandlerMethod
  37. if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
  38. //进行强制转换
  39. HandlerMethod handlerMethod = (HandlerMethod)handler;
  40. //获取拦截的方法
  41. Method method = handlerMethod.getMethod();
  42. //获取方法上的注解对象,看是否被RequestLimit修饰
  43. RequestLimit limiter = getTagAnnotation(method, RequestLimit.class);
  44. //判断是否限流
  45. if(null != limiter){
  46. if(isLimit(request,limiter)){
  47. responseOut(response,limiter.maxCount());
  48. return false;
  49. }
  50. }
  51. }
  52. return super.preHandle(request, response, handler);
  53. }
  54. /**
  55. * 封装返回结果
  56. */
  57. private void responseOut(HttpServletResponse response,Integer limit) throws IOException {
  58. response.setCharacterEncoding("UTF-8");
  59. response.setContentType("application/json; charset=utf-8");
  60. PrintWriter writer = response.getWriter();
  61. Map<String,String> resultMap = new HashMap<>();
  62. resultMap.put("status","502");
  63. resultMap.put("msg","接口超出最大请求数:" + limit);
  64. String s = JSONObject.toJSON(resultMap).toString();
  65. writer.append(s);
  66. }
  67. //获取处理类上的注解
  68. public <T extends Annotation> T getTagAnnotation(Method method, Class<T> annotationClass){
  69. //获取方法中是否有相关注解
  70. T methodAnnotation = method.getAnnotation(annotationClass);
  71. //获取类上是否有相关注解
  72. T classAnnotation = method.getDeclaringClass().getAnnotation(annotationClass);
  73. //判断是否存在相关注解
  74. if(null != methodAnnotation){
  75. return methodAnnotation;
  76. }else return classAnnotation;
  77. }
  78. /**
  79. * 判断接口是否限流,通过请求的SessionId进行限流
  80. */
  81. public boolean isLimit(HttpServletRequest request,RequestLimit limiter){
  82. //获取请求的SessionID
  83. String id = request.getSession().getId();
  84. //查看是否在限流map里面
  85. Integer num = map.get(id);
  86. System.out.println("SessionId:" + id + "\n" + "num:" + num + "\n" + "limiterCount:" + limiter.maxCount() + "\n" + "limit:" + limiter.limit());
  87. //没有则初始化限流map,并创建定时任务(解除限流)
  88. if(null == num){
  89. //初始化计数器
  90. map.put(id,1);
  91. //创建定时器任务,删除限流器
  92. Timer timer = new Timer();
  93. //获取限流的时间毫秒数
  94. long delay = getDelay(limiter.timeUnit(), limiter.limit());
  95. timer.schedule(new TimerTask() {
  96. @Override
  97. public void run() {
  98. System.out.println("删除任务执行");
  99. map.remove(id);
  100. }
  101. },delay);
  102. }else{
  103. //累加请求
  104. ++num;
  105. //判断是否超出最大限流次数
  106. if(num > limiter.maxCount()){
  107. return true;
  108. }
  109. //更新计数器
  110. map.put(id,num);
  111. }
  112. return false;
  113. }
  114. /**
  115. * 获取限流时间,总共限流的毫秒数
  116. * @param timeUnit
  117. * @param limit
  118. * @return
  119. */
  120. public long getDelay(TimeUnit timeUnit,Integer limit){
  121. if(null == timeUnit || limit == 0){
  122. return 0;
  123. }
  124. switch (timeUnit){
  125. case MILLISECONDS:
  126. return limit;
  127. case MINUTES:
  128. return limit*60*1000;
  129. case HOURS:
  130. return limit*60*60*1000;
  131. default:
  132. return limit*1000;
  133. }
  134. }
  135. }
1.3 将自定义拦截器加入到Spring的拦截器
  1. package com.zhf.model.config;
  2. import com.zhf.model.interceptor.RequestLimitInterceptor;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6. import javax.annotation.Resource;
  7. @Component
  8. public class WebMVCConfig implements WebMvcConfigurer {
  9. @Resource
  10. RequestLimitInterceptor limiter;
  11. @Override
  12. public void addInterceptors(InterceptorRegistry registry) {
  13. registry.addInterceptor(limiter);
  14. WebMvcConfigurer.super.addInterceptors(registry);
  15. }
  16. }
1.4 创建MainController进行测试
为了方便测试,这里设置为1分钟5次
  1. package com.zhf.model.controller;
  2. import com.zhf.model.annotation.RequestLimit;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import java.util.concurrent.TimeUnit;
  6. @RestController
  7. @RequestMapping("/main")
  8. public class MainController {
  9. @RequestMapping("/lock")
  10. @RequestLimit(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
  11. public String testLock(){
  12. return "ok";
  13. }
  14. }
  • 16:19点击5次,接口限流

  • 16:20再次点击,限流解除

  1. AOP实现

  • 没依赖先添加依赖

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-aspects</artifactId>
  4. <version>4.3.7.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-aop</artifactId>
  9. <version>4.3.7.RELEASE</version>
  10. </dependency>
2.1 创建限流的注解类RateLimiter.java
  1. package com.zhf.model.annotation;
  2. import java.lang.annotation.*;
  3. import java.util.concurrent.TimeUnit;
  4. @Target(ElementType.METHOD)
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. public @interface RateLimiter {
  8. /**
  9. * 限流的时间单位
  10. */
  11. TimeUnit timeUnit() default TimeUnit.SECONDS;
  12. /**
  13. * 限流的时长
  14. */
  15. int limit() default 1;
  16. /**
  17. * 最大限流量
  18. * @return
  19. */
  20. int maxCount() default 1;
  21. }

2.2 创建切面RateLimiterAspect.java

这里直接使用Redis(String)的过期时间作为限流的计数器
直接使用String不方便管理,可以使用RedisScript进行管理
  1. package com.zhf.model.aop;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.zhf.model.annotation.RateLimiter;
  4. import com.zhf.model.exception.CommonException;
  5. import org.aspectj.lang.JoinPoint;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Before;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.data.redis.core.RedisTemplate;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.context.request.RequestContextHolder;
  12. import org.springframework.web.context.request.ServletRequestAttributes;
  13. import javax.servlet.http.HttpServletResponse;
  14. import java.io.IOException;
  15. import java.io.PrintWriter;
  16. import java.util.HashMap;
  17. import java.util.Map;
  18. /**
  19. * 限流处理类
  20. * @Aspect 声明该类为切面
  21. */
  22. @Aspect
  23. @Component
  24. public class RateLimiterAspect
  25. {
  26. @Autowired
  27. private RedisTemplate redisTemplate;
  28. /**
  29. * 执行前置方法,"@annotation(rateLimiter)"在注解rateLimiter之前执行
  30. * @param point
  31. * @param rateLimiter
  32. * @throws Throwable
  33. */
  34. @Before("@annotation(rateLimiter)")
  35. public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
  36. {
  37. //获取请求
  38. ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
  39. //获取SessionID
  40. String id = attributes.getRequest().getSession().getId();
  41. //这里使用redis过期时间作限流
  42. Integer count = (Integer)redisTemplate.opsForValue().get(id);
  43. //如果第一次请求或上次限流已经解除
  44. System.out.println("Count:" + count + ";TimeUnit:" + rateLimiter.timeUnit());
  45. if(null == count){
  46. //初始化限流器
  47. redisTemplate.opsForValue().set(id,1,rateLimiter.limit(),rateLimiter.timeUnit());
  48. }else{
  49. //累加
  50. ++count;
  51. redisTemplate.opsForValue().set(id,count,0);
  52. if(count > rateLimiter.maxCount()){
  53. //抛出自定义异常码,然后统一返回
  54. throw new CommonException(480);
  55. }
  56. }
  57. }
  58. }
2.3 自定义异常类CommonException,同一异常返回
  • 自定义异常类

  1. package com.zhf.model.exception;
  2. import lombok.Data;
  3. @Data
  4. public class CommonException extends RuntimeException{
  5. private int code;
  6. private String msg;
  7. public CommonException() {
  8. }
  9. public CommonException(int code) {
  10. this.code = code;
  11. }
  12. public CommonException(int code, String msg) {
  13. this.code = code;
  14. this.msg = msg;
  15. }
  16. }
  1. package com.zhf.model.handler;
  2. import com.zhf.model.exception.CommonException;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.web.bind.annotation.*;
  6. import java.time.format.DateTimeParseException;
  7. @RestControllerAdvice
  8. @Slf4j
  9. public class CommonExceptionHandler {
  10. @ExceptionHandler(NullPointerException.class)
  11. @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
  12. public ReturnResult handleTypeMismatchException(NullPointerException ex){
  13. log.debug(ex.getMessage());
  14. return new ReturnResult(500,"空指针异常");
  15. }
  16. @ExceptionHandler(ArithmeticException.class)
  17. @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
  18. public ReturnResult handleArithmeticException(ArithmeticException ex){
  19. ex.printStackTrace();
  20. return new ReturnResult(500,"被除数不能为零");
  21. }
  22. @ExceptionHandler(DateTimeParseException.class)
  23. @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
  24. public ReturnResult handleDateTimeParseException(DateTimeParseException ex){
  25. ex.printStackTrace();
  26. return new ReturnResult(500,"时间转换格式错误");
  27. }
  28. @ExceptionHandler(CommonException.class)
  29. @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
  30. public ReturnResult handleCommonException(CommonException ex){
  31. ex.printStackTrace();
  32. if(ex.getCode() == 480){
  33. return new ReturnResult(480,"接口限流");
  34. }
  35. if(ex.getCode() == 502){
  36. return new ReturnResult(502,"自定义异常处理502");
  37. }
  38. return new ReturnResult(500,"时间转换格式错误");
  39. }
  40. }
2.4创建测试类
方便测试,一分钟五次
  1. package com.zhf.model.controller;
  2. import com.zhf.model.annotation.RateLimiter;
  3. import com.zhf.model.annotation.RequestLimit;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import java.util.concurrent.TimeUnit;
  7. @RestController
  8. @RequestMapping("/main")
  9. public class MainController {
  10. /**
  11. * 测试限流
  12. * @return
  13. */
  14. @RequestMapping("/lock")
  15. @RequestLimit(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
  16. public String testLock(){
  17. return "ok";
  18. }
  19. @RequestMapping("/testAopLimit")
  20. @RateLimiter(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
  21. public String testAopLimit(){
  22. return "ok";
  23. }
  24. }
18:00点击5次,接口限流
18:01再次请求,解除限流

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号