springboot项目api防刷

    xiaoxiao2023-10-07  159

    aop+redis实现ip请求方法防刷

    引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 修改配置文件 spring: redis: host: localhost port: 6379 database: 1 password: timeout: 5000ms 创建一个限流的注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Limiter { /** * 从第一次访问接口的时间到cycle周期时间内,无法超过frequency次 * * @return */ int frequency() default 20; /** * 周期时间,单位ms: * 默认周期时间为一分钟 * * @return */ long cycle() default 60 * 1000; /** * 返回的错误信息 * * @return */ String message() default "请求过于频繁"; /** * 到期时间,单位s: * 如果在cycle周期时间内超过frequency次,则默认1分钟内无法继续访问 * @return */ long expireTime() default 1 * 60; } 创建aop @Aspect @Component public class LimitingAspect { private static final String LIMITING_KEY = "limiting:%s:%s"; private static final String LIMITING_BEGINTIME = "beginTime"; private static final String LIMITING_EXFREQUENCY = "exFrequency"; @Autowired private RedisTemplate<String, Object> redisTemplate; @Pointcut("@annotation(limiter)") public void pointcut(Limiter limiter) { } @Around("pointcut(limiter)") public Object around(ProceedingJoinPoint pjp, Limiter limiter) throws Throwable { //获取请求的ip和方法 String ipAddress = WebUtil.getIpAddress(); String methodName = pjp.getSignature().toLongString(); //获取方法的访问周期和频率 long cycle = limiter.cycle(); int frequency = limiter.frequency(); long currentTime = System.currentTimeMillis(); //获取redis中周期内第一次访问方法的时间和执行的次数 Long beginTimeLong = (Long) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME); Integer exFrequencyLong = (Integer) redisTemplate.opsForHash().get(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY); long beginTime = beginTimeLong == null ? 0L : beginTimeLong; int exFrequency = exFrequencyLong == null ? 0 : exFrequencyLong; //如果当前时间减去周期内第一次访问方法的时间大于周期时间,则正常访问 //并将周期内第一次访问方法的时间和执行次数初始化 if (currentTime - beginTime > cycle) { redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_BEGINTIME, currentTime); redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, 1); redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), limiter.expireTime(), TimeUnit.SECONDS); return pjp.proceed(); } else { //如果在周期时间内,执行次数小于频率,则正常访问 //并将执行次数加一 if (exFrequency < frequency) { redisTemplate.opsForHash().put(String.format(LIMITING_KEY, ipAddress, methodName), LIMITING_EXFREQUENCY, exFrequency + 1); redisTemplate.expire(String.format(LIMITING_KEY, ipAddress, methodName), limiter.expireTime(), TimeUnit.SECONDS); return pjp.proceed(); } else { //否则抛出访问频繁异常 throw new FrequentRequestsException(limiter.message()); } } } }

    项目中用到的其他工具包可在项目连接中查看

    创建测试类 @RestController public class TestController { //限制在周期内只能访问3次 @Limiter(frequency = 3) @GetMapping("getString") public String getString(){ return "hello"; } } 测试 浏览器中访问前三次时都能正常获取返回值,当访问第四次时抛出异常 demo.springboot.limiter.exception.FrequentRequestsException: 请求过于频繁 at demo.springboot.limiter.aspect.LimitingAspect.around(LimitingAspect.java:70) ~[classes/:na]

    需要限制访问次数的接口上加上@Limiter注解即可

    项目路径


    作者博客

    作者公众号

    最新回复(0)