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.";
    }

}

  

posted @ 2024-02-28 11:15  话祥  阅读(31)  评论(0编辑  收藏  举报