文章目录
-
-
- [🧭 路由与转发](#🧭 路由与转发)
- 安全与认证(守门员职责)
- 统一监控与运维(可观测性)
-
- 一、创建网关
-
- [1. **添加依赖**:](#1. 添加依赖:)
- 2.**编写启动类**:
- 3.**配置路由规则**:
- 二、断言
-
- [1. 长短写法](#1. 长短写法)
-
- [✍️ 简短写法(Shortcut Configuration)](#✍️ 简短写法(Shortcut Configuration))
- [📝 完整写法(Fully Expanded Configuration)](#📝 完整写法(Fully Expanded Configuration))
- [🔄 两种写法对比](#🔄 两种写法对比)
- [2. 核心配置规则详情](#2. 核心配置规则详情)
-
-
- [1. 最常用的基础规则](#1. 最常用的基础规则)
- [2. 基于时间的规则](#2. 基于时间的规则)
- [3. 流量控制与灰度规则](#3. 流量控制与灰度规则)
- [4. 高级匹配规则](#4. 高级匹配规则)
-
- 三、过滤器
-
-
- [📊 过滤器全景概览](#📊 过滤器全景概览)
- [🔧 1. GatewayFilter:路由级别的过滤器](#🔧 1. GatewayFilter:路由级别的过滤器)
- [📦 2. default-filters:默认过滤器](#📦 2. default-filters:默认过滤器)
- [🧬 3. GlobalFilter:全局过滤器(代码方式)](#🧬 3. GlobalFilter:全局过滤器(代码方式))
-
网关(Gateway)在系统架构中,尤其是在微服务环境下,充当着 所有流量的"总入口"和"守门员"。它的核心职能是 "路由转发" 与 "通用问题处理"**的分离,让后端服务可以专注于业务逻辑本身。
具体来说,网关的核心功能主要分为以下几大类:
🧭 路由与转发
-
动态路由 :根据请求的路径(如
/user)、域名、Header等信息,将请求转发到对应的微服务。 -
负载均衡:当后端服务有多个实例时,网关能通过轮询、随机、IP Hash等算法分发请求,实现负载均衡。
-
协议转换:能在不同网络协议间"翻译",例如将外部的HTTP请求转换为内部服务使用的Dubbo、gRPC等RPC协议,或是支持WebSocket。
安全与认证(守门员职责)
网关是执行安全策略的理想位置,避免每个服务都重复实现安全逻辑。
-
身份认证:在网关层统一验证请求携带的Token、API Key、OAuth 2.0凭证等,确认"你是谁"。
-
授权与访问控制:配合认证结果,判断"你能做什么",例如通过IP黑白名单限制访问。
统一监控与运维(可观测性)
网关是流经所有请求的必经之路,提供了系统监控的黄金观测点。
-
日志与监控:集中记录所有请求的耗时、状态码、来源等信息,方便进行全链路追踪、性能分析和问题排查。
-
灰度发布与版本管理:通过路由规则,可以按比例将少量请求导向新版本服务,实现金丝雀发布(灰度发布),待验证稳定后再全量切换,降低上线风险。
一、创建网关
使用 Spring Cloud Gateway 创建网关,核心就是通过 路由(Route) 、断言(Predicate) 和 过滤器(Filter) 这三个概念,将请求灵活地转发给后端服务。
1. 添加依赖:
在 pom.xml 文件中,添加 Spring Cloud Gateway 的起步依赖。如果需要与服务注册中心(如 Nacos)集成,也一并引入。
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 以 Nacos 服务发现为例 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.编写启动类:
创建一个标准的 Spring Boot 启动类,并启用服务发现功能(如果使用了注册中心)。
java
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.配置路由规则:
在 application.yml 文件中进行核心配置。下面的例子中,网关启动在 18012 端口,并将所有 /provider1/** 开头的请求,负载均衡地转发到注册中心名为 service-provider 的服务上。
yaml
server:
port: 18012
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos 注册中心地址
gateway:
routes:
- id: service-provider-route # 路由ID,唯一即可
uri: lb://service-provider # lb:// 表示启用负载均衡,后面跟服务名
predicates:
- Path=/provider1/** # 断言:路径匹配
filters:
- StripPrefix=1 # 过滤器:去掉第一层路径前缀
| 配置项 | 说明 | 示例 |
|---|---|---|
id |
路由的唯一标识,自定义即可。 | user-service |
uri |
请求最终被转发到的目标地址。可以直接写 http:// 开头的固定地址,也可以使用 lb:// 前缀实现负载均衡。 |
lb://userservice (从注册中心根据服务名发现地址) |
predicates |
断言条件数组。所有条件都满足时,请求才会被该路由处理。 | - Path=/user/** (路径匹配) |
filters |
过滤器数组,在请求转发前后对请求/响应进行处理。 | - StripPrefix=1 (去掉路径中的第一层前缀) |
注意 :
lb://是负载均衡的缩写,使用前需要确保项目中已经集成了服务发现和负载均衡组件(如 Spring Cloud LoadBalancer)。
二、断言
断言就是路由的"准入条件"或"匹配规则"。 它决定了一个请求有没有资格被当前这个路由处理。
1. 长短写法
在 Spring Cloud Gateway 中,断言的"长短写法"指的是配置断言(Predicate)的两种不同语法风格。简单来说,就是**"简短写法"(Shortcut)和"完整写法"**(Fully Expanded)。它们最终实现的效果完全一样,只是配置方式不同。
✍️ 简短写法(Shortcut Configuration)
这是最常用、最简洁的方式,适合大多数场景。它通过键值对 或参数序列来配置,Spring Cloud Gateway 会按照约定自动解析。
-
格式 :
- PredicateName=参数1, 参数2, ... -
特点:代码量少,一目了然。
示例:
yaml
predicates:
# 1. 简单参数:路径匹配
- Path=/api/user/**
# 2. 多个参数,用逗号分隔:Cookie 名和值的正则
- Cookie=user_type, vip
# 3. 多个参数,用逗号分隔:Header 名和值的正则
- Header=X-Request-Id, \d+
# 4. 参数包含逗号的情况,用分号分隔:在指定时间之后
- After=2025-01-01T00:00:00+08:00[Asia/Shanghai]
📝 完整写法(Fully Expanded Configuration)
当断言需要配置的参数较多,或参数本身带有特殊字符(如逗号)时,使用完整写法会更清晰。它使用 name + args 的 Map 结构来明确定义。
-
格式 :用
name指定断言类型,用args以键值对形式指定参数。 -
特点:结构清晰,可读性强,适合复杂配置。
示例:
predicates:
# 1. 等同于:- Path=/api/user/**
- name: Path
args:
patterns: /api/user/**
# 2. 等同于:- Cookie=user_type, vip
- name: Cookie
args:
name: user_type
regexp: vip
# 3. 等同于:- Header=X-Request-Id, \d+
- name: Header
args:
header: X-Request-Id
regexp: \d+
🔄 两种写法对比
| 特性 | 简短写法 | 完整写法 |
|---|---|---|
| 可读性 | 简洁紧凑,但参数较多时可能不够直观 | 结构化,参数含义一目了然 |
| 配置量 | 更少 | 更多,需要写 name 和 args |
| 适用场景 | 日常开发,参数少且逻辑简单 | 复杂配置,或参数需精确指定键名时 |
| 本质 | 是完整写法的语法糖,最终会被转换为完整写法 | 网关底层解析的原生格式 |
2. 核心配置规则详情
Spring Cloud Gateway 内置了丰富的断言工厂,我按使用场景把它们分成了几类,方便你查阅。
1. 最常用的基础规则
| 断言类型 | 作用 | 简短写法示例 | 说明 |
|---|---|---|---|
| Path | 匹配请求路径 | - Path=/user/**, /order/** |
支持多个路径,用逗号分隔。这是最核心的规则。 |
| Method | 匹配请求方法 | - Method=GET, POST |
匹配指定的 HTTP 方法。 |
| Header | 匹配请求头 | - Header=X-Request-Id, \d+ |
必须包含指定的请求头,且值匹配正则(\d+ 表示数字)。 |
| Cookie | 匹配 Cookie | - Cookie=user_type, vip |
必须包含指定的 Cookie,且值匹配正则。 |
| Query | 匹配查询参数 | - Query=page, \d+ |
必须包含指定的查询参数,且值匹配正则。 |
2. 基于时间的规则
| 断言类型 | 作用 | 简短写法示例 | 说明 |
|---|---|---|---|
| After | 在某个时间之后 | - After=2025-01-01T00:00:00+08:00[Asia/Shanghai] |
用于控制路由的生效时间。 |
| Before | 在某个时间之前 | - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai] |
用于控制路由的失效时间。 |
| Between | 在两个时间之间 | - Between=2025-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai] |
时间区间,常用于定时任务路由。 |
| TimeRange | 指定时间段内 | - TimeRange=2025-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai] |
功能与 Between 类似。 |
3. 流量控制与灰度规则
| 断言类型 | 作用 | 简短写法示例 | 说明 |
|---|---|---|---|
| Weight | 按权重分配流量 | - Weight=group1, 80 |
常用于灰度发布。例如,将 80% 的流量分配给稳定版本,20% 分配给新版本。同一个组(group)内所有路由的权重之和应为 100。 |
| RemoteAddr | 按 IP 地址匹配 | - RemoteAddr=192.168.1.0/24 |
支持 CIDR 格式。可以用于内部网络访问控制。 |
4. 高级匹配规则
| 断言类型 | 作用 | 简短写法示例 | 说明 |
|---|---|---|---|
| Host | 按主机名匹配 | - Host=**.example.com, **.example.org |
常用于多租户场景,根据域名路由。 |
| XForwardedRemoteAddr | 按代理后的真实 IP 匹配 | - XForwardedRemoteAddr=10.0.0.0/8 |
当网关前面还有代理(如 Nginx)时,使用此断言获取真实 IP。 |
三、过滤器
📊 过滤器全景概览
| 分类 | 作用范围 | 配置方式 | 典型场景 |
|---|---|---|---|
| GatewayFilter | 特定路由 | routes[].filters |
路径前缀剥离、添加请求头、重写路径 |
| default-filters | 所有路由(配置方式) | spring.cloud.gateway.default-filters |
统一添加响应头、全局跨域处理 |
| GlobalFilter | 所有路由(代码方式) | 实现 GlobalFilter 接口 |
统一鉴权、全局日志、分布式限流 |
| 下面我分别展开讲这三类,并给出典型代码和配置示例。 |
🔧 1. GatewayFilter:路由级别的过滤器
这是配置在具体路由下的过滤器,只对匹配该路由的请求生效。
配置示例:
yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1 # 去掉第一层路径前缀
- AddRequestHeader=X-Request-From, Gateway # 添加请求头
- AddResponseHeader=X-Response-Time, 2025 # 添加响应头
- SetResponseStatus=200 # 强制设置响应状态码
- RequestRateLimiter= # 限流(需要配合 Redis)
key-resolver: '#{@userKeyResolver}'
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
常用内置 GatewayFilter 速查表:
| 过滤器名称 | 作用 | 示例 |
|---|---|---|
StripPrefix |
去掉路径中的前 N 段 | StripPrefix=1:/user/info → /info |
AddRequestHeader |
添加请求头 | AddRequestHeader=X-User, admin |
AddResponseHeader |
添加响应头 | AddResponseHeader=X-Version, 1.0 |
RemoveRequestHeader |
删除请求头 | RemoveRequestHeader=Cookie |
SetPath |
重写路径 | SetPath=/new/{segment} |
RewritePath |
用正则重写路径 | RewritePath=/user/(?<seg>.*), /$\{seg} |
RequestRateLimiter |
限流 | 见上面示例 |
Retry |
请求失败时重试 | Retry=3, statuses=500 |
SetResponseStatus |
强制设置响应状态码 | SetResponseStatus=200 |
注意 :
RequestRateLimiter需要配合 Redis 以及自定义的KeyResolver(用于区分不同用户/IP)才能生效。
📦 2. default-filters:默认过滤器
配置在 default-filters 下的过滤器,会对所有路由生效,相当于用配置方式定义的"全局过滤器",适用于一些通用场景。
配置示例:
yaml
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default, true
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
- AddRequestHeader=X-Request-Id, ${random.value}
典型用途:
| 用途 | 配置 |
|---|---|
| 统一添加响应头 | AddResponseHeader=X-App-Name, gateway |
| 去重跨域响应头(避免浏览器报错) | DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE |
| 生成全链路追踪 ID | AddRequestHeader=X-Trace-Id, ${random.value} |
🧬 3. GlobalFilter:全局过滤器(代码方式)
这是最灵活、最强大的方式。通过实现 GlobalFilter 接口,你可以编写 Java 代码来处理所有经过网关的请求,适合实现复杂的业务逻辑。
核心接口:
java
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
实战示例:统一 JWT 鉴权
下面的代码实现了一个简单的全局鉴权过滤器,除了白名单路径外,所有请求都必须携带有效的 Token。
java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
// 白名单路径(从配置文件注入)
@Value("${sa-token.exclude-auth-paths:}")
private List<String> excludePaths;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 1. 白名单放行
if (excludePaths.stream().anyMatch(path::startsWith)) {
return chain.filter(exchange);
}
// 2. 获取 Token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 3. 校验 Token(调用 Sa-Token 或自定义逻辑)
String jwt = token.substring(7);
if (!validateToken(jwt)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 4. 放行
return chain.filter(exchange);
}
private boolean validateToken(String token) {
// 调用 Sa-Token 或 JWT 工具类校验
return StpUtil.getLoginIdByToken(token) != null;
}
@Override
public int getOrder() {
return -100; // 数字越小,优先级越高,执行越靠前
}
}
实战示例:记录请求耗时(Pre + Post 逻辑)
java
@Component
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.currentTimeMillis();
// 前置逻辑:记录开始
log.info("请求开始:{} {}", exchange.getRequest().getMethod(), exchange.getRequest().getPath());
return chain.filter(exchange).then(
// 后置逻辑:记录耗时
Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
log.info("请求完成,耗时:{}ms", duration);
})
);
}
@Override
public int getOrder() {
return -1;
}
}