SpringAop+Redis+自定义注解实现ip限流

98 阅读1分钟

背景:

项目中需要提供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测试

image.png 当1秒内请求超过5次时:

image.png