限制接口调用频率_基于Redis实现接口限流

pom.xml引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

由请求方IP组成Redis的key,请求次数自增1。达到阈值,即可抛出异常,提示请求者操作频繁

String.format("您的操作过于频繁,请%s分钟后再试! ", stringRedisTemplate.getExpire(key, TimeUnit.MINUTES))

这个逻辑可以放入Controller层的入口,也可结合AOP切面编程,给接口做调用频率限制。

 自定义注解

/**
 * 接口调用次数限制
 */
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 生命周期
public @interface Limit {

    /**
     * 阈值,达到阈值拒绝请求
     */
    long threshold() default 0;

    /**
     * 重置时间,重置时间结束方解除限制
     */
    long reset() default 0;

    /**
     * 重置时间单位,默认分钟,可自定义设置
     */
    TimeUnit unit() default MINUTES;

}

 切面逻辑

    /**
     * 接口调用次数限制注解逻辑实现
     */
    @Around("@annotation(org.ashe.xxx.Limit)")
    public Object limit(ProceedingJoinPoint joinPoint) throws Throwable {
        // 取出注解实例
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Limit limit = method.getAnnotation(Limit.class);
        // 获取ipAddress
        String ipAddress = jwtService.getClientIpAddress();
        String key = RedisKey.getKey(RedisKey.REQUEST_COUNT, ipAddress);
        if (limitIpCallThis(key)) {
            throw new ServiceException(String.format("发送短信验证码的次数受限,请%s分钟后再试", stringRedisTemplate.getExpire(key, limit.unit())));
        }
        return joinPoint.proceed();
    }

    private boolean limitIpCallThis(String key) {
        // 自增key,初始值为1
        Long increment = stringRedisTemplate.opsForValue().increment(key);
        switch (Objects.requireNonNull(increment).intValue()) {
            case 1 ->
                // 第一次调用,初始化过期时间60分钟
                    stringRedisTemplate.expire(key, 60, TimeUnit.MINUTES);
            case 5 ->
                // 达到阈值后,刷新过期时间为5分钟
                    stringRedisTemplate.expire(key, 5, TimeUnit.MINUTES);
            default -> {
            }
        }
        return increment > 5;
    }

 使用示例

    @PostMapping("/send")
    @Limit(threshold = 5, reset = 5) // (1小时内)某个ip连续请求次数 > 5次后拒绝,5分钟后解除限制
    public ResponseEntity<Void> sendCode(@RequestBody MobileDTO dto){
        service.sendCode(dto.getMobile());
        return ResponseEntity.ok().build();
    }

我想起之前调用亚马逊某个接口时,其限流措施就是每天对应token的使用次数,由于每个账号的token不同,那么这个token就是对应的业务主键,以此做基准来限制调用频率。

此处代码是验证码登录接口,请求还未携带token,因此key选择ip作为限制基准。

posted @ 2023-08-01 10:02  Ashe|||^_^  阅读(310)  评论(1编辑  收藏  举报