Spring Cloud 以Gateway实现限流(自定义返回内容)

前言


    Spring Cloud Gateway自带RequestRateLimiterGatewayFilterFactory限流方案,可基于Redis和RedisRateLimiter实现默认算法为令牌桶的请求限流。作为自带的该限流方案,其可与Spring生态的其它各项组件无缝集成,并且自身实现也相对完善/好用,因此在没有特殊/复杂需求的情况下,该方案是实现基础限流的首选。
 
 

依赖


    在pom.xml文件中添加以下依赖。

<!--  Spring Boot Redis响应式起步依赖:用于实现请求限流  -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--  Spring Cloud网关起步依赖  -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

    继承&改造UsernamePasswordAuthenticationToken类以创建免密授权的鉴权类,该类会置null密码以逃脱密码校验。此外该类还可限定免密授权的处理器不会作用在其它授权模式上,因此其功能虽然完全可以使用UsernamePasswordAuthenticationToken代替,但其存在依然是不可省略的。

/*** @Author: 说淑人* @Date: 2025/5/8 21:45* @Description: 免密鉴权令牌类*/
public static class PasswordLessAuthenticationToken extends UsernamePasswordAuthenticationToken {private static final long serialVersionUID = -2798549339574220892L;public PasswordLessAuthenticationToken(Object principal) {// ---- credentials即为密码,为null表示不进行校验。super(principal, null);setAuthenticated(false);}public PasswordLessAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(principal, null, authorities);}}

