Gateway
# 基础入门
# 功能

# 创建项目
使用网关需要用到注册中心nacos
引入 spring-cloud-starter-gateway 、 spring-cloud-starter-alibaba-nacos-discovery
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
将网关注册到注册中心中,创建 application.yml 编写如下配置
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
server:
port: 80
在启动类中标注@EnableDiscoveryClient启用注册到nacos
# 配置网关
创建 application-route.yml 编写如下配置,并将文件导入application.yml中
spring:
profiles:
include: route # 加载名为application-route.yml的配置文件
--- # 或者
spring:
config:
import:
- classpath:application-route.yml # 显式指定文件路径
spring:
cloud:
gateway:
routes:
- id: order
uri: lb://service-order
predicates:
- Path=/api/order/**
- id: product
uri: lb://service-product
predicates:
- Path=/api/product/**
predicates断言:找到匹配的路径就会转发给uri指定路径
此时启动后提示服务不可用,是因为spring将负载均衡单独抽取出来,需要在单独导入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
# 改造微服务
为 service-order、service-prduct 添加 /api 基础路径
此时访问会404,因为网关只是将路径转发,而我们的项目web前缀路径是没有api的,所以可以为Controller类指定一个前缀路径@RequestMapper("api/order"),但是@RequestMapper不一定可以标注在@SentinelClient上,@SentinelClient注解提供了一个参数path可以指定前缀路径
# 原理
每个网关路由规则都有一个全局唯一的ID;
路由规则根据断言匹配到请求会转发给URI目的地;
转发途中会经过我们设置Filter过滤器;
规则按照顺序匹配,第一个规则匹配后就不会匹配后面的路由规则,可以通过order指定匹配顺序,越小越优先;
# Predicate - 断言
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Path
args:
patterns: /api/order/**
matchTrailingSlash: true
matchTrailingSlash匹配末尾''/'',如果设置为false,/api/order/**
和/api/order/**/
是两个路径
在idea中可以ctrl+h查看RoutePredicateFactory的实现中看到所有断言的名字
# 全写法Fully Expanded Arguments
spring:
cloud:
gateway:
routes:
- id: bing_route
uri: https://cn.bing.com
predicates:
- name: Path
args:
patterns: /search
- name: Query
args:
param: q
regexp: haha
# 短写法
spring:
cloud:
gateway:
routes:
- id: bing_route
uri: https://cn.bing.com
predicates:
- name: Path
args:
patterns: /search
- Query=q,haha
# 断言规则
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
名 | 参数(个数/类型) | 作用 |
---|---|---|
After | 1/datetime | 在指定时间之后 |
Before | 1/datetime | 在指定时间之前 |
Between | 2/datetime | 在指定时间区间内 |
Cookie | 2/string,regexp(正则) | 包含cookie名且必须匹配指定值 |
Header | 2/string,regexp | 包含请求头且必须匹配指定值 |
Host | N/string | 请求host必须是指定枚举值 |
Method | N/string | 请求方式必须是指定枚举值 |
Query | 2/string,regexp | 包含指定请求参数 |
RemoteAddr | 1/List | 请求来源于指定网络域(CIDR写法) |
Weight | 2/string,int | 按指定权重负载均衡 |
XForwardedRem oteAddr | 1/List | 从X-Forwarded-For请求头中解析请求来源, 并判断 是否来源于指定网络域 |
例子:
spring:
cloud:
gateway:
routes:
- id: bing-route
uri: https://cn.bing.com/
predicates:
- name: Path
args:
patterns: /search
- name: Query
args:
param: q
regexp: haha
order: 10
metadata:
hello: world
匹配条件:
- 路径:/search
- 请求参数:q的值为haha
# 自定义断言工厂⭐
继承AbstractRoutePredicateFactory并自定义配置文件VipRoutePredicateFactory,带两个参数param、value
重写shortcutFieldOrder
package com.hmdp.predicate;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
public VipRoutePredicateFactory() {
super(Config.class);
}
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
HttpServletRequest request = serverWebExchange.getRequest();
String value = request.getQueryParams().getFirst(config.getParam());
return StringUtils.hasText(value) && value.equals(config.getValue());
}
}
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "value");
}
@Validated
public static class Config {
@NotEmpty
private String param;
@NotEmpty
private String value;
public @NotEmpty String getParam() {
return param;
}
public void setParam(@NotEmpty String param) {
this.param = param;
}
public @NotEmpty String getValue() {
return value;
}
public void setValue(@NotEmpty String value) {
this.value = value;
}
}
}
- 断言名字会自动匹配为~RoutePredicateFactory前的名字,即Vip
- 短写法的两个参数顺序会匹配到Arrays.asList("param", "value")上的顺序
spring:
cloud:
gateway:
routes:
- id: bing_route
uri: https://cn.bing.com
predicates:
- name: Path
args:
patterns: /search
- name: Query
args:
param: q
regexp: haha
- Vip=user,hehe
路径需要满足/search?q=haha&user=hehe
# Filter - 过滤器

