最近搭建的博客网站,详情被人刷了,特意以此来提醒该加限流处理了
引入依赖
<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()
}
}
}
}
评论区