Predicate是Java8中引入的一个新功能,和我们平时写单元测试的时候Assertion差不多,Predicate是接收一个判断条件,返回一个ture或false的布尔值结果,告知调用发判断结果。也可以通过and、or和negative(非)三个操作符多个Predicate串联在一块共同判断
Predicate其实就是我们和Gateway对接的数据暗号,比如要求你的Request中必须带有某个指定的参数叫name,对应的值必须是一个指定的人名(Gavin),如果Request中没有包含name,或者名字不是Gavin,断言就失败了,只有标示和值都一样的情况下才会通过
一个请求在抵达网关层后,首先就要进行断言匹配,在满足所有断言之后才会进入Filter阶段
Gateway提供了十多种内置断言,介绍一些常用的
Path断言是最常用的一个断言请求,几乎所有的路由都要用到
.route(r -> r.path("/gateway/**")
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
.route(r -> r.path("/baidu")
.uri("http://www.baidu.com")
)
Path断言的使用非常简单,就像我们在Controller中配置@RequestPath的方式一样,在Path断言中填入一段URL匹配规则,当实际请求的URL和断言中的规则相匹配的时候,就下发到该路由中URI指定地址,这个地址可以是一个具体的HTTP地址,也可以是一个Eureka中注册的服务名称,路由规则可以一次编写多个绑定关系
这个断言是专门验证HTTP Method的
.route(r -> r.path("/gateway/**")
.and().method(HttpMethod.GET)
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
将Path断言通过一个and连接符和method关联起来,我们如果访问/gateway/sample并且method是GET时才会适配上面的路由规则
请求断言也是在业务中经常使用的,他会从ServerHttpRequest中的Parameters列表中查询指定的属性,
.route(r -> r.path("/gateway/**")
.and().method(HttpMethod.GET)
.and().query("name","test")
.and().query("age")
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
header断言是检查头信息里是否携带了相关属性或令牌
.route(r -> r.path("/gateway/**")
.and().header("Authorization")
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
cookie验证的是cookie中保存的信息,cookie断言和上面介绍的几种断言方式都大同小异,唯一不同的是他必须连同属性值一起验证,不能单独只验证属性是否存在
.route(r -> r.path("/gateway/**")
.and().cookie("name","test")
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
时间匹配有三种模式,分别是Before、After和Between,这些断言指定了在什么时间范围内路由才会生效
.route(r -> r.path("/gateway/**")
.and().before(ZonedDateTime.now().plusMinutes(1))
.uri("lb://FEIGN-SERVICE-PROVIDER/")
)
去到gateway-server项目的yaml配置文件里进行配置
# 新的配置routes和discovery是平级的
# id是这个断言的唯一标识
# uri是如果匹配上所有断言,请求将转发到这里
# StripPrefix相当于把 localhost:50080/gavinrouter/sayhello替换成 FEIGN-CLIENT/sayhello
spring:
application:
name: gateway-server
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: feignclient
uri: lb://FEIGN-CLIENT
predicates:
- Path=/gavinrouter/**
filters:
- StripPrefix=1
配置完成后可以通过下面的路径访问
http://localhost:50080/gavinrouter/sayhello
创建一个config包,在里面创建GatewayConfiguration
package com.icodingedu.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
@Configuration
public class GatewayConfiguration {
@Bean
@Order
public RouteLocator customerRouters(RouteLocatorBuilder builder){
return builder.routes()
.route(r -> r.path("/gatewayjava/**")
.and().method(HttpMethod.GET)
.filters(f -> f.stripPrefix(1)
.addResponseHeader("java-param","gateway-config")
)
.uri("lb://FEIGN-CLIENT")
).build();
}
}
修改后进行访问验证:http://localhost:50080/gavinjava/sayhello
gateway调用的是feign-client的业务,我们就要到feign-client里创建一个controller实现相应的功能
这里面使用的product需要提前在feign-client-intf中定义好
package com.icodingedu.springcloud.pojo;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Product {
private Long productId;
private String description;
private Long stock;
}
在feign-client中创建一个GatewayController
package com.icodingedu.springcloud.controller;
import com.icodingedu.springcloud.pojo.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@Slf4j
@RequestMapping("gateway")
public class GatewayController {
//Product要在feign-client-intf里提前定义好
public static final Map<Long, Product> items = new ConcurrentHashMap<>();
@GetMapping("detail")
public Product getProduct(Long pid){
//如果第一次没有就先创建一个
if(!items.containsKey(pid)){
Product product = Product.builder().productId(pid)
.description("new arrival")
.stock(100L).build();
items.putIfAbsent(pid,product);
}
return items.get(pid);
}
@GetMapping("placeOrder")
public String buy(Long pid){
Product product = items.get(pid);
if(product==null){
return "Product Not Found";
}else if(product.getStock()<=0L){
return "Sold Out";
}
//如果是单体应用,即便是集群也可以保留这个代码,集群解决需要用到分布式锁将控制放到中心节点即可
synchronized (product){
if(product.getStock()<=0L){
return "Sold Out";
}
product.setStock(product.getStock()-1);
}
return "Order Placed";
}
}
回到Gateway-server项目里,按照时间顺延的方式做断言定义
@Configuration
public class GatewayConfiguration {
@Bean
@Order
public RouteLocator customerRouters(RouteLocatorBuilder builder){
return builder.routes()
.route(r -> r.path("/gavinjava/**")
.and().method(HttpMethod.POST)
.and().query("name","gavin")
.filters(f -> f.stripPrefix(1)
.addResponseHeader("java-param","gateway-config")
)
.uri("lb://FEIGN-CLIENT")
)
.route(r -> r.path("/secondkill/**")
.and().after(ZonedDateTime.now().plusSeconds(30))
.filters(f -> f.stripPrefix(1))
.uri("lb://FEIGN-CLIENT")
)
.build();
}
}
可以精确的定义时间节点
@Configuration
public class GatewayConfiguration {
@Bean
@Order
public RouteLocator customerRouters(RouteLocatorBuilder builder){
LocalDateTime ldt = LocalDateTime.of(2020,10,24,20,31,10);
return builder.routes()
.route(r -> r.path("/gavinjava/**")
.and().method(HttpMethod.POST)
.and().query("name","gavin")
.filters(f -> f.stripPrefix(1)
.addResponseHeader("java-param","gateway-config")
)
.uri("lb://FEIGN-CLIENT")
)
.route(r -> r.path("/secondkill/**")
.and().after(ZonedDateTime.of(ldt, ZoneId.of("Asia/Shanghai")))
.filters(f -> f.stripPrefix(1))
.uri("lb://FEIGN-CLIENT")
)
.build();
}
}