一、引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
二、自定义注解及限流拦截器
自定义注解:@Limiter
package com.haitai.web.device.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 限流注解
*
* @Author xincheng.du
* @Date 2023/7/5 17:13
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
* 每秒创建令牌个数,默认:0.5
* 代表每秒允许通过的请求数为0.5个,即2秒只能通过一个请求
*/
double qps() default 0.5D;
/**
* 获取令牌等待超时时间 默认:500
*/
long timeout() default 500;
/**
* 超时时间单位 默认:毫秒
*/
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
/**
* 限速提示信息
*/
String msg() default "访问过于频繁,请稍候再试";
}
限流拦截器:RequestLimitingInterceptor
package com.haitai.web.device.interceptor;
import com.google.common.util.concurrent.RateLimiter;
import com.haitai.web.device.annotation.Limiter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 限流拦截器
*
* @Author xincheng.du
* @Date 2023/7/5 15:38
*/
@Component
@Slf4j
public class RequestLimitingInterceptor implements HandlerInterceptor {
private final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
@Override
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Limiter rateLimit = handlerMethod.getMethodAnnotation(Limiter.class);
// 判断是否有注解
if (rateLimit != null) {
// 获取请求url
String url = request.getRequestURI();
// 判断map集合中是否有创建好的令牌桶
rateLimiterMap.put(url, rateLimiterMap.computeIfAbsent(url, key -> RateLimiter.create(rateLimit.qps())));
RateLimiter rateLimiter = rateLimiterMap.get(url);
// 获取令牌
boolean acquire = rateLimiter.tryAcquire(rateLimit.timeout(), rateLimit.timeunit());
if (!acquire) {
log.warn("请求被限流,url:{}", request.getServletPath());
throw new RuntimeException(rateLimit.msg());
}
}
}
return true;
}
}
三、测试
package com.haitai.web.controller.device;
import com.haitai.common.annotation.Anonymous;
import com.haitai.web.device.annotation.Limiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author xincheng.du
* @Date 2023/7/24 10:04
*/
@RestController
@RequestMapping("/test")
public class LimiterTestController {
@Anonymous // 允许匿名访问
@Limiter(qps = 0.2D) // 5秒一次
@GetMapping("/limiter")
public void limiter() {
System.out.println("=============正常请求=============");
}
}
5秒内第一次请求:
5秒内第二次请求: