1、引入依赖jar
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>4.10.0</version>
</dependency>
2、实现spring 拦截器
@Component
public class AccessLimitInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = LoggerFactory.getLogger(AccessLimitInterceptor.class);
@Autowired
private IApiInterfaceLogService interfaceLogService;
@Autowired
private CommonUtil commonUtil;
/**
* 接口请求限流
*/
@Value("${my.request.limitSwitch}")
private String limitSwitch;
/**
* 单机网关限流用一个ConcurrentHashMap来存储 bucket,
* 如果是分布式集群限流的话,可以采用 Redis等分布式解决方案
*/
private static final Map<String, Bucket> LOCAL_CACHE = new ConcurrentHashMap<>();
/**
* 桶的最大容量,即能装载 Token 的最大数量
*/
@Value("${my.limit.capacity}")
private int capacity = 5;
/**
* 每次 Token 补充量
*/
@Value("${my.limit.refillTokens}")
private int refillTokens = 2 ;
/**
*补充 Token 的时间间隔
*/
Duration refillDuration = Duration.ofSeconds(1);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 增加限流控制开关
if (!"Y".equalsIgnoreCase(limitSwitch)) {
return true;
}
long requestTime = System.currentTimeMillis();
ResultVo resultVo = new ResultVo(ResponseStateCode.FAILED.getCode(), ResponseStateCode.FAILED.getMsg());
try {
// Handler 是否为 HandlerMethod 实例
if (handler instanceof HandlerMethod) {
// 强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法
Method method = handlerMethod.getMethod();
// 是否有AccessLimit注解
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
// 获取注解内容信息
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int limitCount = accessLimit.count();// 请求次数
int limitTime = accessLimit.period();// 请求时间范围
String key = null;
String requestIp = NetWorkUtils.getIpAddr(request);
String requestURI = request.getRequestURI();
StringBuffer defaultKeyBuf = new StringBuffer("limit_");
LimitType limitType = accessLimit.limitType();
switch (limitType) {
case IP:
key = defaultKeyBuf.append(requestIp).toString();
break;
case CUSTOMER:
// 如果此处想根据表达式或者一些规则生成
key = defaultKeyBuf.append(accessLimit.key()).toString();
break;
case IP_API:
// 根据 IP + API 限流 (默认)
key = defaultKeyBuf.append(requestIp).append("_").append(requestURI).toString();
break;
default:
key = defaultKeyBuf.append(StringUtils.upperCase(method.getName())).toString();
}
//新的限流方式,先对个别接口使用
Bucket bucket = LOCAL_CACHE.computeIfAbsent(key, k -> createNewBucket());
logger.info("common api={} ,令牌通可用的Token数量={} ", key, bucket.getAvailableTokens());
if (bucket.tryConsume(1)) {
return true;
} else {
// 请求过于频繁
resultVo = new ResultVo(ResponseStateCode.API_REQUEST_TOO_MUCH.getCode(),
ResponseStateCode.API_REQUEST_TOO_MUCH.getMsg());
SendMsgUtil.contructJsonResponse(response, resultVo);
BaseBean baseBean = commonUtil.getBaseBeanFromRequest(request);
interfaceLogService.insertAsync(NetWorkUtils.getIpAddr(request), requestTime, request.getRequestURI(),
((HandlerMethod) handler).getBean().getClass().getName(), baseBean, resultVo);
return false;
}
}
} catch (Exception e) {
logger.error("AccessLimitInterceptor error", e);
SendMsgUtil.contructJsonResponse(response, resultVo);
BaseBean baseBean = commonUtil.getBaseBeanFromRequest(request);
interfaceLogService.insertAsync(NetWorkUtils.getIpAddr(request), requestTime, request.getRequestURI(),
null, baseBean, resultVo);
return false;
}
return true;
}
private Bucket createNewBucket() {
Refill refill = Refill.of(refillTokens, refillDuration);
Bandwidth limit = Bandwidth.classic(capacity, refill);
return Bucket4j.builder().addLimit(limit).build();
}
public static Map<String, Bucket> getLocalCache() {
return LOCAL_CACHE;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public int getRefillTokens() {
return refillTokens;
}
public void setRefillTokens(int refillTokens) {
this.refillTokens = refillTokens;
}
public Duration getRefillDuration() {
return refillDuration;
}
public void setRefillDuration(Duration refillDuration) {
this.refillDuration = refillDuration;
}
}