背景:
项目中需要提供ToC接口,为了避免被频繁请求,恶意攻击,需要对Ip进行限流
实现:
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IpLimiter {
/**
* 单位时间限制通过请求数
*/
int limit() default 10;
/**
* 单位时间(s)
*/
int timeout() default 1;
/**
* 达到限流提示语
*/
String message() default "请求过于频繁,请稍后再试!";
}
Aop
@Aspect
@Component
@Slf4j
public class IpLimterComponent {
@Autowired
private RedisLettuceUtil redisUtils;
/*@Pointcut("@annotation(com.ark.sdk.framework.common.annotation.IpLimiter)")
public void ipLimiter() {
}*/
@Around("@annotation(ipLimiter)")
public Object around(ProceedingJoinPoint joinPoint, IpLimiter ipLimiter) throws Throwable {
String ip = null;
//获取HttpServletRequest对象
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(sra != null) {
HttpServletRequest request = sra.getRequest();
//获取ip地址
ip =IpUtil.getIpAddr(request);
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取类名和方法名
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
//生成key 类名+方法名+ip,再进行hash
String key = String.valueOf(HashUtil.murmurHash(className + ":" + methodName + ":" + ip));
//原子自增
Long incr = redisUtils.incr(key);
if (incr == 1) {
//设置过期时间,将ipLimiter中的单位时间设置为过期时间,表示,在多少秒内,相同ip地址只能访问多少次
redisUtils.setExpire(key, ipLimiter.timeout());
}
if (incr > ipLimiter.limit()) {
return ResultObject.fail(ipLimiter.message());
}
return joinPoint.proceed();
}
}
编写测试接口
@GetMapping("/test")
@IpLimiter(limit = 5, timeout = 1) // 测试限流:1秒内最多访问5次
public ResultObject test() {
return ResultObject.success("test");
}
Apifox测试
当1秒内请求超过5次时: