自定义注解+拦截器实现访问限流
无论是在项目中 , 还是面试时 , 都经常会遇到需要限流的问题 , 今天主要讲解通过自定义注解 + 拦截器实现限流的这一方式。
为什么要限流?
现实生活中 , 某个店家给老年人发放鸡蛋 , 但是鸡蛋总数就那么多 , 如果有的人拿得多了 , 那么势必会出现一些问题 , 这样的结果就是部分人的体验不好 。限流的思想就是,控制每个人拿到鸡蛋的个数,拿完了就离开,保证每人都可以拿到三个鸡蛋。
回到网络上,同样也是这个道理,例如系统中需要发送短信验证 , 但是某个用户恶意访问该接口 , 导致带宽和金钱白白消耗 , 那么就要需要想办法限制该用户的访问。
限流思路
对系统访问进行限流,我们采用自定义注解 + 拦截器实现 :
代码实现
首先我们需要自定义一个注解 , 用于需要限流的接口上 :
package com.alex.annotation;
import java.lang.annotation.*;
/**
* @program: Blog
* @description: 限流注解类
* @author: Alex
* @create: 2023-05-22 13:59
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimit {
int seconds();
int maxCount();
}
其次 , 我们需要根据用户访问的ip进行限制 :
获取ip及Redis相关工具 , 详见gitee仓库;
最后 , 我们在拦截器中进行拦截操作 :
package com.alex.interceptor;
import com.alex.annotation.AccessLimit;
import com.alex.service.RedisService;
import com.alex.util.IpUtil;
import com.alex.util.Result;
import com.alibaba.fastjson.JSON;
import io.lettuce.core.RedisConnectionException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import static com.alex.constant.CommonConstant.APPLICATION_JSON;
/**
* @program: Blog
* @description: 访问限制拦截器
* @author: Alex
* @create: 2023-05-22 13:57
**/
@Log4j2
@Component
@SuppressWarnings("all")
public class AccessLimitInterceptor implements HandlerInterceptor {
@Resource
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法上的注解
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
if (accessLimit != null) {
long seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String key = IpUtil.getIpAddress(request) + "-" + handlerMethod.getMethod().getName();
try {
long q = redisService.incrExpire(key, seconds);
if (q > maxCount) {
render(response, Result.fail("请求过于频繁," + seconds + "秒后再试"));
log.warn(key + "请求次数超过每" + seconds + "秒" + maxCount + "次");
return false;
}
return true;
} catch (RedisConnectionException e) {
log.warn("redis错误: " + e.getMessage());
return false;
}
}
}
return true;
}
private void render (HttpServletResponse response, Result < ?>result) throws IOException {
response.setContentType(APPLICATION_JSON);
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(result);
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}
以上 , 我们就可以通过自定义注解 + 拦截器 + Redis + 用户IP实现拦截功能 , 只需在需要限流的接口上加@AccessLimit
注解 , 并且指定时间和最大访问次数。