【前后端分离博客】学习笔记03 --- API请求限流

思路:

  1. 自定义注解定义限制周期和限制次数
  2. 拦截器拦截所有请求,如果发现使用了限流注解的API,查看redis里是否记录了该接口的请求次数,如果有,就判断没有超过自定义注解的最大阈值,达到阈值就拦截该请求,否则请求次数加1并放行

代码实现: 

这段代码是一个拦截器(Interceptor)的实现,它用于限制用户访问某些接口的频率。具体来说,它会根据用户的 IP 地址、请求方法和请求 URI 等信息生成一个 Redis 的 Key,然后使用 Redis 的 incr() 方法对该 Key 的值进行自增操作,以统计用户访问该接口的次数。如果用户访问该接口的次数超过了设定的最大值,就会返回一个错误信息,并拒绝该用户的访问。

其中,AccessLimit 是一个自定义的注解,用于在方法上标注该方法的访问控制信息,包括访问时间间隔(seconds)、最大访问次数(maxCount)和错误提示信息(msg)。在 preHandle() 方法中,会通过 handler 参数获取当前请求对应的 HandlerMethod 实例,然后从该实例中获取方法上的 AccessLimit 注解,以获取访问控制信息。如果方法上没有 AccessLimit 注解,就表示该方法不需要进行访问控制,直接通过拦截器。

在具体实现中,使用了 Objects.nonNull() 方法来判断 count 变量是否为空,避免了在 count 为 null 时出现空指针异常。同时,在使用 Redis 的 incr() 方法时,也需要注意可能会发生 Redis 连接失败的情况,因此需要对该异常进行捕获和处理。

/**
 * 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 "操作过于频繁请稍后再试";

}

使用演示

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

猜你喜欢

转载自blog.csdn.net/a1404359447/article/details/130647943