首页 > 其他分享 >spring中接口流量的控制

spring中接口流量的控制

时间:2024-02-28 11:23:57浏览次数:19  
标签:java spring forbiddenTime 流量 interfaceInfo second 接口 import maxTime

  防止接口同一时间内对一个接口进行频繁的访问,可以对接口进行限流。

1.自定义注解,用来标识需要限流的接口。

package com.springweb.demo.limit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 接口流量控制
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessLimit {

    /**
     * 多少秒内
     * @return
     */
    long second() default 2L;

    /**
     * 最大访问次数
     * @return
     */
    long maxTime() default 1L;

    long forbiddenTime() default 3L;

    String apiUrl() default "";

}

2.使用拦截器Interceptor对使用注解的接口进行限流处理

package com.springweb.demo.limit;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Objects;


@Slf4j
public class AccessLimitInterceptor implements HandlerInterceptor {

    private static HashMap<String, InterfaceInfo> accessMap = new HashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod targetMethod = (HandlerMethod) handler;
            AccessLimit targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class);

            boolean isBrushForAllInterface = false;
            long second = 0L, maxTime = 0L, forbiddenTime = 0L;

            if (!Objects.isNull(targetClassAnnotation)) {
                log.info("目标接口方法所在类上有@AccessLimit注解");
                isBrushForAllInterface = true;
                second = targetClassAnnotation.second();
                maxTime = targetClassAnnotation.maxTime();
                forbiddenTime = targetClassAnnotation.forbiddenTime();
            }

            String uri = request.getRequestURI();
            // 目标方法上的@AccessLimit注解
            AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class);
            if (!Objects.isNull(accessLimit)) {
                second = accessLimit.second();
                maxTime = accessLimit.maxTime();
                forbiddenTime = accessLimit.forbiddenTime();
                if (isForbidden(second, maxTime, forbiddenTime, "登录用户唯一标识", uri)) {
                    return false;
                }
            } else {
                if (isBrushForAllInterface && isForbidden(second, maxTime, forbiddenTime, "登录用户唯一标识", uri)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean isForbidden(long second, long maxTime, long forbiddenTime, String id, String uri) {
        String lockKey = id + "_" + uri;
        InterfaceInfo interfaceInfo = accessMap.get(lockKey);
        if (!Objects.isNull(interfaceInfo)) {
            // 还在禁用中
            if (LocalDateTime.now().compareTo(interfaceInfo.getForbiddenTime()) <= 0) {
                return true;
            }

            // 判断两次接口访问的时间差是否超过限制
            long diffSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), interfaceInfo.getAccessTime());
            if (diffSeconds > second) {
                accessMap.remove(lockKey);
            }
        }

        // 判断此用户访问接口是否已经禁用
        if (Objects.isNull(interfaceInfo)) {
            // 还未被禁用
            interfaceInfo = new InterfaceInfo();
            interfaceInfo.setAccessTime(LocalDateTime.now());
            interfaceInfo.setAccessCount(1);
        } else {
            if (interfaceInfo.getAccessCount() < maxTime) {
                interfaceInfo.setAccessCount(interfaceInfo.getAccessCount() + 1);
            } else {
                // 禁止访问
                interfaceInfo.setAccessTime(LocalDateTime.now());
                interfaceInfo.setAccessCount(0);
                // 设置禁止到的时间点
                interfaceInfo.setForbiddenTime(LocalDateTime.now().plus(forbiddenTime, ChronoUnit.SECONDS));
                accessMap.put(lockKey, interfaceInfo);
                return true;
            }
        }

        return false;
    }


}

  这里使用map处理 + InterfaceInfo.java对接口进行限流处理。也可以用redis进行处理。

package com.springweb.demo.limit;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class InterfaceInfo {

    private LocalDateTime accessTime;

    private LocalDateTime forbiddenTime;

    private int accessCount;

}

3.对接口进行限流。

