如何限制前端的请求次数
最近学习缓存击穿
的时候,解决方法是限流,前端限制请求次数。故通过后端来对前端的请求做限流次数。
这里首先不用redis
方法,这里采用通过Aop切面的方式来限制请求次数
创建限流注解
/**
* 限流接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RequestLimit {
// time是调用接口的间隔时间,默认是 5000 毫秒
long time() default 5000;
}
获取单例的map对象存放数据
SubmitBufferSingleton
用来获取唯一的 HashMap<String, Long>,其中的Value是从 1970-01-01T00:00:00Z(协调世界时,UTC)到当前时间点之间的毫秒数
/**
* 获取单例map对象
*/
public class SubmitBufferSingleton {
private static final HashMap<String, Long> map = new HashMap<>();
private SubmitBufferSingleton() {
}
public static HashMap<String, Long> getInstance() {
return map;
}
}
创建切面的任务
@Aspect
@Component
@Slf4j
public class NoRepeatSubmitAop {
// 定义切面
@Pointcut("@annotation(com.zhuang.aspect.annotation.RequestLimit)")
private void noRepeatSubmitAop() {
}
@Synchronized // 作用是创建一个互斥锁,保证只有一个线程对 SubmitBufferSingleton.getInstance() 这个变量进行修改。
@Around("noRepeatSubmitAop()&&@annotation(nrs)")
public Object around(ProceedingJoinPoint pjp, RequestLimit nrs) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
String key = getIp(request) + " :" + request.getServletPath();
long time = nrs.time();
Object o = pjp.proceed();
HashMap<String, Long> hashMap = SubmitBufferSingleton.getInstance();
long nowTime = Instant.now().toEpochMilli();
if (!hashMap.containsKey(key)) {
hashMap.put(key, nowTime + time);
return o;
} else {
if (nowTime > hashMap.get(key)) {
hashMap.put(key, nowTime + time);
return o;
} else {
log.error("操作过于频繁 {}", key);
return "操作过于频繁";
}
}
}
// 获取调用者ip
private static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
创建定时任务
这里设置一个定时器在每秒清理一下HashMap数据,防止 HashMap<String, Long> 越来越大。将 HashMap<String, Long> 从 NoRepeatSubmitAop 中抽出来的优点在这里也体现出来了
@Component
@Slf4j
public class NoRepeatSubmitTask {
@Scheduled(cron = "* * * * * ?")
public void start() {
HashMap<String, Long> hashMap = SubmitBufferSingleton.getInstance();
log.warn(hashMap.toString());
for (Map.Entry<String, Long> next : hashMap.entrySet()) {
String key = next.getKey();
Long value = next.getValue();
if (value > Instant.now().toEpochMilli()) {
hashMap.remove(key);
}
}
// 如果对时间没有特别严格的要求就直接clear
// hashMap.clear();
}
}
主启动类开启注解
@EnableScheduling //开启定时任务
测试接口,内部方法不用纠结
@PostMapping("get")
@RequestLimit(time = 1000)
public ResultVO getUser(@RequestBody(required = false) UserDto userDto) {
try {
if (!ObjectUtils.isEmpty(userDto)) {
userService.list(userDto);
}
} catch (Exception e) {
log.error(e.getMessage());
return ResultVOUtil.error(ResultEnum.ERROR);
}
return ResultVOUtil.success(ResultEnum.SUCCESS);
}
第一次请求成功
多次请求后就会限流
查看日志,请求过于频繁了!