springboot实现自定义注解限流
最近搭建的博客网站,详情被人刷了,特意以此来提醒该加限流处理了
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义注解实现,默认10秒内只能请求5次,当然这个是根据自己的实际情况修改
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
//5次
int count() default 5;
//10秒
int second() default 10;
}
aop
/**
* 限流注解
* Created by PeakGao on 2023/3/2.
*/
@Aspect
@Component
public class IpLimitAspect extends AccessLimitIntercept {
@Pointcut("@annotation(com.fyg.common.annotation.RateLimit)")
public void rateLimit() {
}
@Before("rateLimit()")
public void before(JoinPoint point) throws IOException, InterruptedException {
long beginTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
RateLimit ipLimit = method.getAnnotation(RateLimit.class);
if (ipLimit != null) {
//注解上的描述
sysLog.setOperation(String.valueOf(ipLimit.count()) + String.valueOf(ipLimit.second()));
}
//请求的参数
Object[] args = point.getArgs();
try {
String params = new Gson().toJson(args);
sysLog.setParams(params);
} catch (Exception e) {
}
//请求的方法名
String className = point.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
HttpServletResponse response = HttpContextUtils.getHttpServletResponse();
// try {
this.preHandle(request, response, ipLimit.count(), ipLimit.second());
// } catch (Exception e) {
// logger.error("限流内部程序出错:{}", e.getMessage());
// }
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
logger.info("API:{},限流注解程序执行:{} 毫秒", request.getRequestURI(), time);
}
具体实现逻辑,采用ip限制控流
/**
* 接口限流
* 同一个接口10s内请求超过5次进行限流
* Created by PeakGao on 2023/3/2.
*/
public class AccessLimitIntercept extends BaseController {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, int count, int second) throws InterruptedException, IOException {
//文章搜索则不进行限流,如需部分接口地址限流可自定义注解实现
// 拼接redis key = IP + Api限流
String prefix = Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT;
String key = ipUtils.getIpAddr(request) + request.getRequestURI();
// 获取redis的value
Integer maxTimes = null;
Object value = redisUtil.get(prefix+key);
if (value != null) {
maxTimes = (Integer) value;
}
if (maxTimes == null) {
// 如果redis中没有该ip对应的时间则表示第一次调用,保存key到redis
redisUtil.set(Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT, key, 1, second);
} else if (maxTimes < count) {
// 如果redis中的时间比注解上的时间小则表示可以允许访问,这是修改redis的value时间
redisUtil.set(Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT, key, maxTimes + 1, second);
} else {
// 请求过于频繁
output(response, "{\"code\":\"8002\",\"message\":\"请求过于频繁,请稍后再试\"}");
throw new RuntimeException("API请求限流拦截启动,当前接口:【" + key + "】请求过于频繁");
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ObjectUtils.isNotEmpty(outputStream)) {
outputStream.flush();
outputStream.close();
}
}
}
}