1.如何实现
限制接口请求次数的需要在接口请求之前做操作判断,所以可以通过aop或者拦截器来实现
2.具体实现
1.引入相关jar包
expiringmap是可以设置过期时间的map并且线程安全
<!-- 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>
2.新增自定义注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimiting {
long time() default 60000; // 限制时间 单位:毫秒(当前一分钟)
int value() default 5; // 允许请求的次数
}
3.新增自定义切面类
package com.test.aop; import com.test.interface1.CurrentLimiting; import jakarta.servlet.http.HttpServletRequest; import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @Aspect @Component public class CurrentLimitingAspect { private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> map = new ConcurrentHashMap<>(); /** * 层切点 */ @Pointcut("@annotation(currentLimiting)") public void controllerAspect(CurrentLimiting currentLimiting) { } @Around("controllerAspect(currentLimiting)") public Object doAround(ProceedingJoinPoint pjp, CurrentLimiting currentLimiting) throws Throwable { // 获得request对象 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); // 获取Map value对象, 如果没有则返回默认值 // //getOrDefault获取参数,获取不到则给默认值 ExpiringMap<String, Integer> em = map.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build()); Integer Count = em.getOrDefault(request.getRemoteAddr(), 0); if (Count >= currentLimiting.value()) { // 超过次数,不执行目标方法 return "接口请求超过次数"; } else if (Count == 0) { // 第一次请求时,设置有效时间 em.put(request.getRemoteAddr(), Count + 1, ExpirationPolicy.CREATED, currentLimiting.time(), TimeUnit.MILLISECONDS); } else { // 未超过次数, 记录加一 em.put(request.getRemoteAddr(), Count + 1); } map.put(request.getRequestURI(), em); // result的值就是被拦截方法的返回值 Object result = pjp.proceed(); return result; } }
4.介绍实现方法
使用 ConcurrentHashMap(线程安全)key存储 接口地址, value 存储 expiringmap(可设置过期时间的map)类型的 ip集合, expiringmap key 存储 ip, value 存储 次数。
5.测试接口
@RestController
@RequestMapping("/test")
public class ClassController {
@GetMapping("/test")
@CurrentLimiting()
public String test(){
return "调用成功";
}
}
经测试调用5次之后返回口请求超过次数,更换另一ip则开始重新计算5次
如何不需要限制ip单纯限制接口次数,则只需要使用expiringmap(可设置过期时间的map)key存储 接口地址,value 存储次数。