@AccessLimit使用默认属性。2s内只能访问一次,如果访问超过2次那么接下来的3s内拒绝这个接口的访问。
package com.springweb.demo.controller;

import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import com.springweb.demo.limit.AccessLimit;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/hello")
@ApiSort(1)
@Tag(name = "实例功能")
public class HelloController {

    @GetMapping("/helloString")
    @ResponseBody
    @Operation(summary = "输出hello", description = "hello接口", method = "GET")
    @ApiOperationSupport(order = 1)
    @AccessLimit
    public String helloString(){
        return "hello, spring-web-demo.";
    }

}

  

标签:java,spring,forbiddenTime,流量,interfaceInfo,second,接口,import,maxTime
From: https://www.cnblogs.com/wang-liang-blogs/p/18039398

相关文章

  • Spring Boot使用BESApplicationServer宝兰德替换内嵌Tomcat
    移除自带tomcat<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>${spring.version}</version>......
  • 如何在SpringBoot中优雅地重试调用第三方API?
    1引言在实际的应用中,我们经常需要调用第三方API来获取数据或执行某些操作。然而,由于网络不稳定、第三方服务异常等原因,API调用可能会失败。为了提高系统的稳定性和可靠性,我们通常会考虑实现重试机制。本文将深入探讨如何在SpringBoot项目中优雅地重试调用第三方API,并结合代码......
  • SpringCloud系列之(十三)FAQ
    关于一些可能出现的报错1.DruidDataSource:testWhileIdleistrue,validationQuerynotset工程启动时可能会报如下错误,这是DruidDataSource保持链接的测试,不影响我们的应用。2.idea未监测到yml文件出现这种情况会造成文件内写配置的时候没有提示正常应该是这样的......
  • SpringCloud系列之(十二)SpringCloud Sleuth分布式请求链路追踪
    SpringCloudSleuth分布式请求链路追踪一、概述1.应用场景​ 在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请......
  • SpringCloud系列之(十一)SpringCloud Stream消息驱动
    SpringCloudStream消息驱动企业中常用的消息中间件ActiveMQRabbitMQRocketMQKafka一、消息驱动概述1.为什么引入SpringCloudStream​ 消息中间件的产品众多(ActiveMQ、RabbitMQ、RocketMQ、Kafka...),学习成本高​ 一个系统中可能使用了多种消息中间件,切换/维护/开发成本......
  • SpringCloud系列之(九)服务配置
    服务配置目前在用的服务配置+服务总线的三套方案Config+BusNaccos(Alibaba)Apollo(携程)上海地区一、SpringCloudConfig分布式配置中心1.概述1.1分布式系统面临的配置问题​ 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现......
  • SpringCloud系列之(八)服务网关
    服务网关类比医院的分诊台一、Zuul由Netflix团队研发,不再使用官网:https://github.com/Netflix/zuul/wiki1.Zuul1.x模型​ Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的ServletI0处理模型。​ 学过尚硅谷web中期课程都知道一个题目,Servlet的生命周期......
  • SpringCloud系列之(七)服务降级
    服务降级一、Hystrix断路器1.概述1.1分布式系统面临的问题复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。![](https://gitee.com/honourer/picturebed/raw/master/SpringCloud/图像(16).png)服务雪崩​ 多个微服务之间调用......
  • SpringCloud系列之(六)服务调用
    服务调用完成微服务之间的分布式调用一、Ribbon1.概述1.1是什么​ SpringCloudRibbon是基于NetflixRibbon实现的一套客户端负载均衡【消费者侧80】的工具。​ 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组......
  • SpringCloud系列之(五)服务注册中心
    服务注册中心【服务注册与发现】一、EurekaSpring社区出的,Nacos是阿里出的1.Eureka基础知识1.1什么是服务治理?​ SpringCloud封装了Netflix公司开发的Eureka模块来实现服务治理。​ 在传统的rpc远程调用框架中,服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要......