4.1 API网关概述
什么是API网关
API网关是微服务架构中的重要组件,作为系统的统一入口,负责请求路由、协议转换、安全认证、限流熔断等功能。
API网关的作用
┌─────────────────────────────────────────────────────────────────┐
│ API网关架构图 │
├─────────────────────────────────────────────────────────────────┤
│ 客户端 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Web Browser │ │ Mobile App │ │ Third Party │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ 路由管理 │ │ 安全认证 │ │ 限流熔断 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ 协议转换 │ │ 负载均衡 │ │ 监控日志 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │
│ └─────────────────────┬───────────────────────────────────┘ │
├────────────────────────┼───────────────────────────────────────┤
│ │ 内部网络 │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ User Service │ │ Product Service │ │ Order Service │ │
│ │ 192.168.1.10 │ │ 192.168.1.20 │ │ 192.168.1.30 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
核心功能
- 请求路由:根据请求路径将请求转发到对应的后端服务
- 负载均衡:在多个服务实例之间分发请求
- 安全认证:统一的身份验证和授权
- 限流熔断:保护后端服务免受过载
- 协议转换:支持HTTP、WebSocket等多种协议
- 监控日志:统一的请求日志和监控指标
- 缓存:减少对后端服务的请求
- 跨域处理:统一处理CORS问题
4.2 Spring Cloud Gateway
Gateway简介
Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,基于Spring 5、Spring Boot 2和Project Reactor构建,提供了简单而有效的方式来路由API请求。
核心概念
- Route(路由):网关的基本构建块,包含ID、目标URI、断言集合和过滤器集合
- Predicate(断言):匹配HTTP请求的条件
- Filter(过滤器):对请求和响应进行修改
项目搭建
1. 创建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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>api-gateway</artifactId>
<version>1.0.0</version>
<name>api-gateway</name>
<description>API Gateway Service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Redis for Rate Limiting -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 启动类
ApiGatewayApplication.java
package com.example.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* API网关启动类
*/
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
4.3 路由配置
基础路由配置
application.yml
server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
# 全局CORS配置
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed-headers: "*"
allow-credentials: true
max-age: 3600
# 路由配置
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 20
redis-rate-limiter.burstCapacity: 40
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@ipKeyResolver}"
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- AuthenticationFilter
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5
redis-rate-limiter.burstCapacity: 10
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"
# 认证服务路由
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
# WebSocket路由
- id: websocket-service
uri: lb:ws://websocket-service
predicates:
- Path=/ws/**
# 静态资源路由
- id: static-resources
uri: http://static.example.com
predicates:
- Path=/static/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: static-fallback
fallbackUri: forward:/fallback/static
# 默认过滤器
default-filters:
- AddRequestHeader=X-Gateway-Name, api-gateway
- AddRequestHeader=X-Request-Time, #{T(java.time.Instant).now().toString()}
- AddResponseHeader=X-Response-Time, #{T(java.time.Instant).now().toString()}
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
# Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}
# 日志配置
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.web.reactive: DEBUG
reactor.netty: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# 监控配置
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
gateway:
enabled: true
动态路由配置
1. 路由配置类
GatewayConfig.java
package com.example.apigateway.config;
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.http.HttpMethod;
import java.time.Duration;
/**
* 网关路由配置
*/
@Configuration
public class GatewayConfig {
/**
* 编程式路由配置
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service-route", r -> r
.path("/api/users/**")
.and()
.method(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)
.uri("lb://user-service")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Service", "user-service")
.addResponseHeader("X-Response-Service", "user-service")
.retry(config -> config
.setRetries(3)
.setStatuses(org.springframework.http.HttpStatus.BAD_GATEWAY)
.setBackoff(Duration.ofMillis(100), Duration.ofMillis(1000), 2, false)
)
)
)
// 商品服务路由 - 支持版本控制
.route("product-service-v1", r -> r
.path("/api/v1/products/**")
.uri("lb://product-service-v1")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-API-Version", "v1")
)
)
.route("product-service-v2", r -> r
.path("/api/v2/products/**")
.uri("lb://product-service-v2")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-API-Version", "v2")
)
)
// 基于请求头的路由
.route("mobile-api", r -> r
.path("/api/mobile/**")
.and()
.header("User-Agent", ".*Mobile.*")
.uri("lb://mobile-service")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Client-Type", "mobile")
)
)
// 基于查询参数的路由
.route("beta-features", r -> r
.path("/api/**")
.and()
.query("beta", "true")
.uri("lb://beta-service")
.filters(f -> f
.addRequestHeader("X-Beta-User", "true")
)
)
// 基于时间的路由(限时功能)
.route("time-based-route", r -> r
.path("/api/promotion/**")
.and()
.between(
java.time.ZonedDateTime.now(),
java.time.ZonedDateTime.now().plusDays(7)
)
.uri("lb://promotion-service")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Promotion-Active", "true")
)
)
// 权重路由(灰度发布)
.route("weight-high", r -> r
.path("/api/test/**")
.and()
.weight("group1", 8)
.uri("lb://test-service-stable")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Version", "stable")
)
)
.route("weight-low", r -> r
.path("/api/test/**")
.and()
.weight("group1", 2)
.uri("lb://test-service-canary")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Version", "canary")
)
)
.build();
}
}
2. 自定义断言工厂
CustomRoutePredicateFactory.java
package com.example.apigateway.predicate;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
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
@Slf4j
public class UserRoleRoutePredicateFactory
extends AbstractRoutePredicateFactory<UserRoleRoutePredicateFactory.Config> {
public UserRoleRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("roles");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 从请求头中获取用户角色
String userRoles = exchange.getRequest().getHeaders().getFirst("X-User-Roles");
if (userRoles == null) {
log.debug("No user roles found in request headers");
return false;
}
// 检查用户是否具有所需角色
List<String> userRoleList = Arrays.asList(userRoles.split(","));
boolean hasRole = config.getRoles().stream()
.anyMatch(userRoleList::contains);
log.debug("User roles: {}, Required roles: {}, Has access: {}",
userRoles, config.getRoles(), hasRole);
return hasRole;
};
}
@Data
public static class Config {
private List<String> roles;
}
}
4.4 过滤器
全局过滤器
1. 认证过滤器
AuthenticationGlobalFilter.java
package com.example.apigateway.filter;
import com.example.apigateway.util.JwtUtil;
import lombok.RequiredArgsConstructor;
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.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* 全局认证过滤器
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class AuthenticationGlobalFilter implements GlobalFilter, Ordered {
private final JwtUtil jwtUtil;
// 不需要认证的路径
private static final List<String> SKIP_AUTH_PATHS = Arrays.asList(
"/api/auth/login",
"/api/auth/register",
"/api/auth/refresh",
"/api/public",
"/actuator",
"/swagger",
"/v3/api-docs"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
log.debug("Processing request: {} {}", request.getMethod(), path);
// 跳过不需要认证的路径
if (shouldSkipAuth(path)) {
log.debug("Skipping authentication for path: {}", path);
return chain.filter(exchange);
}
// 获取Authorization头
String authHeader = request.getHeaders().getFirst("Authorization");
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
log.warn("Missing or invalid Authorization header for path: {}", path);
return handleUnauthorized(exchange);
}
// 提取JWT token
String token = authHeader.substring(7);
try {
// 验证token
if (!jwtUtil.validateToken(token)) {
log.warn("Invalid JWT token for path: {}", path);
return handleUnauthorized(exchange);
}
// 提取用户信息
String userId = jwtUtil.getUserIdFromToken(token);
String username = jwtUtil.getUsernameFromToken(token);
List<String> roles = jwtUtil.getRolesFromToken(token);
// 添加用户信息到请求头
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-ID", userId)
.header("X-Username", username)
.header("X-User-Roles", String.join(",", roles))
.build();
log.debug("Authentication successful for user: {} ({})", username, userId);
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (Exception e) {
log.error("Authentication error for path: {}", path, e);
return handleUnauthorized(exchange);
}
}
private boolean shouldSkipAuth(String path) {
return SKIP_AUTH_PATHS.stream().anyMatch(path::startsWith);
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json");
String body = "{\"error\":\"Unauthorized\",\"message\":\"Authentication required\"}";
return response.writeWith(
Mono.just(response.bufferFactory().wrap(body.getBytes()))
);
}
@Override
public int getOrder() {
return -100; // 高优先级,在其他过滤器之前执行
}
}
2. 日志过滤器
LoggingGlobalFilter.java
package com.example.apigateway.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.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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* 全局日志过滤器
*/
@Component
@Slf4j
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
private static final String REQUEST_ID_HEADER = "X-Request-ID";
private static final String START_TIME_ATTR = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 生成请求ID
String requestId = request.getHeaders().getFirst(REQUEST_ID_HEADER);
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
// 记录开始时间
long startTime = System.currentTimeMillis();
exchange.getAttributes().put(START_TIME_ATTR, startTime);
// 添加请求ID到请求头
ServerHttpRequest modifiedRequest = request.mutate()
.header(REQUEST_ID_HEADER, requestId)
.build();
// 记录请求信息
log.info("[{}] Request: {} {} from {}",
requestId,
request.getMethod(),
request.getURI(),
getClientIp(request));
return chain.filter(exchange.mutate().request(modifiedRequest).build())
.then(Mono.fromRunnable(() -> {
// 记录响应信息
ServerHttpResponse response = exchange.getResponse();
Long startTimeAttr = exchange.getAttribute(START_TIME_ATTR);
long duration = startTimeAttr != null ?
System.currentTimeMillis() - startTimeAttr : 0;
log.info("[{}] Response: {} {} in {}ms",
requestId,
response.getStatusCode(),
request.getURI().getPath(),
duration);
// 添加响应头
response.getHeaders().add(REQUEST_ID_HEADER, requestId);
response.getHeaders().add("X-Response-Time", String.valueOf(duration));
}));
}
private String getClientIp(ServerHttpRequest request) {
String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeaders().getFirst("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
@Override
public int getOrder() {
return -200; // 最高优先级
}
}
自定义过滤器
1. 限流过滤器
RateLimitGatewayFilterFactory.java
package com.example.apigateway.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
/**
* 自定义限流过滤器
*/
@Component
@Slf4j
public class RateLimitGatewayFilterFactory
extends AbstractGatewayFilterFactory<RateLimitGatewayFilterFactory.Config> {
private final ReactiveRedisTemplate<String, String> redisTemplate;
private final RedisScript<List> rateLimitScript;
public RateLimitGatewayFilterFactory(ReactiveRedisTemplate<String, String> redisTemplate) {
super(Config.class);
this.redisTemplate = redisTemplate;
this.rateLimitScript = createRateLimitScript();
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("capacity", "refillTokens", "refillPeriod");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String key = getKey(exchange, config);
return redisTemplate.execute(rateLimitScript,
Arrays.asList(key),
String.valueOf(config.getCapacity()),
String.valueOf(config.getRefillTokens()),
String.valueOf(config.getRefillPeriod().getSeconds()))
.cast(List.class)
.flatMap(results -> {
boolean allowed = (Boolean) results.get(0);
long tokensLeft = (Long) results.get(1);
if (allowed) {
log.debug("Rate limit passed for key: {}, tokens left: {}", key, tokensLeft);
return chain.filter(exchange);
} else {
log.warn("Rate limit exceeded for key: {}", key);
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
});
};
}
private String getKey(org.springframework.web.server.ServerWebExchange exchange, Config config) {
// 根据配置的键类型生成限流键
switch (config.getKeyType()) {
case IP:
return "rate_limit:ip:" + getClientIp(exchange.getRequest());
case USER:
String userId = exchange.getRequest().getHeaders().getFirst("X-User-ID");
return "rate_limit:user:" + (userId != null ? userId : "anonymous");
case PATH:
return "rate_limit:path:" + exchange.getRequest().getURI().getPath();
default:
return "rate_limit:global";
}
}
private String getClientIp(org.springframework.http.server.reactive.ServerHttpRequest request) {
String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
private RedisScript<List> createRateLimitScript() {
String script =
"local key = KEYS[1]\n" +
"local capacity = tonumber(ARGV[1])\n" +
"local refill_tokens = tonumber(ARGV[2])\n" +
"local refill_period = tonumber(ARGV[3])\n" +
"local current_time = redis.call('TIME')\n" +
"local current_timestamp = current_time[1]\n" +
"\n" +
"local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')\n" +
"local tokens = tonumber(bucket[1]) or capacity\n" +
"local last_refill = tonumber(bucket[2]) or current_timestamp\n" +
"\n" +
"local time_passed = current_timestamp - last_refill\n" +
"local new_tokens = math.min(capacity, tokens + (time_passed / refill_period) * refill_tokens)\n" +
"\n" +
"local allowed = new_tokens >= 1\n" +
"if allowed then\n" +
" new_tokens = new_tokens - 1\n" +
"end\n" +
"\n" +
"redis.call('HMSET', key, 'tokens', new_tokens, 'last_refill', current_timestamp)\n" +
"redis.call('EXPIRE', key, refill_period * 2)\n" +
"\n" +
"return {allowed, new_tokens}";
return RedisScript.of(script, List.class);
}
@Data
public static class Config {
private int capacity = 10; // 桶容量
private int refillTokens = 1; // 每次补充的令牌数
private Duration refillPeriod = Duration.ofSeconds(1); // 补充周期
private KeyType keyType = KeyType.IP; // 限流键类型
public enum KeyType {
IP, USER, PATH, GLOBAL
}
}
}
2. 缓存过滤器
CacheGatewayFilterFactory.java
package com.example.apigateway.filter;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
/**
* 缓存过滤器
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class CacheGatewayFilterFactory
extends AbstractGatewayFilterFactory<CacheGatewayFilterFactory.Config> {
private final ReactiveRedisTemplate<String, String> redisTemplate;
public CacheGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("ttl", "keyPrefix");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 只缓存GET请求
if (!HttpMethod.GET.equals(exchange.getRequest().getMethod())) {
return chain.filter(exchange);
}
String cacheKey = generateCacheKey(exchange, config);
// 尝试从缓存获取
return redisTemplate.opsForValue().get(cacheKey)
.flatMap(cachedResponse -> {
log.debug("Cache hit for key: {}", cacheKey);
// 返回缓存的响应
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", "application/json");
response.getHeaders().add("X-Cache", "HIT");
DataBuffer buffer = response.bufferFactory().wrap(cachedResponse.getBytes());
return response.writeWith(Mono.just(buffer));
})
.switchIfEmpty(
// 缓存未命中,继续处理请求并缓存响应
chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.debug("Cache miss for key: {}", cacheKey);
// 这里简化处理,实际应该拦截响应体进行缓存
// 由于Spring Cloud Gateway的响应式特性,缓存响应体比较复杂
// 可以考虑使用其他方案如在业务服务中实现缓存
exchange.getResponse().getHeaders().add("X-Cache", "MISS");
}))
);
};
}
private String generateCacheKey(org.springframework.web.server.ServerWebExchange exchange, Config config) {
String path = exchange.getRequest().getURI().getPath();
String query = exchange.getRequest().getURI().getQuery();
StringBuilder keyBuilder = new StringBuilder(config.getKeyPrefix())
.append(":")
.append(path);
if (query != null && !query.isEmpty()) {
keyBuilder.append("?").append(query);
}
return keyBuilder.toString();
}
@Data
public static class Config {
private Duration ttl = Duration.ofMinutes(5); // 缓存TTL
private String keyPrefix = "gateway:cache"; // 缓存键前缀
}
}
4.5 限流与熔断
Redis限流配置
1. 限流键解析器
KeyResolverConfig.java
package com.example.apigateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
/**
* 限流键解析器配置
*/
@Configuration
public class KeyResolverConfig {
/**
* 基于IP的限流
*/
@Bean
@Primary
public KeyResolver ipKeyResolver() {
return exchange -> {
String xForwardedFor = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
String clientIp;
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
clientIp = xForwardedFor.split(",")[0].trim();
} else {
clientIp = exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
return Mono.just(clientIp);
};
}
/**
* 基于用户的限流
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-ID");
return Mono.just(userId != null ? userId : "anonymous");
};
}
/**
* 基于路径的限流
*/
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
/**
* 基于API Key的限流
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> {
String apiKey = exchange.getRequest().getHeaders().getFirst("X-API-Key");
return Mono.just(apiKey != null ? apiKey : "no-api-key");
};
}
/**
* 组合限流键
*/
@Bean
public KeyResolver compositeKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-ID");
String path = exchange.getRequest().getURI().getPath();
String key = (userId != null ? userId : "anonymous") + ":" + path;
return Mono.just(key);
};
}
}
熔断器配置
1. Resilience4j配置
CircuitBreakerConfig.java
package com.example.apigateway.config;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
/**
* 熔断器配置
*/
@Configuration
public class CircuitBreakerConfig {
/**
* 默认熔断器配置
*/
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
.slidingWindowSize(10) // 滑动窗口大小
.slidingWindowType(SlidingWindowType.COUNT_BASED) // 基于计数的滑动窗口
.minimumNumberOfCalls(5) // 最小调用次数
.failureRateThreshold(50) // 失败率阈值50%
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断器打开状态等待时间
.permittedNumberOfCallsInHalfOpenState(3) // 半开状态允许的调用次数
.automaticTransitionFromOpenToHalfOpenEnabled(true) // 自动从打开状态转换到半开状态
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(10)) // 超时时间
.build())
.build());
}
/**
* 用户服务熔断器配置
*/
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> userServiceCustomizer() {
return factory -> factory.configure(builder -> builder
.circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
.slidingWindowSize(20)
.minimumNumberOfCalls(10)
.failureRateThreshold(60)
.waitDurationInOpenState(Duration.ofSeconds(60))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(5))
.build()), "user-service");
}
/**
* 订单服务熔断器配置
*/
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> orderServiceCustomizer() {
return factory -> factory.configure(builder -> builder
.circuitBreakerConfig(io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.custom()
.slidingWindowSize(15)
.minimumNumberOfCalls(8)
.failureRateThreshold(40)
.waitDurationInOpenState(Duration.ofSeconds(45))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(8))
.build()), "order-service");
}
}
2. 降级处理
FallbackController.java
package com.example.apigateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* 降级处理控制器
*/
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
/**
* 用户服务降级
*/
@GetMapping("/user-service")
public ResponseEntity<Map<String, Object>> userServiceFallback() {
log.warn("User service fallback triggered");
Map<String, Object> response = new HashMap<>();
response.put("error", "User service is temporarily unavailable");
response.put("message", "Please try again later");
response.put("timestamp", LocalDateTime.now());
response.put("service", "user-service");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
/**
* 商品服务降级
*/
@GetMapping("/product-service")
public ResponseEntity<Map<String, Object>> productServiceFallback() {
log.warn("Product service fallback triggered");
Map<String, Object> response = new HashMap<>();
response.put("error", "Product service is temporarily unavailable");
response.put("message", "Please try again later");
response.put("timestamp", LocalDateTime.now());
response.put("service", "product-service");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
/**
* 订单服务降级
*/
@GetMapping("/order-service")
public ResponseEntity<Map<String, Object>> orderServiceFallback() {
log.warn("Order service fallback triggered");
Map<String, Object> response = new HashMap<>();
response.put("error", "Order service is temporarily unavailable");
response.put("message", "Your order will be processed when service is restored");
response.put("timestamp", LocalDateTime.now());
response.put("service", "order-service");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
/**
* 默认降级
*/
@GetMapping("/default")
public ResponseEntity<Map<String, Object>> defaultFallback() {
log.warn("Default fallback triggered");
Map<String, Object> response = new HashMap<>();
response.put("error", "Service temporarily unavailable");
response.put("message", "Please try again later");
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
/**
* 静态资源降级
*/
@GetMapping("/static")
public ResponseEntity<Map<String, Object>> staticFallback() {
log.warn("Static resource fallback triggered");
Map<String, Object> response = new HashMap<>();
response.put("error", "Static resource service unavailable");
response.put("message", "Using cached version");
response.put("timestamp", LocalDateTime.now());
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).body(response);
}
}
4.6 安全配置
JWT工具类
JwtUtil.java
package com.example.apigateway.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.List;
/**
* JWT工具类
*/
@Component
@Slf4j
public class JwtUtil {
@Value("${jwt.secret:mySecretKey123456789012345678901234567890}")
private String secret;
@Value("${jwt.expiration:86400}")
private Long expiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
/**
* 验证token
*/
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.error("Invalid JWT token: {}", e.getMessage());
return false;
}
}
/**
* 从token中获取用户ID
*/
public String getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.get("userId", String.class);
}
/**
* 从token中获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getSubject();
}
/**
* 从token中获取角色列表
*/
@SuppressWarnings("unchecked")
public List<String> getRolesFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.get("roles", List.class);
}
/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 检查token是否过期
*/
public boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Claims getClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
}
安全配置
SecurityConfig.java
package com.example.apigateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* 安全配置
*/
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
.and()
.authorizeExchange()
.pathMatchers("/actuator/**", "/fallback/**").permitAll()
.anyExchange().permitAll() // 由自定义过滤器处理认证
.and()
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
4.7 监控与管理
健康检查
GatewayHealthIndicator.java
package com.example.apigateway.health;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuator.health.Health;
import org.springframework.boot.actuator.health.ReactiveHealthIndicator;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 网关健康检查
*/
@Component
@RequiredArgsConstructor
public class GatewayHealthIndicator implements ReactiveHealthIndicator {
private final RouteLocator routeLocator;
private final ReactiveRedisTemplate<String, String> redisTemplate;
@Override
public Mono<Health> health() {
return checkRoutes()
.zipWith(checkRedis())
.map(tuple -> {
boolean routesHealthy = tuple.getT1();
boolean redisHealthy = tuple.getT2();
if (routesHealthy && redisHealthy) {
return Health.up()
.withDetail("routes", "All routes are configured")
.withDetail("redis", "Redis connection is healthy")
.build();
} else {
return Health.down()
.withDetail("routes", routesHealthy ? "OK" : "Some routes are not configured")
.withDetail("redis", redisHealthy ? "OK" : "Redis connection failed")
.build();
}
})
.onErrorReturn(Health.down().withDetail("error", "Health check failed").build());
}
private Mono<Boolean> checkRoutes() {
return routeLocator.getRoutes()
.collectList()
.map(routes -> !routes.isEmpty())
.timeout(Duration.ofSeconds(5))
.onErrorReturn(false);
}
private Mono<Boolean> checkRedis() {
return redisTemplate.opsForValue()
.set("health:check", "ok", Duration.ofSeconds(10))
.timeout(Duration.ofSeconds(3))
.map(result -> true)
.onErrorReturn(false);
}
}
监控指标
GatewayMetricsConfig.java
package com.example.apigateway.config;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
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;
/**
* 网关监控指标配置
*/
@Component
@RequiredArgsConstructor
public class GatewayMetricsFilter implements GlobalFilter, Ordered {
private final MeterRegistry meterRegistry;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
String method = exchange.getRequest().getMethod().name();
Timer.Sample sample = Timer.start(meterRegistry);
return chain.filter(exchange)
.doFinally(signalType -> {
String status = exchange.getResponse().getStatusCode() != null ?
exchange.getResponse().getStatusCode().toString() : "unknown";
sample.stop(Timer.builder("gateway.requests")
.tag("method", method)
.tag("path", path)
.tag("status", status)
.register(meterRegistry));
// 记录请求计数
meterRegistry.counter("gateway.requests.total",
"method", method,
"path", path,
"status", status)
.increment();
});
}
@Override
public int getOrder() {
return -1;
}
}
4.8 总结
核心概念回顾
API网关作用:
- 统一入口:所有外部请求的单一入口点
- 路由转发:根据规则将请求转发到后端服务
- 安全认证:统一的身份验证和授权
- 限流熔断:保护后端服务免受过载
- 监控日志:统一的请求监控和日志记录
Spring Cloud Gateway核心组件:
- Route(路由):定义请求如何转发到后端服务
- Predicate(断言):匹配HTTP请求的条件
- Filter(过滤器):对请求和响应进行处理
过滤器类型:
- 全局过滤器:应用于所有路由
- 路由过滤器:应用于特定路由
- 内置过滤器:Spring Cloud Gateway提供的过滤器
- 自定义过滤器:根据业务需求开发的过滤器
最佳实践
路由设计:
- 使用有意义的路由ID
- 合理设计URL路径规则
- 考虑版本控制和向后兼容
- 使用断言组合实现复杂路由逻辑
安全配置:
- 实施统一的身份验证
- 使用HTTPS保护数据传输
- 配置适当的CORS策略
- 实施API密钥管理
性能优化:
- 配置合适的连接池大小
- 使用缓存减少后端请求
- 实施适当的限流策略
- 监控和优化响应时间
容错处理:
- 配置熔断器保护后端服务
- 实施优雅的降级策略
- 设置合理的超时时间
- 提供有意义的错误响应
监控运维:
- 配置健康检查端点
- 收集关键性能指标
- 实施日志聚合和分析
- 设置告警机制
注意事项
性能考虑:
- Gateway基于Netty,适合高并发场景
- 避免在过滤器中执行阻塞操作
- 合理配置内存和线程池
配置管理:
- 使用配置中心管理路由配置
- 支持动态路由更新
- 区分不同环境的配置
安全防护:
- 防止SQL注入和XSS攻击
- 实施请求大小限制
- 配置IP白名单/黑名单
故障排查:
- 启用详细的调试日志
- 使用链路追踪工具
- 监控关键指标和告警
扩展方向
高级功能:
- 实施API版本管理
- 集成服务网格
- 支持GraphQL网关
- 实现智能路由
集成组件:
- 集成配置中心(Nacos、Apollo)
- 集成链路追踪(Zipkin、Jaeger)
- 集成监控系统(Prometheus、Grafana)
- 集成日志系统(ELK Stack)
通过本章学习,你应该掌握了Spring Cloud Gateway的核心概念和实际应用。API网关是微服务架构中的关键组件,合理的设计和配置能够显著提升系统的可用性、安全性和可维护性。
下一章我们将学习配置中心,了解如何在微服务架构中实现统一的配置管理。