SpringBoot限流拦截器(结合业务)

SpringBoot限流拦截器(结合业务)

背景

从网络安全和系统稳定性来看,限流是非常有必要的。
一些网关,可以帮我们完成限流熔断。但是,在某些场景,当与实际业务相结合时,网关的限流也就不那么方便了。

1.目的

1.解决业务和限流合并的情况。
如,同一个接口,每个用户,在一段时间(10秒)内只能请求几次(4次)。
2.并且可以快速的调整这个限制的频率(动态修改)

  • SpringBoot
  • Redis

2.配置关系

Redis > application.yml > 类中默认

PS:这里是将Redis作为一个配置中心,定时读取。
也可以自行修改为配置中心,如Nacos等,这里不做展开。

注意:如果用Redis做配置中心,定时读取的话,注意在启动类添加@EnableScheduling注解

3.代码

配置类

@Data
@Configuration
@ConfigurationProperties("request.limit")
public class RequestLimitProperties {
    
    
    // 配置文件,默认配置  10秒内4次

    /**
     * 判断周期
     */
    private Integer cycle = 10;

    /**
     * 每周期请求的次数
     */
    private Integer times = 4;
}

拦截器


/**
 * 每个请求次数限制拦截器
 *
 * @author litong
 */
@Order
public class RequestLimitInterceptor implements HandlerInterceptor {
    
    

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取次数
     */
    private static final String CYCLE_KEY = getCycleKey();

    /**
     * 获取单位时间
     */
    private static final String TIMES_KEY = getTimesKey();

    /**
     * 默认判断周期
     */
    private Integer cycle = 10;
    /**
     * 默认每周期请求的次数
     */
    private Integer times = 4;

    @Autowired
    private RequestLimitProperties requestLimitProperties;

    @PostConstruct
    public void pInit() {
    
    
        cycle = requestLimitProperties.getCycle();
        times = requestLimitProperties.getTimes();
    }

    @Scheduled(fixedDelay = 1000)
    public void fetchRequestLimitProperties() {
    
    
        //在有值,且是有效值时,认为Redis优先级更高,更新 cycle 和 times
        Integer redisCycle = (Integer) redisTemplate.opsForValue().get(CYCLE_KEY);
        Integer redisTimes = (Integer) redisTemplate.opsForValue().get(TIMES_KEY);
        if (redisCycle != null) {
    
    
            cycle = redisCycle;
        }
        if (redisTimes != null) {
    
    
            times = redisTimes;
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) {
    
    
        // 获取sid
        String token = httpServletRequest.getHeader("token");
        // 获取请求接口路径
        String requestURI = httpServletRequest.getRequestURI();
        if (requestURI != null) {
    
    
            String[] split = requestURI.split("/");
            if (split.length != 0) {
    
    
                requestURI = split[split.length - 1];
            }
        }
        // 获取Redis中的缓存
        Long expire = redisTemplate.getExpire(getKey(token, requestURI), TimeUnit.SECONDS);
        if (expire == null || expire == -2) {
    
    
            // 第一次请求,记录请求
            redisTemplate.opsForValue().set(getKey(token, requestURI), 1, cycle, TimeUnit.SECONDS);
        } else {
    
    
            // 后续请求,请求次数加1
            Integer s = (Integer) redisTemplate.opsForValue().get(getKey(token, requestURI));
            int time = 0;
            if (s != null && s != -2) {
    
    
                time = s;
            }
            int count = time + 1;
            if (count >= times + 1) {
    
    
                throw new AuthenticationException("接口请求频率过高,sid:" + token + ",url:" + requestURI + ",当前阈值:" + times + "/" + cycle);
            }
            redisTemplate.opsForValue().set(getKey(token, requestURI), count, expire == 0 ? 1 : expire, TimeUnit.SECONDS);
        }
        return true;
    }

    private String getKey(String token, String requestURI) {
    
    
        return "REQUEST_LIMIT_REDIS:" + requestURI + ":" + token;
    }

    private static String getCycleKey() {
    
    
        return "REQUEST_LIMIT_CYCLE:";
    }

    private static String getTimesKey() {
    
    
        return "REQUEST_LIMIT_TIMES:";
    }
}

猜你喜欢

转载自blog.csdn.net/LitongZero/article/details/105331978