spring中接口流量的控制
防止接口同一时间内对一个接口进行频繁的访问,可以对接口进行限流。
1.自定义注解,用来标识需要限流的接口。
package com.springweb.demo.limit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口流量控制
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {
/**
* 多少秒内
* @return
*/
long second() default 2L;
/**
* 最大访问次数
* @return
*/
long maxTime() default 1L;
long forbiddenTime() default 3L;
String apiUrl() default "";
}
2.使用拦截器Interceptor对使用注解的接口进行限流处理
package com.springweb.demo.limit;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Objects;
@Slf4j
public class AccessLimitInterceptor implements HandlerInterceptor {
private static HashMap<String, InterfaceInfo> accessMap = new HashMap<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod targetMethod = (HandlerMethod) handler;
AccessLimit targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class);
boolean isBrushForAllInterface = false;
long second = 0L, maxTime = 0L, forbiddenTime = 0L;
if (!Objects.isNull(targetClassAnnotation)) {
log.info("目标接口方法所在类上有@AccessLimit注解");
isBrushForAllInterface = true;
second = targetClassAnnotation.second();
maxTime = targetClassAnnotation.maxTime();
forbiddenTime = targetClassAnnotation.forbiddenTime();
}
String uri = request.getRequestURI();
// 目标方法上的@AccessLimit注解
AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class);
if (!Objects.isNull(accessLimit)) {
second = accessLimit.second();
maxTime = accessLimit.maxTime();
forbiddenTime = accessLimit.forbiddenTime();
if (isForbidden(second, maxTime, forbiddenTime, "登录用户唯一标识", uri)) {
return false;
}
} else {
if (isBrushForAllInterface && isForbidden(second, maxTime, forbiddenTime, "登录用户唯一标识", uri)) {
return false;
}
}
}
return true;
}
private boolean isForbidden(long second, long maxTime, long forbiddenTime, String id, String uri) {
String lockKey = id + "_" + uri;
InterfaceInfo interfaceInfo = accessMap.get(lockKey);
if (!Objects.isNull(interfaceInfo)) {
// 还在禁用中
if (LocalDateTime.now().compareTo(interfaceInfo.getForbiddenTime()) <= 0) {
return true;
}
// 判断两次接口访问的时间差是否超过限制
long diffSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), interfaceInfo.getAccessTime());
if (diffSeconds > second) {
accessMap.remove(lockKey);
}
}
// 判断此用户访问接口是否已经禁用
if (Objects.isNull(interfaceInfo)) {
// 还未被禁用
interfaceInfo = new InterfaceInfo();
interfaceInfo.setAccessTime(LocalDateTime.now());
interfaceInfo.setAccessCount(1);
} else {
if (interfaceInfo.getAccessCount() < maxTime) {
interfaceInfo.setAccessCount(interfaceInfo.getAccessCount() + 1);
} else {
// 禁止访问
interfaceInfo.setAccessTime(LocalDateTime.now());
interfaceInfo.setAccessCount(0);
// 设置禁止到的时间点
interfaceInfo.setForbiddenTime(LocalDateTime.now().plus(forbiddenTime, ChronoUnit.SECONDS));
accessMap.put(lockKey, interfaceInfo);
return true;
}
}
return false;
}
}
这里使用map处理 + InterfaceInfo.java对接口进行限流处理。也可以用redis进行处理。
package com.springweb.demo.limit;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class InterfaceInfo {
private LocalDateTime accessTime;
private LocalDateTime forbiddenTime;
private int accessCount;
}
3.对接口进行限流。
@AccessLimit使用默认属性。2s内只能访问一次,如果访问超过2次那么接下来的3s内拒绝这个接口的访问。
package com.springweb.demo.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import com.springweb.demo.limit.AccessLimit;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/hello")
@ApiSort(1)
@Tag(name = "实例功能")
public class HelloController {
@GetMapping("/helloString")
@ResponseBody
@Operation(summary = "输出hello", description = "hello接口", method = "GET")
@ApiOperationSupport(order = 1)
@AccessLimit
public String helloString(){
return "hello, spring-web-demo.";
}
}

浙公网安备 33010602011771号