[Separation of front-end and back-end blogs] Study Notes 03 --- API request flow limiting

Ideas:

  1. Custom annotations define limit cycles and limit times
  2. The interceptor intercepts all requests. If an API using the current-limiting annotation is found, check whether the number of requests for this interface is recorded in redis. If so, judge that the maximum threshold of the custom annotation has not been exceeded. If the threshold is reached, the request will be intercepted, otherwise Increase the number of requests by 1 and release

 

Code: 

This code is an implementation of an Interceptor, which is used to limit the frequency of user access to certain interfaces. Specifically, it generates a Redis Key based on information such as the user's IP address, request method, and request URI, and then uses the incr() method of Redis to auto-increment the value of the Key to count the number of users accessing the interface. frequency. If the number of times a user accesses the interface exceeds the set maximum value, an error message will be returned and the user's access will be denied.

 

Among them, AccessLimit is a custom annotation, which is used to mark the access control information of the method on the method, including the access interval (seconds), the maximum number of visits (maxCount) and the error message (msg). In the preHandle() method, the HandlerMethod instance corresponding to the current request is obtained through the handler parameter, and then the AccessLimit annotation on the method is obtained from the instance to obtain access control information. If there is no AccessLimit annotation on the method, it means that the method does not require access control and passes directly through the interceptor.

 

In the specific implementation, the Objects.nonNull() method is used to judge whether the count variable is empty, which avoids a null pointer exception when the count is null. At the same time, when using the incr() method of Redis, you also need to pay attention to the possibility that the Redis connection may fail, so you need to capture and handle the exception.

/**
 * Redis拦截器
 * API请求限流
 * @author DarkClouds
 * @date 2023/05/10
 */
@Slf4j
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        boolean result = true;
        // Handler 是否为 HandlerMethod 实例
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
            //方法上没有访问控制的注解,直接通过
            if (accessLimit != null) {
                int seconds = accessLimit.seconds();
                int maxCount = accessLimit.maxCount();
                String ip = IpUtils.getIpAddress(request);
                String method = request.getMethod();
                String requestUri = request.getRequestURI();
                String redisKey = ip + ":" + method + ":" + requestUri;
                try {
                    Long count = redisService.incr(redisKey, 1L);
                    // 第一次访问
                    if (Objects.nonNull(count) && count == 1) {
                        redisService.setExpire(redisKey, seconds, TimeUnit.SECONDS);
                    } else if (count > maxCount) {
                        //触发限制时的消息提示返回给前端
                        WebUtils.renderString(response, JSON.toJSONString(Result.fail(accessLimit.msg())));
                        log.warn(redisKey + "请求次数超过每" + seconds + "秒" + maxCount + "次");
                        result = false;
                    }
                } catch (RedisConnectionFailureException e) {
                    log.error("redis错误: " + e.getMessage());
                    result = false;
                }
            }
        }
        return result;
    }
}
/**
 * Redis限流注解
 *
 * @author DarkClouds
 * @date 2023/05/10
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    /**
     * 限制周期(秒)
     */
    int seconds();

    /**
     * 规定周期内限制次数
     */
    int maxCount();

    /**
     * 触发限制时的消息提示
     */
    String msg() default "操作过于频繁请稍后再试";

}

demo

@AccessLimit(seconds = 60, maxCount = 3)
public void test() {
    //当60秒内连续请求该方法超过3次,将被限流,无法访问
}

Guess you like

Origin blog.csdn.net/a1404359447/article/details/130647943