在微服务架构中,Gateway(网关)是一个重要的组件,负责处理外部请求并将它们路由到适当的微服务。以下是Gateway在微服务中的一些主要功能:
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
server:
port: 9000
spring:
cloud:
consul:
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
host: 127.0.0.1
port: 8500
gateway:
routes:
- id: order
uri: http://localhost:80
predicates:
- Path=/feign/pay/gateway/**
这里将网关微服务添加进了consul配置中心,并且配置了一条到http://localhost:80,请求路径为/feign/pay/gateway/**的路由路径,这里的请求服务器我们是写死的,不便于代码的维护和更改。
@SpringBootApplication
public class Gateway {
public static void main(String[] args) {
SpringApplication.run(Main9527.class,args);
}
}
server:
port: 9000
spring:
cloud:
consul:
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
host: 127.0.0.1
port: 8500
gateway:
routes:
- id: order
uri: lb://cloud-consumer-openfeign-order
predicates:
- Path=/feign/pay/gateway/**
此时我们使用的不再是固定的主机,而是使用微服务名来查找对应的服务,并且使用lb(loadBalancer)实现了网关的负载均衡作用
Spring Cloud Gateway 将路由匹配为 Spring WebFluxHandlerMapping基础架构的一部分。Spring Cloud Gateway 包含许多内置的路由谓词工厂。所有这些谓词都匹配 HTTP 请求的不同属性。您可以将多个路由谓词工厂与逻辑and语句结合起来。(谓词工厂也就是我们所说的断言)
在springcloud-gateway官网可以看到,谓词有十几种
我们运行搭建好的gateway网关微服务,查看控制台可以看到如下信息,正好与官网的分类一一对应
用法示例: 如下示例当中会发现我实际上相当于设置了两个predicates(断言),path也算是一个,After又是一个,在实际开发当中,根据自己的实际场景可以随便使用断言。
server:
port: 9000
spring:
application:
name: cloud-gateway
cloud:
consul:
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
host: localhost
port: 8500
gateway:
#配置路由路径映射
routes:
- id: pay
uri: lb://cloud-pay
predicates:
- Path= /pay/gateway/*/**
#- My=zxc
#- After= 2024-04-03T18:44:12.217681800+08:00[Asia/Shanghai] #设置在时间后访问
#- Method=GET #基于请求方式的断言
#- RemoteAddr=192.168.100.1/24 #限制ip访问
#- Query=username,\d+ 基于请求参数的断言
#- Host=192.168.**.** #基于主机的断言,可以使用通配符
#- Header=X-request-Id,\d+ #基于请求头的断言
#- Cookie=username,zxc #基于请求Cookie的断言
#- Before= 2024-04-03T18:46:12.217681800+08:00[Asia/Shanghai] #设置在时间前访问
#- Between= 2024-04-03T18:46:12.217681800+08:00[Asia/Shanghai], 2024-04-03T18:49:12.217681800+08:00[Asia/Shanghai] #设置在时间段内前访问
需求:自定义用户等级userA\userB\userC,通过断言适配其是否拥有访问权限
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
//配置文件短促写法
@Override
public List shortcutFieldOrder() {
return Arrays.asList("userType");
}
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
@Override
public Predicate apply(MyRoutePredicateFactory.Config config)
{
return new Predicate()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
}
server:
port: 9000
spring:
cloud:
consul:
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
host: 127.0.0.1
port: 8500
gateway:
routes:
- id: pay_gateway
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/**
# - name: My #未从写shortcutFieldOrder方法则只能使用全配置
# args:
# userType: auser
- My=auser #重写shortcutFieldOrder方法则能使用短配置
请求对应服务,分别尝试访问时不添加参数和添加参数userType=userA
过滤器可以在执行方法前和执行方法后进行过滤,所谓的过滤就是可以在请求上加一些操作,例如匹配到路由后可以在请求上添加个请求头,或者参数等等。
Gateway过滤器分为了两种:路由过滤器 和 全局过滤器:
1、路由过滤器:路由过滤器针对于某一个路由进行使用,其中官网给我们提供了30多种类型的路由过滤器。
2、全局过滤器:全局的往往是我们经常会用到的,他和路由过滤器区别就是,他是针对于所有路由进行设置的过滤规则,实际开发当中很少会针对于某一个路由使用Filter,大部分都会采用全局过滤器。
在配置时使用Default Filters相当于全局配置
server:
port: 9000
spring:
application:
name: cloud-gateway
cloud:
consul:
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
host: localhost
port: 8500
gateway:
#配置路由路径映射
routes:
#gateway过滤器配置
- id: cloud-pay02
uri: lb://cloud-pay
predicates:
- Path= /pay/gateway/filter/**
#添加请求路径头部
#- Path= /abc/pay/gateway/filter/**
#修改请求路径
#- Path= /abc/abc/{segment}
#删除请求路径头部
#- Path= /gateway/filter/**
filters:
#出现错误重定向(302临时重定向)
#- RedirectTo=302,http://www.baidu.com/
#修改请求路径
#- SetPath=/pay/gateway/{segment}
#添加请求路径前部
#- PrefixPath=/pay
#添加请求头信息
- AddRequestHeader=X-Request-zxc1,zxc1
- AddRequestHeader=X-Request-zxc2,zxc2
#删除请求头信息
- RemoveRequestHeader=sec-fetch-site,none
#修改请求头信息
- SetRequestHeader=x-request-zxc1,qh
#添加和删除请求参数
- AddRequestParameter=id,123
- RemoveRequestParameter=name
#添加删除修改响应头信息
#- AddResponseHeader=name,zxc
- SetResponseHeader=Date,2019
- RemoveResponseHeader=Transfer-Encoding
需求:使用网关实现接口日志,统计接口调用时间
@Component
@Slf4j
public class MyGlobalFiter implements GlobalFilter, Ordered {
public static final String BEGIN_VISIT_TIME = "begin_visit_time"; //开始调用时间
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//先记录访问接口时间
exchange.getAttributes().put(BEGIN_VISIT_TIME,System.currentTimeMillis());
//返回统计的结果给后台
return chain.filter(exchange).then(Mono.fromRunnable(()->{
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if(beginVisitTime != null){
log.info("访问接口主机:" + exchange.getRequest().getURI().getHost());
log.info("访问接口端口:" + exchange.getRequest().getURI().getPort());
log.info("访问接口路径:" + exchange.getRequest().getURI().getPath());
log.info("访问接口参数:" + exchange.getRequest().getURI().getRawQuery());
log.info("访问接口时长:" + (System.currentTimeMillis() - beginVisitTime) + "毫秒");
log.info("===分割线======================================================================");
}
}));
}
/**
* 数字越小优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
全局过滤器不需要配置
需求:请求头携带相关参数才能访问
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory {
public MyGatewayFilterFactory(){
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info("进入了自定义网关过滤器MyGatewayFilterFactory,status: " + config.getStatus());
if(request.getQueryParams().containsKey("zxc")){
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
@Override
public List shortcutFieldOrder() {
return Arrays.asList("status");
}
//内部类
//设定一个状态值,满足才可以访问
public static class Config{
@Getter
@Setter
private String status;
}
}
配置文件:
server:
port: 9000
spring:
cloud:
consul:
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
host: 127.0.0.1
port: 8500
gateway:
routes:
- id: order
uri: lb://cloud-consumer-openfeign-order
predicates:
- Path=/feign/pay/gateway/**
filters:
- My=zxc
测试:
请求对应的接口,分别尝试携带key为zxc的参数,值随意,和不携带参数的请求是否可以正常访问