    实现AuthenticationProvider接口以创建免密授权的实际处理器。

/*** @Author: 说淑人* @Date: 2025/5/8 19:38* @Description: 免密鉴权供应者类*/
@Component
public class PasswordLessAuthenticationProvider implements AuthenticationProvider {private final UserDetailsService userDetailsService;public PasswordLessAuthenticationProvider(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// ---- 获取用户名,并调用我们实现的userDetailsService.loadUserByUsername(username)String username = (String) authentication.getPrincipal();UserDetails user = userDetailsService.loadUserByUsername(username);// ---- 如果用户不存在,按框架逻辑抛出原样异常以统一格式。if (user == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return new PasswordLessAuthenticationToken(user, user.getAuthorities());}@Overridepublic boolean supports(Class<?> authentication) {// ---- 该方法用于限定PasswordLessAuthenticationProvider只对// PasswordLessAuthenticationToken生效,这个可以避免// PasswordLessAuthenticationProvider作用于其它授权模式,而这会导致混乱。return PasswordLessAuthenticationToken.class.isAssignableFrom(authentication);}}

 
 

配置


    在application.yml文件中添加以下Redis与Gateway配置。

# ---- Spring Config
spring:# ---- Redis Configredis:host: 127.0.0.1port: 6379# ---- Gateway Configgateway:routes:# ---- 转发的服务。- id: world-biz-manageuri: lb://world-biz-managepredicates:- Path=/api/manage/**# ---- 添加RequestRateLimiter过滤器实现限流。因为过滤器是注册在具体服# 务下的,因此也只会对当前服务进行限流。filters:- name: RequestRateLimiterargs:# ---- 键解析器:定义限流数据键的生成规则,常用的生成规则有基于用# 户/IP/接口,而这里使用IP进行区分,即各IP限流数据是独立的。key-resolver: "#{@ipKeyResolver}"# ---- 每秒生成15个令牌,因此在令牌桶无令牌的情况下,一个IP每秒# 最多请求15次。redis-rate-limiter.replenishRate: 15# ---- 令牌桶中最多保存30个令牌,可支持单个IP最多30个请求/秒的流# 量高发。redis-rate-limiter.burstCapacity: 30# ---- 一次请求消耗一个令牌。redis-rate-limiter.requestedTokens: 1

    创建WebConfig(名称自定)类,用于内部创建/注册IP键解析器实例。

package com.ssr.world.frame.gateway.tool.config.web;import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;/*** @Author: 说淑人* @Date: 2025/4/22 22:39* @Description: 网络配置类*/
@Configuration
public class WebConfig {/*** IP键解析器** @return IP键解析器*/@Bean// ---- 你没看错,没有public。KeyResolver ipKeyResolver() {// ---- 以IP地址作为限流键的区分标志,即每个IP都有自己独立的令牌桶。return exchange -> Mono.just(// ---- 此处也可以获取其它参数作为限流键的区分表示,例如:// 头信息中携带的用户ID// 请求的接口// ---- 将上述参数混合使用也是不错的做法。exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());}}

 
 

启动&测试


# ---- 为了方便测试,我们暂时修改令牌的生成/存储上限为1/1。
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1

    使用Postman进行连续快速地多次调用后返回以下内容,表示请求被限流:

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c1b593acd672465bb9cb74f1dd24bda1.png
    Redis数据结构如下所示。注意!由于限流数据键每秒都会更新/删除,所以想看到的话需要频繁请求接口以保持限流状态。
在这里插入图片描述
 
 

自定义回应


    Spring Cloud Gateway通过返回429的异常状态来表示限流异常/情况,但显然这种行为并无法兼容进异常的统一返回格式,因此此处会展示自定义限流异常回应信息的完整流程…这通过自定义限流过滤器的方式实现:
    创建WebRequestRateLimiterGatewayFilterFactory类并重写apply(Config config)方法以自定义限流回应的内容/格式。

import com.alibaba.fastjson2.JSONObject;
import com.ssr.world.tool.toft.model.bo.result.ResultBox;
import com.ssr.world.tool.toft.model.eo.web.WebResultEnum;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;@Component
public class WebRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {// ---- defaultKeyResolver会因为多实例而出现无法注入的情况,可通过// 在ipKeyResolver()方法上添加@Primary注解的方式处理。public WebRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {super(defaultRateLimiter, defaultKeyResolver);}@Overridepublic GatewayFilter apply(Config config) {// ---- 获取健解析器/速率限制器。KeyResolver resolver = config.getKeyResolver() != null ? config.getKeyResolver() : getDefaultKeyResolver();RateLimiter<Object> limiter = config.getRateLimiter() != null ? config.getRateLimiter() : getDefaultRateLimiter();return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {String routeId = config.getRouteId();if (routeId == null) {Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);assert route != null;routeId = route.getId();}return limiter.isAllowed(routeId, key).flatMap(response -> {// ---- 继承所有的头信息。for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());}// ---- 如果未被限流,直接返回。if (response.isAllowed()) {return chain.filter(exchange);}// ---- 如果被限流了,重置回应体。ServerHttpResponse httpResponse = exchange.getResponse();// ---- 重设状态/内容类型/内容长度(皆可选)。httpResponse.setStatusCode(HttpStatus.OK);httpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);httpResponse.getHeaders().setContentLength(REQUEST_TOO_QUICKLY_BYTES.length);// ---- 重设回应体。Map<String, Object> map = new HashMap<>();map.put("code", "自定义错误码");map.put("message", "请求频率过高,请稍后重试...");map.put("data", "自定义数据");map.put("success", false);// ---- 回应内容是固定的,因此可以bytes直接在static中预加载,这能一定程度提升性能。byte[] bytes = JSONObject.toJSONString(map).getBytes(StandardCharsets.UTF_8);DataBuffer buffer = httpResponse.bufferFactory().wrap(bytes);return httpResponse.writeWith(Mono.just(buffer));});});}}

    修改application.yml文件中指定的限流过滤器为自定义的限流过滤器。

filters:# ---- 将原本的RequestRateLimiter改为自定义的WebRequestRateLimiter,即略去名称尾部的GatewayFilterFactory部分。- name: WebRequestRateLimiterargs:key-resolver: "#{@ipKeyResolver}"redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 1redis-rate-limiter.requestedTokens: 1

    启动测试。
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/904886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

容器填充函数fill和memset对比总结

文章目录 1、fill() 按元素填充2、memset() 按字节填充3、对比 1、fill() 按元素填充 用于将容器或数组的指定范围内的元素赋值为指定值&#xff08;按元素类型填充&#xff09;。属于 C STL 算法&#xff08;<algorithm> 头文件&#xff09;。 // 从起始地址到结束地址…

【Linux实践系列】:进程间通信:万字详解共享内存实现通信

&#x1f525; 本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 人生就像一场马拉松&#xff0c;重要的不是起点&#xff0c;而是坚持到终点的勇气 ★★★ 本文前置知识&#xff1a; …

CogView4 文本生成图像

CogView4 文本生成图像 flyfish 基于 CogView4Pipeline 的图像生成程序&#xff0c;其主要目的是依据 JSON 文件里的文本提示信息来生成图像&#xff0c;并且把生成的图像保存到指定文件夹。 JSON 文件格式 [{"prompt": "your first prompt"},{"pr…

路由重发布

路由重发布 实验目标&#xff1a; 掌握路由重发布的配置方法和技巧&#xff1b; 掌握通过路由重发布方式实现网络的连通性&#xff1b; 熟悉route-pt路由器的使用方法&#xff1b; 实验背景&#xff1a;假设学校的某个分区需要配置简单的rip协议路由信息&#xff0c;而主校…

机器人领域和心理学领域 恐怖谷 是什么

机器人领域和心理学领域 恐怖谷 是什么 恐怖谷是一个在机器人领域和心理学领域备受关注的概念,由日本机器人专家森政弘于1970年提出。 含义 当机器人与人类的相似度达到一定程度时,人类对它们的情感反应会突然从积极变为消极,产生一种毛骨悚然、厌恶恐惧的感觉。这种情感…

Go-GJSON 组件,解锁 JSON 读取新姿势

现在的通义灵码不但全面支持 Qwen3&#xff0c;还支持配置自己的 MCP 工具&#xff0c;还没体验过的小伙伴&#xff0c;马上配置起来啦~ https://click.aliyun.com/m/1000403618/ 在 Go 语言开发领域&#xff0c;json 数据处理是极为常见的任务。Go 标准库提供了 encoding/jso…

数据分析_数据预处理

1 数据预处理流程 ①数据清洗:处理数据缺失、数据重复、数据异常等问题,提升数据质量. ②数据转换:涵盖基本数据转换、语义数据转换、衍生数据转换和隐私数据转换,适配分析需求. ③数据集成:整合多源数据. 2 数据清洗 2.1 数据缺失 2.1.1 数值型数据缺失 数值型列的部分数值不…

vue +xlsx+exceljs 导出excel文档

实现功能&#xff1a;分标题行导出数据过多&#xff0c;一个sheet表里表格条数有限制&#xff0c;需要分sheet显示。 步骤1:安装插件包 npm install exceljs npm install xlsx 步骤2&#xff1a;引用包 import XLSX from xlsx; import ExcelJS from exceljs; 步骤3&am…

ThinkPad T440P如何从U盘安装Ubuntu24.04系统

首先制作一个安装 U 盘。我使用的工具是 Rufus &#xff0c;它的官网是 rufus.ie &#xff0c;去下载最新版就可以了。直接打开这个工具&#xff0c;选择自己从ubuntu官网下载Get Ubuntu | Download | Ubuntu的iso镜像制作U盘安装包即可。 其次安装之前&#xff0c;还要对 Thi…

第十七次博客打卡

今天学习的内容是动态规划算法。 动态规划算法&#xff08;Dynamic Programming&#xff0c;简称 DP&#xff09;是一种通过将复杂问题分解为更小的子问题来求解的算法思想。它主要用于解决具有重叠子问题和最优子结构特性的问题。 一、动态规划的基本概念 1. 最优子结构 一个复…

视觉革命来袭!ComfyUI-LTXVideo 让视频创作更高效

探索LTX-Video 支持的ComfyUI 在数字化视频创作领域&#xff0c;视频制作效果的提升对创作者来说无疑是一项重要的突破。LTX-Video支持的ComfyUI便是这样一款提供自定义节点的工具集&#xff0c;它专为改善视频质量、提升生成速度而开发。接下来&#xff0c;我们将详细介绍其功…

Java版ERP管理系统源码(springboot+VUE+Uniapp)

ERP系统是企业资源计划&#xff08;Enterprise Resource Planning&#xff09;系统的缩写&#xff0c;它是一种集成的软件解决方案&#xff0c;用于协调和管理企业内各种关键业务流程和功能&#xff0c;如财务、供应链、生产、人力资源等。它的目标是帮助企业实现资源的高效利用…

CenOS7切换使用界面

永久切换 在开始修改之前&#xff0c;我们首先需要查看当前的启动模式。可以通过以下命令来实现&#xff1a; systemctl get-default执行此命令后&#xff0c;系统会返回当前的默认启动模式&#xff0c;例如graphical.target表示当前默认启动为图形界面模式。 获取root权限&…

Dify使用总结

最近完成了一个Dify的项目简单进行总结下搭建服务按照官方文档操作就行就不写了。 进入首页之后由以下组成&#xff1a; 探索、工作室、知识库、工具 探索&#xff1a; 可以展示自己创建的所有应用&#xff0c;一个应用就是一个APP&#xff0c;可以进行测试使用 工作室包含…

计网学习笔记———网络

&#x1f33f;网络是泛化的概念 网络是泛化的概念 &#x1f342;泛化理解 网络的概念在生活中无处不在举例&#xff1a;社交网络、电话网路、电网、计算机网络 &#x1f33f;网络的定义 定义&#xff1a; 离散的个体通过通讯手段连成群体&#xff0c;实现资源的共享与交流、个…

《Python星球日记》 第53天:卷积神经网络(CNN)入门

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、图像表示与通道概念1. 数字图像的本质2. RGB颜色模型3. 图像预处理 二、卷积…

SpringBoot2集成xxl-job详解

官方教程 搭建调度中心 Github Gitee 注&#xff1a;版本3.x开始要求Jdk17&#xff1b;版本2.x及以下支持Jdk1.8。如对Jdk版本有诉求&#xff0c;可选择接入不同版本 clone源代码执行xxl-job\doc\db\tables_xxl_job.sql # # XXL-JOB v2.4.1 # Copyright (c) 2015-present, x…

HashMap中put()方法的执行流程

HashMap 是 Java 中最常用的数据结构之一&#xff0c;用于存储键值对。其 put() 方法是向哈希表中插入或更新键值对的核心操作。本文将详细解析 put() 方法的执行过程&#xff0c;涵盖哈希值计算、桶定位、冲突处理和扩容等步骤。 一、put() 方法的执行过程 put() 方法通过一系…

【Oracle认证】MySQL 8.0 OCP 认证考试英文版(MySQL30 周年版)

文章目录 1、MySQL OCP考试介绍2、考试注册流程3、考试复习题库 Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到2025.07.31 之前。所有人均可以免费考取原价245美元 &#xff08;约1500&#xff09;的MySQL OCP 认证。 1、MySQL OCP考试介绍 OCP考试 OCP认证是Oracle公司推…

SpringBoot框架开发网络安全科普系统开发实现

概述 基于SpringBoot框架的网络安全科普系统开发指南&#xff0c;该系统集知识科普、案例学习、在线测试等功能于一体&#xff0c;本文将详细介绍系统架构设计、功能实现及技术要点&#xff0c;帮助开发者快速构建专业的网络安全教育平台。 主要内容 系统功能架构 本系统采…