aop切面实现一定时间内限制接口访问次数
还是查svn的tags信息,框架里封装的svn接口效率实在有点墨迹,连续点个几次就挂了。
首先我想的是做缓存,查出所有的项目tags放到redis,定时任务定期更新svn数据,但是时效性不行,做到5分钟一updata还是满足不了一群程序员的需求(内部小工具);
然后我又去找svn提供的其他接口,大都不尽人意,效率上七七八八;
最后还是选择了做接口访问次数限制,通过限制访问频率控制接口不会因为连续访问svn连接超时或访问超时挂掉。
原理:自定义注解,把注解添加到我们的接口上;定义一个切面,执行方法前去ExpiringMap查询该IP在规定时间内请求了多少次,如超过次数则直接返回请求失败。
1.自定义一个注解RequestLimit
1 @Documented 2 @Target(ElementType.METHOD) // 说明该注解只能放在方法上面 3 @Retention(RetentionPolicy.RUNTIME) 4 public @interface RequestLimit { 5 long time() default 2000; // 限制时间 单位:毫秒 6 int count() default 1; // 允许请求的次数 7 }
2.自定义切面判定访问频率
1 @Aspect 2 @Component 3 public class RequestLimitAspect { 4 5 private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>(); 6 7 // 定义切点 8 // 让所有有@LimitRequest注解的方法都执行切面方法 9 @Pointcut("@annotation(requestLimit)") 10 public void excudeService(RequestLimit requestLimit) { 11 } 12 13 @Around("excudeService(requestLimit)") 14 public ResultVo doAround(ProceedingJoinPoint pjp, RequestLimit requestLimit) throws Throwable { 15 16 // 获得request对象 17 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); 18 ServletRequestAttributes sra = (ServletRequestAttributes) ra; 19 HttpServletRequest request = sra.getRequest(); 20 21 // 获取Map对象, 如果没有则返回默认值 22 // 第一个参数是key, 第二个参数是默认值 23 ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build()); 24 Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0); 25 26 27 28 if (uCount >= requestLimit.count()) { // 超过次数,不执行目标方法 29 return Result.error(ResultEnum.FAILED.getCode(),"请求频率太快啦~~"); 30 } else if (uCount == 0){ // 第一次请求时,设置有效时间 31 // /** Expires entries based on when they were last accessed */ 32 // ACCESSED, 33 // /** Expires entries based on when they were created */ 34 // CREATED; 35 uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, requestLimit.time(), TimeUnit.MILLISECONDS); 36 } else { // 未超过次数, 记录加一 37 uc.put(request.getRemoteAddr(), uCount + 1); 38 } 39 book.put(request.getRequestURI(), uc); 40 41 // result的值就是被拦截方法的返回值 42 Object result = pjp.proceed(); 43 44 return Result.success(result); 45 } 46 47 }
3.接口加上注解即可食用
1 @GetMapping("/getTags") 2 @RequestLimit(count = 2) 3 public ResultVo<List<String>> getProjectTags() { 4 return Result.success(); 5 }
用到的maven依赖
<!-- AOP依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.5.RELEASE</version> </dependency> <!-- Map依赖 --> <dependency> <groupId>net.jodah</groupId> <artifactId>expiringmap</artifactId> <version>0.5.8</version> </dependency>