名 | 参数(个数/类型) | 作⽤ |
---|---|---|
AddRequestHea der | 2/string | 添加请求头 |
AddRequestHea dersIfNotPresen t | 1/List | 如果没有则添加请求头, key:value⽅式 |
AddRequestPar ameter | 2/string 、string | 添加请求参数 |
AddResponseHe ader | 2/string 、string | 添加响应头 |
CircuitBreaker | 1/string | 仅⽀持forward:/inCaseOfFailureUseThis⽅式进⾏ 熔断 |
CacheRequestB ody | 1/string | 缓存请求体 |
DedupeRespons eHeader | 1/string | 移除重复响应头, 多个⽤空格分割 |
FallbackHeader s | 1/string | 设置Fallback头 |
JsonToGrpc | 请求体Json转为gRPC | |
LocalResponse Cache | 2/string | 响应数据本地缓存 |
MapRequestHea der | 2/string | 把某个请求头名字变为另—个名字 |
ModifyRequestB ody | 仅 Java 代码⽅式 | 修改请求体 |
ModifyRespons eBody | 仅 Java 代码⽅式 | 修改响应体 |
PrefixPath | 1/string | ⾃动添加请求前缀路径 |
PreserveHostHe ader | 0 | 保护Host头 |
RedirectTo | 3/string | 重定向到指定位置 |
RemoveJsonAttr ibutesResponse Body | 1/string | 移除响应体中的某些Json字段, 多个⽤ ,分割 |
RemoveRequest Header | 1/string | 移除请求头 |
RemoveRequest Parameter | 1/string | 移除请求参数 |
RemoveRespon seHeader | 1/string | 移除响应头 |
RequestHeader Size | 2/string | 设置请求⼤⼩, 超出则响应431状态码 |
RequestRateLim iter | 1/string | 请求限流 |
RewriteLocation ResponseHeade r | 4/string | 重写Location响应头 |
RewritePath | 2/string | 路径重写 |
RewriteRequest Parameter | 2/string | 请求参数重写 |
RewriteRespons eHeader | 3/string | 响应头重写 |
SaveSession | 0 | session保存, 配合spring-session框架 |
SecureHeaders | 0 | 安全头设置 |
SetPath | 1/string | 路径修改 |
SetRequestHea der | 2/string | 请求头修改 |
SetResponseHe ader | 2/string | 响应头修改 |
SetStatus | 1/int | 设置响应状态码 |
StripPrefix | 1/int | 路径层级拆除 |
Retry | 7/string | 请求重试设置 |
RequestSize | 1/string | 请求⼤⼩限定 |
SetRequestHost Header | 1/string | 设置Host请求头 |
TokenRelay | 1/string | OAuth2的token转发 |
# 例子
filters:
- RewritePath=/api/order/?(?<segment>.*),/$\{segment} # 路径重写
- AddResponseHeader=X-Response-Abc,123 # 添加请求头
# 默认DefaultFliter
filters:
- RewritePath=/api/order/?(?<segment>.*),/S\{segment}
# - AddResponseHeaderX-Response-Abc,123
default-filters:
AddResponseHeader=X-Response-Abc,123
# 全局GlobalFilter
对所有请求都生效的Filter,
package com.hmdp;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@SLf4j
public class RtGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getURI().tostring();
long start = System.currentTimeMillis();
log.info("请求【{}】开始,时问,{}", uri, start);
//=====================以上是前置逻======================
Mono<Void> filter = chain.filter(exchange)
.doFinally((result) -> {
long end = System.currentTimeMillis();
log.info("请【{}】结束:时问:{},E时:ms", uri, end, end - start);
});//放行 10s
//=============以下是后置逻提================
return null;
}
@Override
public int getorder() {
return 0;
}
}
特性 | default-filters | globalFilter |
---|---|---|
配置方式 | 通过配置文件(YAML/Properties)集中定义 | 通过代码实现(GlobalFilter 接口) |
执行顺序 | 在路由级 filters 之前 执行 | 通过 @Order 或 Ordered 控制 |
灵活性 | 仅支持内置过滤器(如 AddResponseHeader ) | 支持完全自定义逻辑 |
适用场景 | 简单的全局响应/请求修改 | 复杂的全局逻辑(如鉴权、日志) |
# 自定义Filter⭐
命名规则:~GatewayFilterFactory
package com.hmdp.predicate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import reactor.core.publisher.Mono;
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(()->{
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers=response.getHeaders();
String value = config.getValue();
if ("uuid".equalsIgnoreCase(value)) {
value=UUID.randomUUID().toString();
}
if ("jwt".equalsIgnoreCase(value)) {
value=JwtUtils.generateToken();
}
headers.add(config.getName(),value);
}));
}
};
}
}
例子
spring:
cloud:
gateway:
routes:
- id: bing_route
uri: https://cn.bing.com
predicates:
- name: Path
args:
patterns: /api/order/**
filters:
- RewritePath=/api/order/?(?<segment>,*),/$\{segment}
- OnceToken=X-Response-Token, uuid #/base64/jwt # 自定义Filter
# 面试题——如何解决跨域问题
如果Controller过多,就需要在每个Controller上都添加@CrosOrigin注解 而过滤器CrosFilter需要在每一个服务都配置 在分布式系统中可以使用gateway网关中配置过滤器设置统一跨域
spring:
cloud:
gateway:
globalcors:
cors-configurations: # 是一个Map,/**允许所有的请求
'[/**]':
allowed-origin-patterns: '*'
allowed-headers: '*'
allowed-methods: '*'
针对性的允许跨域
spring:
cloud:
gateway:
routes:
- id: cors_route
uri: https://example.org
predicates:
- Path=/service/**
metadata:
cors:
allowedorigins: '*'
allowedMethods:
- GET
- POST
allowedHeaders: '*'
maxAge: 30
# 面试题——微服务之间的调用经过网关吗
不经过,微服务之间的调用通过从注册中心获取服务的地址,然后直接进行调用,
也可以经过网关,将FeignClient(openfeign)的服务名改为网关地址,网关会根据路由规则(如/api/service/**
)转发到对应服务。
在实际使用中经过网关调用服务不符合结构设计,网关是前端与后台的之间调用,而且通过网关调用服务中间多了一层