简述
Spring Cloud Gateway
可以
反向代理
鉴权
流量控制
熔断
日志监控
其本身也是一个微服务,需要注册到服务注册中心
三大核心
其有三大核心概念
Route:路由,是构建网关的基本模块,由ID、目标URI、一系列的断言和过滤器组成,如果断言为
true
则匹配该路由Predicate:断言,
java.util.function.Predicate
,如果请求与断言相匹配则进行路由Filter:过滤器,指
GatewayFilter
的实例,可在请求被路由前或后对请求进行修改
工作流程
客户端
Gateway Client
向Spring Cloud Gateway
发起请求;到
Gateway Handler Mapping
中找到与请求相匹配的路由并将其发送到Gateway Web Handler
;Gateway Web Handler
再通过指定的过滤器链将请求发送到实际的服务Proxied Service
;这里的过滤器为pre(之前)类型;
可以做参数校验、权限校验、流量控制、日志输出、协议转换等;
服务
Proxied Service
处理完,返回响应;响应通过过滤器链,
Gateway Web Handler
和Gateway Handler Mapping
给到客户端Gateway Client
这里的过滤器为post(之后)类型;
可以做响应内容、响应头的修改、日志输出、流量监控等;
路由映射
内部服务
接口控制器
package com.xlyo.springgatewaylearnservice.controller;
import jakarta.annotation.PostConstruct;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/pay")
public class PayController {
private static final List<String> payList = new ArrayList<>();
@PostConstruct
public void init() {
payList.addAll(List.of("Apple", "Banana", "Orange", "Pineapple", "Mango"));
}
@GetMapping("/list")
public List<String> list() {
return payList;
}
@GetMapping("/add")
public String add(String fruit) {
payList.add(fruit);
return "Added " + fruit + " to the list.";
}
}
网关
pom依赖
现在的spring cloud gateway
是响应式的,不需要引入spring-boot-starter-web
<properties>
<spring-cloud.version>2023.0.1</spring-cloud.version>
</properties>
<dependencies>
<!-- Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置文件
spring:
application:
name: spring-learn-gateway
cloud:
gateway:
routes:
- id: pay_route1 # 路由ID,需唯一
uri: http://localhost:8080 # 匹配后面提供服务的路由地址
predicates:
- Path=/pay/list/** # 断言,与路径相匹配的进行路由
- id: pay_route2
uri: http://localhost:8080
predicates:
- Path=/pay/add
server:
port: 9527
测试
现在访问网关可以直接访问到内部的微服务
GET http://localhost:9527/pay/list
动态获取服务URI
提供微服务的名称动态获取服务的URI
使用负载均衡和微服务名称进行动态获取lb://微服务名称
spring:
cloud:
gateway:
routes:
- id: pay_route1
# uri: http://localhost:8080 # 采用动态获取方式
uri: lb://spring-gateway-learn-service
predicates:
- Path=/pay/list/**
- id: pay_route2
uri: lb://spring-gateway-learn-service
predicates:
- Path=/pay/add
Predicate
简述
用来控制允不允许访问目标路由地址
其能断言的前缀(如上述的Path
)全都来自RoutePredicateFactory的实现类,默认提供如下
常用断言API
After
在某个时间之前禁止访问
使用ZonedDateTime
获取时间字符串,然后填入
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
Before
在某个时间之后禁止访问
Between
在某个时间之间禁止访问
Cookie
需要2个参数,一个是Cookie
名称,另一个是正则表达式
其值匹配正则表达式才能允许访问
Header
需要2个参数,一个是请求头属性名称,另一个是正则表达式
其值匹配正则表达式才能允许访问
Host
需要域名进行匹配,匹配成功允许访问
Method
对HTTP请求的方法进行匹配,匹配成功允许访问
Path
与路径匹配的允许访问
Query
需要2个参数,一个是请求URL中的属性名称,另一个是正则表达式
请求URL中的属性值匹配正则表达式才能允许访问
RemoteAddr
客户端(外部)的IP地址匹配网段的允许访问
自定义断言
命名规范XxxxRoutePredicateFactory
继承AbstractRoutePredicateFactory或实现RoutePredicateFactory接口
MyRoutePredicateFactory
package com.xlyo.springlearngateway.predicate;
import io.micrometer.common.util.StringUtils;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* 这里实现通过配置用户的会员等级来判断是否可以访问
* 会员等级:Normal、Advanced、VIP、SVIP
*/
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(MyRoutePredicateFactory.Config.class); // super传递断言规则内部类
}
/**
* 根据配置的会员等级来判断是否可以访问
*/
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {
return serverWebExchange -> {
// 获取用户的会员等级
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (StringUtils.isBlank(userType)) return false; // 如果用户没有会员等级,则不允许访问
// 判断用户的会员等级是否符合要求
return userType.equals(config.getUserType());
};
}
/**
* 支持Shortcut风格的配置
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
/**
* 断言规则配置内部类
* 在配置文件中配置
*/
@Data
@Validated
public static class Config {
@NotEmpty
private String userType; // 会员等级:Normal、Advanced、VIP、SVIP
}
}
配置文件
spring:
cloud:
gateway:
routes:
- id: pay_route1
uri: http://localhost:8080
predicates:
- Path=/pay/list/**
- My=VIP # 自定义断言
测试
现在请求参数带userType
并且值为配置文件中的值才能允许访问
GET http://localhost:9527/pay/list?userType=VIP
Filter
全局默认过滤器
Clobal Filter
单一内置过滤器
Gateway Filter
自定义过滤器
单一内置过滤器
可以
PrefixPath、SetPath和RedirectTo这三个与路由相关比较重要
自定义全局过滤器
以下以统计接口调用耗时为目标设计自定义全局过滤器
package com.xlyo.springlearngateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.time.Instant;
/**
* 全局过滤器,用于记录请求的调用时间
*/
@Slf4j
@Component
public class DisplayCallTimeGlobalFilter implements GlobalFilter, Ordered {
// 开始调用方法的时间
private static final String BEGIN_CALL_TIME = "begin_call_time";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 记录开始调用的时间到请求的attribute中
exchange.getAttributes().put(BEGIN_CALL_TIME, Instant.now().toEpochMilli());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(BEGIN_CALL_TIME);
if (startTime!= null) {
URI requestUri = exchange.getRequest().getURI();
Long endTime = Instant.now().toEpochMilli();
log.info("请求[{}]耗时:{}ms", requestUri, endTime - startTime);
}
}));
}
/**
* 设置过滤器的顺序(数字越小,优先级越高)
*/
@Override
public int getOrder() {
return 0;
}
}
自定义条件过滤器
与单一内置过滤器的用法相同
package com.xlyo.springlearngateway.filter;
import lombok.Data;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String status = request.getQueryParams().getFirst("status");
if (status!= null && status.equals(config.getStatus())) {
return chain.filter(exchange);
}
// 若状态参数不匹配,则返回400
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
};
}
@Override
public List<String> shortcutFieldOrder() {
return List.of("status");
}
/**
* 自定义配置内部类
*/
@Data
public static class Config {
private String status; // 设定一个状态参数,用于对应值匹配才能访问
}
}
spring:
application:
name: spring-learn-gateway
cloud:
gateway:
routes:
- id: pay_route1
uri: http://localhost:8080
predicates:
- Path=/pay/list/**
filters:
- My=123 # 自定义条件过滤器