利用RateLimiter加上自定义Aop注解限制某个接口的访问频率

实际工作中有这种需求:
某个接口被不同的外部ip大量访问,且每个IP的访问频率很高。为了节约后端服务器资源,于是想通过
RateLimiter令牌桶+自定义Aop注解来限制同一ip在单位时间内只能访问固定的次数,超过这个次数的请求被拦截掉。具体实现如下:
1、自定义Aop注解

package com.fancetech.tools.annotation;

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})
public @interface RequestLimitAnnotate {


    /**
     * 请求次数限制
     */
    long requestLimitNum();


    /**
     * 限流的时间间隔,单位秒
     */
    long timeInterval();
}

2、注解实现类

@Order(3)
@Aspect
@Component
@Slf4j
public class RequestLimitAop extends CBaseController {


    /**
     * 每秒产生的令牌数
     */
    private double permitsPerSecond;


	@Pointcut("@annotation(com.fancetech.tools.annotation.RequestLimitAnnotate)")
    public void annotationMethod() {
    }

    /**
     * google的缓存,maximumSize 存储的最大缓存个数,当缓存达到设置数时,会采取默认的淘汰策略(即清楚最近访问频率不高的key)
     */
    private final LoadingCache<String, RateLimiter> ipRequestCaches = CacheBuilder.newBuilder()
            .maximumSize(10000)// 设置缓存个数(是key的个数)
            .expireAfterAccess(180, TimeUnit.SECONDS) //从最后一次访问该缓存计时开始,在180s内,如果该缓存没有再被访问,则清楚该缓存
            .build(new CacheLoader<String, RateLimiter>() {
                @Override
                public RateLimiter load(String ipUrl)  {
                    //log.info("permitsPerSecond:{}",permitsPerSecond);
                    return RateLimiter.create(permitsPerSecond);
                }
            });
    
	@Around("annotationMethod()")
    public Object interceptRequest(ProceedingJoinPoint joinPoint) {
        try {
            String ip = WebUtils.getValidIp(request);
            if (EmptyUtils.isEmpty(ip)) {
                log.warn("当前请求是内部服务器发出,不需要拦截");
                return joinPoint.proceed();
            }
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            RequestLimitAnnotate annotation = method.getAnnotation(RequestLimitAnnotate.class);
            String url = request.getRequestURI();
            //次数限制
            long limitNum = annotation.requestLimitNum();
            //时间间隔
            long timeInterval = annotation.timeInterval();
            this.permitsPerSecond = (double) limitNum / timeInterval;
            RateLimiter rateLimiter = ipRequestCaches.get(ip + url);
            if (rateLimiter.tryAcquire()) {
                return joinPoint.proceed();
            }
        } catch (Throwable e) {
            log.error("request aop 异常", e);
        }
        throw new RequestLimitException("当前请求次数频繁,请稍后再试。");
    }
}
         

3、具体细节讲解:
a、首先 this.permitsPerSecond = (double) limitNum / timeInterval;计算出每秒需要该接口通过的访问次数,也就是令牌桶产生令牌的个数。
b、Guava 缓存库中的 CacheBuilder.newBuilder() 方法来创建一个 LoadingCache 实例。LoadingCache 是 Guava 提供的一种特殊的缓存实现,它能够在缓存未命中时自动加载缓存值,也就是说当调用ipRequestCaches.get(key)方法时,如果没有这个缓存,则自动创建缓存。如上图所示,创建缓存值时,key=ip+url,value=RateLimiter.create(permitsPerSecond) 这个RateLimiter对象。
maximumSize(10000) 表示这个缓存对象里面只能存10000个键值对。
c、RateLimiter rateLimiter = ipRequestCaches.get(ip + url); 当过来一个ip后,访问缓存里面的令牌桶对象,如果能拿到令牌桶对象并能获取到令牌,则执行接口的业务方法,否则抛出业务异常。
d、通过上述就可以实现每个ip在单位时间内(其实就是每秒内)只能访问某个接口指定的次数
4、实际应用

    @RequestLimitAnnotate(requestLimitNum = 6,timeInterval = 10)
    @ApiOperation(value = "生成访问时长", notes = "生成访问时长(定时调用时间就是randomTime单位是秒)")
    @PostMapping("/brow/time")
    public ResultBean<Object> shareBrowTime(@RequestBody @Valid KbDataFlowVo kbDataFlowVo) {
        outInfoService.kbShareBrowTime(kbDataFlowVo.getBrowId(), kbDataFlowVo.getRandomTime(), kbDataFlowVo.getType());
        return this.success();
    }
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要通过自定义注解AOP来实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问接口。其他接口仍然需要进行Token验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值