一、核心概念(速记)
- Gateway:Spring 官方异步非阻塞网关,性能远超 Zuul,SpringCloud Alibaba 标配网关
- 三大核心 :路由 (Route) + 断言 (Predicate) + 过滤器 (Filter)
- 关键禁忌 :不能引入 spring-boot-starter-web(会与 WebFlux 冲突)
- 作用:统一入口、负载均衡、权限校验、日志、限流、熔断、请求 / 响应增强
二、搭建 Gateway 工程(完整代码)
1. pom.xml(必选依赖)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_parent</artifactId>
<groupId>com.hg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api_gateway</artifactId>
<dependencies>
<!-- Nacos 注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- ↓↓↓ 绝对不能加 ↓↓↓ -->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!--</dependency>-->
</dependencies>
</project>
2. application.yml(基础配置)
yaml
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.209.129:8848
server:
port: 9527
3. 启动类
java
运行
package com.hg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class, args);
}
}
三、路由配置(两种方式)
方式 1:固定 IP + 端口路由
yaml
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer
uri: http://localhost:80 # 固定地址
predicates:
- Path=/consumer/**
方式 2:服务名 + 负载均衡路由(推荐)
yaml
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer
uri: lb://sentinel-consumer # lb = 负载均衡
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1 # 去掉第一层路径
四、断言 Predicate(完整代码)
1. 常用内置断言
yaml
predicates:
- Path=/consumer/** # 路径匹配
- Method=GET # 请求方式
- After=2025-01-01T00:00:00+08:00[Asia/Shanghai]
- RemoteAddr=192.168.1.0/24 # IP 段
- Header=X-Id, \d+ # 请求头
- Query=age, \d+ # 请求参数
2. 自定义断言工厂(年龄判断)
java
运行
package com.hg.predicate;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge", "maxAge");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String ageStr = exchange.getRequest().getQueryParams().getFirst("age");
if (StringUtils.isNotEmpty(ageStr)) {
int age = Integer.parseInt(ageStr);
return age > config.getMinAge() && age < config.getMaxAge();
}
return false;
};
}
@Data
@NoArgsConstructor
public static class Config {
private int minAge;
private int maxAge;
}
}
使用配置
yaml
predicates:
- Path=/consumer/**
- Age=18,60
五、过滤器 Filter(全套代码)
1. 自定义局部过滤器(接口耗时统计)
java
运行
package com.hg.filter;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (!config.isParamValue()) {
return chain.filter(exchange);
}
long start = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long time = System.currentTimeMillis() - start;
System.out.println("接口耗时:" + time + "ms");
}));
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("paramValue");
}
@Data
@NoArgsConstructor
public static class Config {
private boolean paramValue;
}
}
使用配置
yaml
filters:
- Log=true
2. 自定义全局过滤器(登录 Token 校验)
java
运行
package com.hg.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getQueryParams().getFirst("token");
if (token == null || token.isBlank()) {
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> map = new HashMap<>();
map.put("code", 401);
map.put("msg", "未登录,请先携带token访问");
byte[] bytes = new byte[0];
try {
bytes = new ObjectMapper().writeValueAsString(map).getBytes(StandardCharsets.UTF_8);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
DataBuffer buffer = response.bufferFactory().wrap(bytes);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
六、Sentinel 网关限流(完整代码)
1. pom.xml 加入依赖
xml
<!-- Sentinel 核心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Gateway + Sentinel 整合 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
2. application.yml
yaml
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
3. 自定义限流返回结果
java
运行
package com.hg.exception;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class GatewaySentinelConfig {
@PostConstruct
public void init() {
BlockRequestHandler handler = (exchange, t) -> {
Map<String, Object> map = new HashMap<>();
map.put("code", 200);
map.put("msg", "请求过于频繁,请稍后再试!");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
};
GatewayCallbackManager.setBlockHandler(handler);
}
}
七、全套测试地址
- 正常访问:
http://localhost:9527/consumer/getUserById/1?token=123 - 年龄断言:
http://localhost:9527/consumer/getUserById/1?token=123&age=20 - 未登录拦截:
http://localhost:9527/consumer/getUserById/1 - 限流触发:快速刷新即可看到 Sentinel 自定义返回