1.如何实现
限制接口请求次数的需要在接口请求之前做操作判断,所以可以通过aop或者拦截器来实现
2.具体实现
1.引入相关jar包
expiringmap是可以设置过期时间的map并且线程安全,也可以通过定时任务定时清空普通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.wxw.userdemo.aop;
import com.wxw.userdemo.annotation.CurrentLimiting;
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 javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @author wxw
* @data 2022/3/12 11 :21
* @description
*/
@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("/class")
public class ClassController {
@GetMapping("/testCl")
@CurrentLimiting()
public String test(){
return "调用成功";
}
}
经测试调用5次之后返回口请求超过次数,更换另一ip则开始重新计算5次
?如何不需要限制ip单纯限制接口次数,则只需要使用expiringmap(可设置过期时间的map)key存储 接口地址,value 存储次数。
|