专业网站建设 公司哪家好,建设网站教程2016,免费发布招聘信息,鲜花网站前台数据库建设Gateway新一代网关
1、概述
Cloud全家桶中有个很重要的组件就是网关#xff0c;在1.x版本中都是采用的Zuul网关#xff1b;
但在2.x版本中#xff0c;zuul的升级一直跳票#xff0c;SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul。
官网在1.x版本中都是采用的Zuul网关
但在2.x版本中zuul的升级一直跳票SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul。
官网Spring Cloud Gateway 中文文档 (springdoc.cn) 微服务架构中网关在哪里 Spring Cloud Gateway能干什么
反向代理鉴权流量控制熔断日志监控
总结
**Spring Cloud Gateway组件的核心是一系列的过滤器通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器隐藏微服务结点IP端口信息从而加强安全保护。**Spring Cloud Gateway本身也是一个微服务需要注册进服务注册中心。 2、Gateway三大核心
Route路由: 网关的基本构件。它由一个ID、一个目的地URI、一个谓词Predicate集合和一个过滤器Filter集合定义。如果集合谓词为真则路由被匹配。Predicate谓词: 这是一个 Java 8 Function Predicate。输入类型是 Spring Framework ServerWebExchange。这让你可以在HTTP请求中的任何内容上进行匹配比如header或查询参数。Filter过滤器: 这些是 GatewayFilter 的实例已经用特定工厂构建。在这里你可以在发送下游请求之前或之后修改请求和响应。
web前端请求通过一些匹配条件定位到真正的服务节点。并在这个转发过程的前后进行一些精细化控制。predicate就是我们的匹配条件filter就可以理解为一个无所不能的拦截器。有了这两个元素再加上目标uri就可以实现一个具体的路由了 3、Spring Cloud Gateway 工作流程 客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是过滤器可以在代理请求发送之前和之后运行逻辑。
所有的 pre 前过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后post 后过滤器逻辑被运行。
注意在路由中定义的没有端口的URI其HTTP和HTTPS URI的默认端口值分别为80和443。
总结路由转发断言判断执行过滤器链
4、入门配置
新建modulecloud-gateway9527 添加POM添加springcloudgateway依赖。 dependencies!--gateway--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependency!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-consul-discovery/artifactId/dependency!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build 写配置文件application.yml
server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: trueservice-name: ${spring.application.name} 主启动类
SpringBootApplication
EnableDiscoveryClient
public class GateWay9527 {public static void main(String[] args) {SpringApplication.run(GateWay9527.class,args);}
} 业务类是不需要写的网关与业务无关。
先启动Consul然后启动网关入住进服务注册中心。 5、9527网关如何做路由映射
我们目前不希望暴露8001端口希望在8001这个真正的支付微服务外面再套一个9527网关。
8001新建GateWayController
RestController
public class GateWayController {ResourcePayService payService;GetMapping(value /pay/gateway/get/{id})public ResultDataPay getById(PathVariable(id) Integer id){Pay pay payService.getById(id);return ResultData.success(pay);}GetMapping(value /pay/gateway/info)public ResultDataString getGatewayInfo(){return ResultData.success(gateway info test IdUtil.simpleUUID());}
} 测试一下 没有问题下面在网关的配置文件中新增配置新增路由和断言配置。
server:port: 9527spring:application:name: cloud-gateway #以微服务注册进consul或nacos服务列表内cloud:consul: #配置consul地址host: localhostport: 8500discovery:prefer-ip-address: trueservice-name: ${spring.application.name}gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名uri: http://localhost:8001 #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名uri: http://localhost:8001 #匹配后提供服务的路由地址predicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由
添加网关之后的访问就不是8001了应该是localhost:9527/pay/gateway/get/1 目前通过网关访问是成功的如果通过80访问网关然后再访问8001呢
我们启动80订单微服务它从Consul注册中心通过微服务名称找到8001支付微服务进行调用
80 → 9527 → 8001
要求访问9527网关后才能访问8001如果我们此时启动80订单可以做到吗
修改cloud-api-commons的api接口添加网关测试方法
//网关测试
GetMapping(value /pay/gateway/get/{id})
public ResultData getById(PathVariable(id) Integer id);
GetMapping(value /pay/gateway/info)
public ResultDataString getGatewayInfo(); cloud-consumer-feign-order80添加网关测试的controller
RestController
public class OrderGateWayController
{Resourceprivate PayFeignApi payFeignApi;GetMapping(value /feign/pay/gateway/get/{id})public ResultData getById(PathVariable(id) Integer id){return payFeignApi.getById(id);}GetMapping(value /feign/pay/gateway/info)public ResultDataString getGatewayInfo(){return payFeignApi.getGatewayInfo();}
} 我们发现是可以访问的真的走网关了吗我们的API接口上FeignClient(value cloud-payment-service)打的可是cloud-payment-service微服务根本没有网关的事呀所以这个时候的网关压根就没有使用我们把网关的微服务关掉再用80访问一定是可以访问的。 网关的9527关掉了现在访问 依旧正常访问。我们想一下API上FeignClient注解上原来就是cloud-payment-service我们换成网关的服务名称cloud-gateway不就成了。
//FeignClient(value cloud-payment-service)
FeignClient(value cloud-gateway) //换成网关微服务
public interface PayFeignApi {/*** 新增一条支付相关流水记录* param payDTO* return*/PostMapping(/pay/add)public ResultData addPay(RequestBody PayDTO payDTO);//通过id查询GetMapping(value /pay/get/{id})public ResultData getPayById(PathVariable(id) Integer id);//删除DeleteMapping(/pay/del/{id})public ResultData delById(PathVariable(id) Integer integer);//修改PutMapping(/pay/update)public ResultData update(RequestBody PayDTO payDTO);//查全部GetMapping(/pay/getall)public ResultData getAll();//openfeign天然支持负载均衡演示GetMapping(/pay/getInfo)public String myLB();//Resilience4j CircuitBreaker 的例子GetMapping(/pay/circuit/{id})public String myCircuit(PathVariable(id) Integer id);//Resilience4j bulkhead 的例子GetMapping(value /pay/bulkhead/{id})public String myBulkhead(PathVariable(id) Integer id);//Resilience4j ratelimit 的例子GetMapping(value /pay/ratelimit/{id})public String myRatelimit(PathVariable(id) Integer id);GetMapping(value /pay/micrometer/{id})public String myMicrometer(PathVariable(id) Integer id);//网关测试GetMapping(value /pay/gateway/get/{id})public ResultData getById(PathVariable(id) Integer id);GetMapping(value /pay/gateway/info)public ResultDataString getGatewayInfo();
} 重启80微服务再次测试
无网关的直接500了 再把网关打开就成功访问了 目前我们的配置还是有问题的比如我们网关中的uri地址本地加端口号写的死死的一改就无了… 6、GateWay高级特性
6.1、Route以微服务名动态获取服务URI 我们之前给URI写死了现在可以修改成它的微服务名称这样就可以动态的从服务注册中心拿到具体的地址加端口即使端口更改了也没有问题。URL中添加一个lb代表SpringCloud的负载均衡ReactorLoadBalancer。
将9527的配置文件再来修改一下
gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由 修改以后可以重启一下网关测试。 测试没有问题我们把8001的端口给改成8003再测试一下。 依然可用。
6.2、Predicate断言(谓词)
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping 基础设施的一部分。Spring Cloud Gateway包括许多内置的路由谓词工厂。所有这些谓词都与HTTP请求的不同属性相匹配。你可以用逻辑 and 语句组合多个路由谓词工厂。
我们可以查看9527后台输出的默认路由谓词是和官网的那12个默认路由谓词一一对应着的 两种配置方式 Shortcut Configuration快捷方式配置由过滤器名称filter name后面跟一个等号 ()然后是用逗号 (,) 分隔的参数值。 Fully Expanded Arguments完全展开的参数看起来更像标准的yaml配置有名称/值对。一般来说会有一个 name key和一个 args key。args key是一个键值对的映射用于配置谓词或过滤器。
常用的默认路由谓词解释 After 路由谓词工厂需要一个参数即一个日期时间这是一个java ZonedDateTime这个谓词匹配发生在指定日期时间之后的请求。 那么这个日期怎么获得 写一个普通的java类来获得。 public class ZonedDateTimeDemo
{public static void main(String[] args){System.out.println(ZonedDateTime.now());}
}获得时间格式2024-03-17T20:25:30.21812920008:00[Asia/Shanghai] 开始配置 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- After2024-03-17T20:30:30.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由在我设置的8:30:30之前是不能访问的8点半之后就可以访问了。 Before 路由谓词工厂只需要一个参数即 datetime这个谓词匹配发生在指定日期时间datetime之前的请求。 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- After2024-03-17T20:30:30.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由- Before2024-03-17T20:39:00.21812920008:00[Asia/Shanghai] 可以看到8.38还能正常访问的39就不能了。 Between 路由谓词工厂需要两个参数datetime1 和 datetime2它们是java ZonedDateTime 对象。这个谓词匹配发生在 datetime1 之后和 datetime2 之前也就是这个时间段的请求。 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间段才能够访问现时秒杀- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由- Before2024-03-17T20:39:00.21812920008:00[Asia/Shanghai]可以看到这个只能在9点到9点零1才能访问到。 Cookie 路由谓词工厂接受两个参数即 cookie name 和一个 regexp这是一个Java正则表达式。这个谓词匹配具有给定名称且其值符合正则表达式的cookie。 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由- Cookieusername, zm只有加了cookie才能访问而且键值(kv)要匹配 有三种可以带cookie访问的方法 方法1使用原生命令cmd打开运行框 不带cookie的不能访问 带了cookie可以访问 方法2使用postman测试工具测试 不带cookie的不能访问 带了cookie可以访问 方法3使用浏览器控制台实现 不带cookie或者故意填错误的不能访问 带了正确的cookie可以访问 Header 路由谓词工厂需要两个参数header 和一个 regexp这是一个Java正则表达式。这个谓词与具有给定名称且其值与正则表达式相匹配的 header 匹配。 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由- Cookieusername, zm测试方法1使用原生命令 不是正整数 是正整数 测试方法2使用postman测试 不是正整数 是正整数 Host 路由谓语工厂接受一个参数一个主机Host名称的 patterns 列表。该pattern是Ant风格的模式以 . 为分隔符。这个谓词匹配符合该pattern的Host header。 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由#- Cookieusername, zm- Host**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配使用原命令测试 正确符合规则的可以访问 不符合规则的拒绝访问 使用postman测试 正常匹配规则的
不符合规则的 7.Path 路由谓词工厂需要两个参数一个Spring PathMatcher patterns 的list和一个可选的flag matchTrailingSlash默认为 true。我们最开始用的就是这个就不再看了。
8.Query 路由谓词工厂需要两个参数一个必需的 param 和一个可选的 regexp这是一个Java正则表达式。
gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票#- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问- Queryusername,\d #要有参数名称为username并且值还要是整数才能路由- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由#- Cookieusername, zm- Host**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配 测试
按照规则访问 不按规则不能访问 9.RemoteAddr 路由谓词工厂接受一个 sources 集合最小长度为1它是CIDR注解IPv4或IPv6字符串如 192.168.0.1/16其中 192.168.0.1 是一个IP地址16 是一个子网掩码。
gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票#- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问#- Queryusername,\d #要有参数名称为username并且值还要是整数才能路由- RemoteAddr10.2.142.1/24 # 外部访问我的IP限制最大跨度不超过32目前是1~24它们是 CIDR 表示法。- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由#- Cookieusername, zm- Host**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配 测试重启服务按照原来的localhost地址访问一下不能访问。 换成我们本地的IP就可以了 10.Method 路由谓词工厂接受一个 methods 参数它是一个或多个参数要匹配的HTTP方法。
我们可以限制只能使用GET或者POST方法才能访问。
- MethodGET,POST #只能是get或post请求方法6.3、自定义断言XXXRoutePredicateFactory规则
原有的默认的路由断言规则不够用我们就自定义一个。
查看AfterRoutePredicateFactory它就是继承了AbstractRoutePredicateFactory
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactoryConfig 然后再点AbstractRoutePredicateFactory里面它又实现了RoutePredicateFactory接口 所以我们要想自定义一个路由断言就需要这几点
要么继承AbstractRoutePredicateFactory抽象类要么实现RoutePredicateFactory接口开头的名字任意取但是必须以RoutePredicateFactory为后缀结尾由于AbstractRoutePredicateFactory里面就实现类RoutePredicateFactory接口我们就直接继承它即可一举两得 新建类名XXX需要以RoutePredicateFactory为后缀结尾并继承AbstractRoutePredicateFactory抽象类 Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactoryMyRoutePredicateFactory.Config重写apply方法 Override
public PredicateServerWebExchange apply(MyRoutePredicateFactory.Config config) {return null;
}新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config这个Config类就是我们的路由断言规则非常重要 Validated
public static class Config{SetterGetterNotNullprivate String userType; //会员类型
}空参构造方法内部调用super //空参构造函数
public MyRoutePredicateFactory() {super(Config.class);
}重写apply方法第二版 Override
public PredicateServerWebExchange apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {Overridepublic boolean test(ServerWebExchange serverWebExchange) {String userType serverWebExchange.getRequest().getQueryParams().getFirst(userType);if (userType null) {return false;}//这个config是从配置文件中拿到的如果和传来的相匹配就通过if (userType.equals(config.getUserType())){return true;}return false;}};
}完整方法 //业务需求说明自定义配置会员等级userType按照钻石/黄金/青铜的等级yml配置会员等级
Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactoryMyRoutePredicateFactory.Config {Overridepublic PredicateServerWebExchange apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {Overridepublic boolean test(ServerWebExchange serverWebExchange) {String userType serverWebExchange.getRequest().getQueryParams().getFirst(userType);if (userType null) {return false;}//这个config是从配置文件中拿到的如果和传来的相匹配就通过if (userType.equals(config.getUserType())){return true;}return false;}};}Validated public static class Config{SetterGetterNotNullprivate String userType; //会员类型}//空参构造函数public MyRoutePredicateFactory() {super(Config.class);}
}把自定义的断言配置到yml文件 gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票#- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问#- Queryusername,\d #要有参数名称为username并且值还要是整数才能路由#- RemoteAddr10.2.142.1/24 # 外部访问我的IP限制最大跨度不超过32目前是1~24它们是 CIDR 表示法。#- MethodGET,POST #只能是get或post请求方法- Mydiamond #自定义的断言- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由#- Cookieusername, zm- Host**.zmblog.com,**.zm.cn #请求host的header值带zmblog.com或zm.cn的可以匹配 重启测试发现报错了 报错说没绑定快捷方式的配置
Caused by: org.springframework.boot.context.properties.bind.validation.BindValidationException: Binding validation errors on 那我们就用完全展开的配置方式再次重写yml
gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由#- Between2024-03-17T21:00:00.21812920008:00[Asia/Shanghai],2024-03-17T21:01:00.21812920008:00[Asia/Shanghai] #在这个时间之后才能够访问到点放票#- HeaderX-Request-Id, \d #只有X-Request-Id属性为正整数才能访问#- Queryusername,\d #要有参数名称为username并且值还要是整数才能路由#- RemoteAddr10.2.142.1/24 # 外部访问我的IP限制最大跨度不超过32目前是1~24它们是 CIDR 表示法。#- MethodGET,POST #只能是get或post请求方法# - Mydiamond #自定义的断言- name: Myargs:userType: diamond #使用完全展开的方式配置 重启不报错测试一下
带正确参数的访问 不带参数或者不是配置文件中写的userType就不能访问 修改一下自定义断言MyRoutePredicateFactory添加方法shortcutFieldOrder能使用短配置
//实现短配置方式
public ListString shortcutFieldOrder() {return Collections.singletonList(userType);
} 现在的完整MyRoutePredicateFactory代码
//业务需求说明自定义配置会员等级userType按照钻石/黄金/青铜的等级yml配置会员等级
Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactoryMyRoutePredicateFactory.Config {Overridepublic PredicateServerWebExchange apply(MyRoutePredicateFactory.Config config) {return new GatewayPredicate() {Overridepublic boolean test(ServerWebExchange serverWebExchange) {String userType serverWebExchange.getRequest().getQueryParams().getFirst(userType);if (userType null) {return false;}//这个config是从配置文件中拿到的如果和传来的相匹配就通过if (userType.equals(config.getUserType())){return true;}return false;}};}Validated public static class Config{SetterGetterNotNullprivate String userType; //会员类型}//空参构造函数public MyRoutePredicateFactory() {super(Config.class);}//实现短配置方式public ListString shortcutFieldOrder() {return Collections.singletonList(userType);}} 到配置文件中使用短配置方式配置测试 - Mydiamond #自定义的断言
# - name: My
# args:
# userType: diamond #使用完全展开的方式配置
6.4、Filter过滤
6.4.1、Filter概述
路由Route过滤器Filter允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路由过滤器的范围是一个特定的路由。Spring Cloud Gateway 包括许多内置的 GatewayFilter 工厂。
中文官网Spring Cloud Gateway 中文文档 (springdoc.cn)
其实就相当于SpringMVC里面的拦截器interceptorServlet过滤器。pre和post分别会在请求被执行前调用和被执行后调用用来修改请求和响应信息。
能干嘛
请求鉴权异常处理记录接口调用时长统计(重点)
类型 全局默认过滤器Global Filters; gateway出厂默认已经有的直接使用主要作用于所有的路由不需要在配置文件中配置作用在所有的路由上实现GlobalFilter接口即可。 单一内置过滤器GatewayFilter; 也可以称为网关过滤器这种过滤器主要是作用于单一路由或者某个路由分组 自定义过滤器
6.4.2、Gateway内置Filter
一些常用的 请求头(RequestHeader)相关组 对应着官网的 6.1.The AddRequestHeader GatewayFilter Factory 指定请求头内容ByName 在8001微服务的GateWayController中新增方法 //测试The AddRequestHeader GatewayFilter Factory
GetMapping(/pay/gateway/filter)
public ResultDataString getGatewayFilter(HttpServletRequest request){String result ;EnumerationString headers request.getHeaderNames();while (headers.hasMoreElements()) {String headName headers.nextElement();String headerValue request.getHeader(headName);System.out.println(请求头名headName\t\t\t请求值headerValue);if (headName.equalsIgnoreCase(X-Request-zm1) || headName.equalsIgnoreCase(X-Request-zm2)){result result headName \t headerValue ;}}return ResultData.success(getGatewayFilter过滤器testresult \t DateUtil.now());
}9527配置文件添加过滤内容 - id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置- AddRequestHeaderX-Request-zm2,zmValue2 #请求头kv如果一头含有多参数则重写一行设置启动9527和8001并再次调用localhost:9527/pay/gateway/filter 查看后台 6.18.The RemoveRequestHeader GatewayFilter Factory 可以删除请求头的内容根据请求头名 我们把名为sec-fetch-site的请求头删除掉 - id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置指定请求头内容 - AddRequestHeaderX-Request-zm2,zmValue2 #请求头kv如果一头含有多参数则重写一行设置- RemoveRequestHeader sec-fetch-site #删除请求头sec-fetch-site重启测试 6.29.The SetRequestHeader GatewayFilter Factory 可以修改请求头的值那我们把请求头名为sec-fetch-mode的请求值修改成myNavigate - id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置指定请求头内容 - AddRequestHeaderX-Request-zm2,zmValue2 #请求头kv如果一头含有多参数则重写一行设置- RemoveRequestHeader sec-fetch-site #删除请求头sec-fetch-site- SetRequestHeadersec-fetch-mode,myNavigate #把请求头名为sec-fetch-mode的请求值修改成myNavigate重启测试 请求参数RequestParameter相关组 此组分别对应着 6.3、The AddRequestParameter GatewayFilter Factory在请求中添加参数 6.19、The RemoveRequestParameter GatewayFilter Factory删除请求中的参数值 这两个同时演示在配置文件中配置 filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置指定请求头内容 - AddRequestHeaderX-Request-zm2,zmValue2 #请求头kv如果一头含有多参数则重写一行设置- RemoveRequestHeader sec-fetch-site #删除请求头sec-fetch-site- SetRequestHeadersec-fetch-mode,myNavigate #把请求头名为sec-fetch-mode的请求值修改成myNavigate- AddRequestParametercustomerID,9527 #新增请求参数的kv- RemoveRequestParametercustomerName # 删除url请求参数customerName你传递过来也是null修改一下GateWayController获取请求的参数并输出。 //测试The AddRequestHeader GatewayFilter Factory
GetMapping(/pay/gateway/filter)
public ResultDataString getGatewayFilter(HttpServletRequest request){String result ;EnumerationString headers request.getHeaderNames();while (headers.hasMoreElements()) {String headName headers.nextElement();String headerValue request.getHeader(headName);System.out.println(请求头名headName\t\t\t请求值headerValue);if (headName.equalsIgnoreCase(X-Request-zm1) || headName.equalsIgnoreCase(X-Request-zm2)){result result headName \t headerValue ;}}System.out.println(----------------------------------);String customerID request.getParameter(customerID);System.out.println(参数customerIDcustomerID);System.out.println(----------------------------------);String customerName request.getParameter(customerName);System.out.println(参数customerNamecustomerName);System.out.println(----------------------------------);return ResultData.success(getGatewayFilter过滤器testresult \t DateUtil.now());
}启动测试我们先不添加参数直接发请求。 然后在请求中添加参数http://localhost:9527/pay/gateway/filter?customerID6666customerNamezzzmmm 原先在配置文件中写死的customerID会被你的请求覆盖掉而参数customerName是要被移除的参数所以你传入什么都会被移除为null 响应头(ResponseHeader)相关组 6.4.The AddResponseHeader GatewayFilter Factory添加响应头6.30.The SetResponseHeader GatewayFilter Factory修改响应头6.20.The RemoveResponseHeader GatewayFilter Factory移除响应头 配置文件修改新增一个响应头X-Response-zm并设值为ZMResponse,修改响应头时间Date为2066-12-25然后将自带的Content-Type 响应属性删除。 filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置指定请求头内容 - AddRequestHeaderX-Request-zm2,zmValue2 #请求头kv如果一头含有多参数则重写一行设置- RemoveRequestHeader sec-fetch-site #删除请求头sec-fetch-site- SetRequestHeadersec-fetch-mode,myNavigate #把请求头名为sec-fetch-mode的请求值修改成myNavigate- AddRequestParametercustomerID,9527 #新增请求参数的kv- RemoveRequestParametercustomerName # 删除url请求参数customerName你传递过来也是null- AddResponseHeaderX-Response-zm,ZMResponse #新增一个响应头X-Response-zm并设值为ZMResponse- SetResponseHeaderDate,2066-12-25 #修改响应头时间Date为2066-12-25- RemoveResponseHeaderContent-Type #将自带的Content-Type 响应属性删除重启服务查看效果 前缀和路径相关组 6.14.The PrefixPath GatewayFilter Factory自动添加路劲前缀 之前正确的地址http://localhost:9527/pay/gateway/filter - id: pay_routh3uri: lb://cloud-payment-servicepredicates:# - Path/pay/gateway/filter/**- Path/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filterfilters:- PrefixPath/pay #和上面的Path拼在一起就是http://localhost:9527/pay/gateway/filter现在的访问地址就是http://localhost:9527/gateway/filte了 如果还访问原来的地址就是404了 6.29.The SetPath GatewayFilter Factory修改访问路劲 此时访问的地址就变成了localhost:9527/ZM/abcd/filter才能正确访问 - id: pay_routh3uri: lb://cloud-payment-servicepredicates:# - Path/pay/gateway/filter/**# - Path/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filter- Path/ZM/abcd/{segment} #修改访问路径{segment}占位符的内容会和SetPath的{segment}保持一致filters:- SetPath/pay/gateway/{segment}最后的那个占位符写错你就访问不了了 那如果把占位符的写对前面自己改的写错呢 还是不能访问的连前面都不匹配。 6.16. The RedirectTo GatewayFilter Factory重定向到某界面 - id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**# - Path/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filter#- Path/ZM/abcd/{segment} #修改访问路径{segment}占位符的内容会和SetPath的{segment}保持一致filters:- RedirectTo302,http://www.zmblog.vip #访问localhost:9527/pay/gateway/filter/ 直接跳转到我的个人博客上直接跳转过来 其它 6.38.Default Filters配置在此处相当于全局通用自定义秒变Global。 GlobalFilter 接口的签名与 GatewayFilter 相同。这些是特殊的过滤器有条件地应用于所有路由。
6.4.3、Gateway自定义Filter
自定义全局Filter
对于我们之前说的那个面试题解决统计接口调用耗时情况的怎么实现就可以使用自定义全局过滤器的方式搞定。
官网说明 我们参照官网的示例写出自己的全局Filter
Component
Slf4j
public class MyGlobal implements GlobalFilter, Ordered {public static final String START_TIME start_time;Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {//先记录访问接口开始的时间exchange.getAttributes().put(START_TIME,System.currentTimeMillis());//调用chain.filter(exchange)将请求传递给下一个过滤器或最终的目标服务。//使用then方法确保在请求处理完成后执行一些操作return chain.filter(exchange).then(Mono.fromRunnable(() - {Long startTime exchange.getAttribute(START_TIME);if (startTime ! null) {log.info(访问接口的主机{},exchange.getRequest().getURI().getHost());log.info(访问接口端口{},exchange.getRequest().getURI().getPort());log.info(访问接口URL{},exchange.getRequest().getURI().getPath());log.info(访问接口的URL参数{},exchange.getRequest().getURI().getRawQuery());log.info(访问接口的时长{},(System.currentTimeMillis()-startTime)毫秒);System.out.println(----------------------分割线---------------------------);}}));}//返回的值越小优先级别越高Overridepublic int getOrder() {return -1;}
} 配置文件yml gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名#uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates:- Path/pay/gateway/get/** # 断言路径相匹配的进行路由- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID)没有固定规则但要求唯一建议配合服务名# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/info/** # 断言路径相匹配的进行路由- id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**filters:- AddRequestHeaderX-Request-zm1,zmValue1 #请求头kv如果一头含有多参数则重写一行设置指定请求头内容- AddRequestHeaderX-Request-zm2,zmValue2 重启测试一下 自定义条件Filter
自定义单一内置过滤器GatawayFilter可以先参考GateWay内置出厂默认的然后照猫画虎具体的内容感觉还有点像前面自定义的断言。 自定义网关过滤器规则 新建类名XXX需要以GatewayFilterFactory结尾并继承AbstractGatewayFilterFactory类 新建MyGatewayFilterFactory.Config内部类 public static class Config{GetterSetterprivate String status;//设置一个状态值或者标志位它等于多少匹配上才能访问
}重写apply方法 Override
public GatewayFilter apply(Config config) {return new GatewayFilter() {Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request exchange.getRequest();System.out.println(进入了自定义网关过滤器MyGatewayFilterFactorystatus--config.getStatus());if (request.getQueryParams().containsKey(zm)) {return chain.filter(exchange);}else {exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);return exchange.getResponse().setComplete();}}};
}重写shortcutFieldOrder方法 Override
public ListString shortcutFieldOrder() {return Arrays.asList(status);
}空参构造方法内部调用super public MyGatewayFilterFactory(){super(MyGatewayFilterFactory.Config.class);
}完整MyGatewayFilterFactory类 Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactoryMyGatewayFilterFactory.Config {public MyGatewayFilterFactory(){super(MyGatewayFilterFactory.Config.class);}Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request exchange.getRequest();System.out.println(进入了自定义网关过滤器MyGatewayFilterFactorystatus--config.getStatus());if (request.getQueryParams().containsKey(zm)) {return chain.filter(exchange);}else {exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);return exchange.getResponse().setComplete();}}};}Overridepublic ListString shortcutFieldOrder() {return Arrays.asList(status);}public static class Config{GetterSetterprivate String status;//设置一个状态值或者标志位它等于多少匹配上才能访问}}全局配置文件中配置yml - id: pay_routh3uri: lb://cloud-payment-servicepredicates:- Path/pay/gateway/filter/**# - Path/gateway/filter/** #实际上你应该在浏览器输入http://localhost:9527/gateway/filter#- Path/ZM/abcd/{segment} #修改访问路径{segment}占位符的内容会和SetPath的{segment}保持一致filters:- Myzm #自定义的过滤器重启9527测试 先还按照原来的不带任何参数看能不能访问 当然不能访问然后添加参数的参数的value是什么我们就没有做限制随便。 带着我们设置的参数就可以访问了。