Spring Cloud微服务课程设计 第六章:Spring Cloud Gateway网关
·
第六章:Spring Cloud Gateway网关
理论讲解
什么是API网关?
API网关就像是公司的前台接待处,所有外部请求都要先经过网关,由网关负责路由到相应的部门(微服务)。
网关可以统一处理认证、限流、监控、安全等跨切面关注点。
Spring Cloud Gateway的特点:
- 基于Spring WebFlux,非阻塞异步模型,性能高
- 支持动态路由、限流、熔断、重试等功能
- 使用谓词(Predicate)和过滤器(Filter)实现灵活的路由规则
应用场景举例:
想象一个购物中心:
- 顾客从大门进入(统一入口)
- 根据指示牌(路由规则)找到不同的店铺(微服务)
- 大门保安检查会员卡(身份认证)
- 人流高峰期限制进入人数(限流)
- 临时关闭某个区域(熔断)
项目结构
chapter-06-gateway/
├── eureka-server/ # 注册中心
├── user-service/ # 用户服务
├── product-service/ # 商品服务
├── order-service/ # 订单服务
├── payment-service/ # 支付服务
└── api-gateway/ # API网关(新增)
完整代码实现
1. API Gateway网关服务
pom.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-course</artifactId>
<groupId>com.lihaozhe</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
应用启动类
package com.lihaozhe.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* API网关应用启动类
* 使用Spring Cloud Gateway作为API网关,统一入口
* 网关作为所有外部请求的统一入口,负责路由、过滤、安全等
*
* @author 李昊哲
* @version 1.0.0
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
网关配置类
package com.lihaozhe.apigateway.config;
import com.lihaozhe.apigateway.filter.AuthFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
* 网关路由配置类
* 配置路由规则、过滤器、谓词等
* <p>
* 网关核心概念:
* - Route: 路由信息,包含ID、目标URI、谓词集合、过滤器集合
* - Predicate: 谓词,用于匹配请求的条件
* - Filter: 过滤器,用于修改请求和响应
*/
@Configuration
public class GatewayConfig {
/**
* 自定义路由规则
* 可以配置基于路径、头部、参数、时间等的路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
// 构建路由规则
return builder.routes()
// 用户服务路由 - 路径匹配方式
.route("user-service-route", r -> r.path("/api/users/**")
// 添加过滤器链
.filters(f -> f
// 去掉路径的第一部分(/api)
.stripPrefix(1)
// 添加请求头 X-Request-Source,值为 gateway
.addRequestHeader("X-Request-Source", "gateway")
// 添加请求头 X-Request-Time,值为当前时间戳
.addRequestHeader("X-Request-Time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
// 添加响应头 X-Response-Time,值为当前时间戳
.addResponseHeader("X-Response-Time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))))
// 设置目标服务URI,lb://表示使用负载均衡
.uri("lb://user-service"))
// 商品服务路由 - 路径匹配 + 方法限制
.route("product-service-route", r -> r.path("/api/products/**")
// 添加方法限制谓词,只允许GET、POST、PUT方法
.and().method(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT)
// 添加过滤器链
.filters(f -> f
// 去掉路径的第一部分(/api)
.stripPrefix(1)
// 添加请求头 X-Request-Source,值为 gateway
.addRequestHeader("X-Request-Source", "gateway")
// 配置限流过滤器
.requestRateLimiter(config -> config
// 设置限流器
.setRateLimiter(redisRateLimiter())
// 设置限流键解析器
.setKeyResolver(apiKeyResolver()))
// 配置重试机制
.retry(config -> config
// 设置重试次数为3次
.setRetries(3)
// 设置只对GET方法进行重试
.setMethods(HttpMethod.GET)))
// 设置目标服务URI
.uri("lb://product-service"))
// 订单服务路由 - 路径匹配 + 头部验证
.route("order-service-route", r -> r.path("/api/orders/**")
// 添加头部验证谓词,要求请求头中包含 X-Require-Auth: true
.and().header("X-Require-Auth", "true")
// 添加过滤器链
.filters(f -> f
// 去掉路径的第一部分(/api)
.stripPrefix(1)
// 添加请求头 X-Request-Source,值为 gateway
.addRequestHeader("X-Request-Source", "gateway")
// 添加自定义认证过滤器
.filter(new AuthFilter().apply(new AuthFilter.Config())))
// 设置目标服务URI
.uri("lb://order-service"))
// 支付服务路由 - 路径匹配 + 时间限制(工作时间)
.route("payment-service-route", r -> r.path("/api/payments/**")
// 添加谓词,限制只有在工作时间(9:00-18:00)才能访问
.and().predicate(serverWebExchange -> {
// 获取当前小时数(东八区)
int hour = LocalDateTime.now(ZoneId.of("Asia/Shanghai")).getHour();
// 判断是否在工作时间内
return hour >= 9 && hour < 18;
})
// 添加过滤器链
.filters(f -> f
// 去掉路径的第一部分(/api)
.stripPrefix(1)
// 添加请求头 X-Request-Source,值为 gateway
.addRequestHeader("X-Request-Source", "gateway"))
// 设置目标服务URI
.uri("lb://payment-service"))
// 静态资源路由
.route("static-resource-route", r -> r.path("/static/**")
// 添加过滤器链
.filters(f -> f
// 添加响应头 X-Static-Resource,值为 true
.addResponseHeader("X-Static-Resource", "true")
// 设置响应头 Cache-Control,启用缓存
.setResponseHeader("Cache-Control", "public, max-age=3600"))
// 设置静态资源服务器URI
.uri("http://localhost:8088"))
// 默认路由 - 服务发现自动路由
.route("default-route", r -> r.path("/**")
// 设置默认路由到用户服务
.uri("lb://user-service"))
// 构建完成并返回路由定位器
.build();
}
/**
* Redis限流器(需要Redis服务器)
* 实际生产环境需要配置Redis连接
*/
@Bean
public RedisRateLimiter redisRateLimiter() {
// 默认配置:每秒10个请求,桶容量20
return new RedisRateLimiter(10, 20);
}
/**
* 限流键解析器
* 根据不同的策略生成限流key
*/
@Bean
@Primary
public KeyResolver apiKeyResolver() {
// 根据用户IP限流
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
/**
* 用户限流键解析器(按用户ID限流)
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 从请求头或token中提取用户ID
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
// 如果未获取到用户ID,则使用匿名用户
if (userId == null) {
userId = "anonymous";
}
// 返回用户ID作为限流键
return Mono.just(userId);
};
}
/**
* 路径限流键解析器(按路径限流)
*/
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value()
);
}
}
自定义过滤器
认证过滤器
package com.lihaozhe.apigateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
/**
* 自定义认证过滤器
* 验证请求是否具有有效的认证信息
*
* @author 李昊哲
* @version 1.0.0
*/
@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {
public AuthFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 检查请求头中是否包含认证信息
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
// 简单的认证检查(实际项目中应该使用JWT或其他认证机制)
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
// 认证失败,返回401 Unauthorized
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().add("WWW-Authenticate", "Bearer realm=\"api-gateway\"");
return exchange.getResponse().setComplete();
}
// 认证成功,继续执行下一个过滤器
return chain.filter(exchange);
};
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("required");
}
public static class Config {
private boolean required = true;
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
}
@Override
public String name() {
return "Auth";
}
}
日志过滤器
package com.lihaozhe.apigateway.filter;
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.time.Duration;
import java.time.Instant;
/**
* 全局日志过滤器
* 记录所有请求的访问日志和性能指标
* <p>
* 过滤器执行顺序:
* - 数值越小优先级越高
* - 可以通过Ordered接口或@Order注解控制
*
* @author 李昊哲
* @version 1.0.0
*/
@Component
public class LoggingFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求开始前记录时间
exchange.getAttributes().put(START_TIME, Instant.now());
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
String remoteAddress = exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().toString() : "unknown";
System.out.printf(">>> 网关收到请求: %s %s from %s%n", method, path, remoteAddress);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 请求完成后记录日志
Instant startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
long duration = Duration.between(startTime, Instant.now()).toMillis();
int status = exchange.getResponse().getStatusCode() != null ?
exchange.getResponse().getStatusCode().value() : 0;
System.out.printf("<<< 网关处理完成: %s %s - Status: %d - Time: %dms%n", method, path, status, duration);
// 记录到监控系统(这里简单打印)
if (duration > 1000) {
System.out.printf("!!! 慢请求警告: %s %s 耗时 %dms%n", method, path, duration);
}
}
}));
}
@Override
public int getOrder() {
// 高优先级,最先执行
return Ordered.HIGHEST_PRECEDENCE;
}
}
限流过滤器
package com.lihaozhe.apigateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 自定义限流过滤器
* 基于IP或用户进行限流控制
*
* @author 李昊哲
* @version 1.0.0
*/
@Component
public class RateLimitFilter extends AbstractGatewayFilterFactory<RateLimitFilter.Config> {
public RateLimitFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String ip = exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
String path = exchange.getRequest().getPath().value();
// 简单的内存限流实现(生产环境应该使用Redis)
if (isRateLimited(ip, path, config)) {
// 返回限流错误
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().add("X-RateLimit-Limit", String.valueOf(config.getRequestsPerMinute()));
exchange.getResponse().getHeaders().add("X-RateLimit-Retry-After", "60");
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(
"{\"error\": \"Rate limit exceeded\", \"message\": \"Too many requests\"}".getBytes()
))
);
}
return chain.filter(exchange);
};
}
/**
* 简单的内存限流检查
*/
private boolean isRateLimited(String ip, String path, Config config) {
String key = ip + ":" + path;
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - 60000; // 1分钟窗口
// 这里应该使用Redis或分布式缓存
// 简化实现:总是返回false
return false;
}
@Override
public List<String> shortcutFieldOrder() {
return java.util.Arrays.asList("requestsPerMinute", "burstCapacity");
}
public static class Config {
private int requestsPerMinute = 100; // 每分钟请求数
private int burstCapacity = 50; // 突发容量
public int getRequestsPerMinute() {
return requestsPerMinute;
}
public void setRequestsPerMinute(int requestsPerMinute) {
this.requestsPerMinute = requestsPerMinute;
}
public int getBurstCapacity() {
return burstCapacity;
}
public void setBurstCapacity(int burstCapacity) {
this.burstCapacity = burstCapacity;
}
}
}
降级处理控制器
package com.lihaozhe.apigateway.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 网关降级处理控制器
* 当后端服务不可用时返回友好的降级响应
*
* @author 李昊哲
* @version 1.0.0
*/
@RestController
@RequestMapping("/fallback")
public class FallbackController {
/**
* 降级处理
*
* @return 降级响应
*/
@GetMapping("/fallback")
public Map<String, Object> fallback() {
return Map.of(
"code", "503",
"message", "服务暂时不可用,请稍后重试",
"timestamp", System.currentTimeMillis()
);
}
/**
* 服务降级
*
* @return 降级响应
*/
@GetMapping("/service-fallback")
public Map<String, Object> serviceFallback() {
return Map.of(
"code", "503",
"message", "服务暂时不可用,请稍后重试",
"timestamp", System.currentTimeMillis()
);
}
}
网关健康检查控制器
package com.lihaozhe.apigateway.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 网关状态控制器
* 提供网关状态检查和服务信息查询
*
* @author 李昊哲
* @version 1.0.0
*/
@RestController
@RequestMapping("/gateway")
public class GatewayStatusController {
@Autowired
private DiscoveryClient discoveryClient;
/**
* 网关状态信息
* GET /gateway/status
*
* @return 网关状态信息
*/
@GetMapping("/status")
public Map<String, Object> gatewayStatus() {
return Map.of(
"gateway", "UP",
"timestamp", System.currentTimeMillis()
);
}
/**
* 获取注册的服务列表
* GET /gateway/services
*
* @return 服务列表
*/
@GetMapping("/services")
public Map<String, Object> getServices() {
List<String> services = discoveryClient.getServices();
return Map.of(
"totalServices", services.size(),
"services", services
);
}
/**
* 健康检查
* GET /gateway/health
*
* @return 健康状态
*/
@GetMapping("/health")
public Map<String, Object> health() {
return Map.of(
"status", "UP",
"service", "api-gateway",
"timestamp", System.currentTimeMillis()
);
}
}
网关配置文件
server:
port: 8080
spring:
application:
name: api-gateway
main:
allow-bean-definition-overriding: true
cloud:
gateway:
server:
webflux:
discovery:
locator:
enabled: true
lower-case-service-id: true
# 配置受信任代理(Spring Cloud 2025.0.0 新要求)
trusted-proxies: "192\\.168\\..*|10\\..*|127\\.0\\.0\\.1"
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
# 支付服务路由
- id: payment-service
uri: lb://payment-service
predicates:
- Path=/api/payments/**
filters:
- StripPrefix=1
# 全局 CORS 配置
globals:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
# 配置Redis服务器地址和密码
data:
redis:
host: 118.31.221.165
port: 6379
password: lihaozhe
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
shutdown-timeout: 100ms
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
management:
endpoints:
web:
exposure:
include: health,info,metrics,gateway
endpoint:
health:
show-details: always
gateway:
access: unrestricted
logging:
level:
org.springframework.cloud.gateway: DEBUG
com.lihaozhe.apigateway: INFO
reactor.netty.http.client: WARN
org.springframework.data.redis: DEBUG
2. 其他服务配置调整
为了配合网关,我们需要调整其他服务的配置,确保它们可以通过网关正确路由。
用户服务配置调整
# user-service的application.properties
# 添加网关相关的头部信息支持
server.servlet.context-path=/
# 其他配置保持不变...
商品服务配置调整
# product-service的application.properties
# 添加网关相关的头部信息支持
server.servlet.context-path=/
# 其他配置保持不变...
订单服务配置调整
# order-service的application.properties
# 添加网关相关的头部信息支持
server.servlet.context-path=/
# 其他配置保持不变...
支付服务配置调整
# payment-service的application.properties
# 添加网关相关的头部信息支持
server.servlet.context-path=/
# 其他配置保持不变...
开发思路和过程
1. 网关设计原则:
- 单一入口:所有外部请求都通过网关
- 路由转发:根据规则将请求路由到对应服务
- 横切关注点:在网关层处理认证、限流、监控等
- 服务发现:与Eureka集成,动态发现服务实例
2. 网关核心概念:
- 路由 (Route):定义转发规则,包括ID、目标URI、谓词和过滤器
- 谓词 (Predicate):匹配请求的条件,如路径、方法、头部等
- 过滤器 (Filter):处理请求和响应的逻辑,如添加头部、重试、限流等
3. 网关过滤器类型:
- 全局过滤器 (Global Filter):对所有路由生效
- 路由过滤器 (Route Filter):对特定路由生效
4. 常用网关功能:
- 身份认证:验证请求的合法性
- 动态路由:根据规则路由到不同服务
- 请求限流:限制单位时间内的请求数量
- 熔断降级:服务不可用时返回降级结果
- 日志监控:记录请求日志和性能指标
运行测试
启动顺序:
- 启动Eureka Server (端口8761)
- 启动User Service (端口8081)
- 启动Product Service (端口8082)
- 启动Order Service (端口8083)
- 启动Payment Service (端口8086)
- 启动API Gateway (端口8080)
测试步骤:
# 1. 通过网关访问用户服务(无需认证)
curl http://localhost:8080/api/users/health
# 2. 通过网关访问商品服务
curl http://localhost:8080/api/products/1
# 3. 测试认证 - 不带token应该被拒绝
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-H "X-Require-Auth: true" \
-d '{"userId": 1, "productId": 1, "quantity": 1}'
# 4. 测试认证 - 带有效token
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-H "X-Require-Auth: true" \
-H "Authorization: Bearer valid_jwt_token_12345" \
-d '{"userId": 1, "productId": 1, "quantity": 1}'
# 5. 测试支付服务(在工作时间内)
curl http://localhost:8080/api/payments/health
# 6. 查看网关状态信息
curl http://localhost:8080/gateway/status
# 7. 查看网关路由信息
curl http://localhost:8080/gateway/routes
# 8. 测试降级功能(停止某个服务后)
curl http://localhost:8080/api/users/1
# 9. 测试限流(快速连续调用)
for i in {1..15}; do
curl http://localhost:8080/api/products/1 &
done
wait
# 10. 查看网关Actuator端点
curl http://localhost:8080/actuator/gateway/routes
curl http://localhost:8080/actuator/health
观察网关特性:
- 所有请求都通过网关统一入口(端口8080)
- 网关根据路径将请求路由到不同服务
- 认证过滤器会检查请求头中的token
- 日志过滤器记录请求处理时间和状态
- 查看网关控制台日志,了解请求处理流程
这一章我们学习了Spring Cloud Gateway网关的使用,实现了统一的API入口和路由功能。
在下一章中,我们将学习Spring Cloud Config配置中心,实现配置的集中管理。
更多推荐
所有评论(0)