Spring Cloud Gateway

一、为什么使用 Gateway

微服务架构下,客户端直接调用多个微服务存在缺陷:

  1. 客户端需要维护大量微服务地址,难以管理
  2. 无法实现负载均衡
  3. 无法统一做鉴权、限流、日志、跨域处理

解决方案 :使用 Spring Cloud Gateway 作为统一入口,接管所有客户端请求,实现路由转发、过滤、限流、鉴权等功能。

主流网关:Nginx+Lua、Spring Cloud Gateway(性能是 Zuul 1.6 倍,Spring Cloud Alibaba 推荐使用)


二、Gateway 核心概念

  1. 路由(Route):网关的基础,由 ID、目标 URI、断言、过滤器组成
  2. 断言(Predicate):匹配请求规则,满足条件才转发
  3. 过滤器(Filter):对请求 / 响应进行修改、拦截、增强
  4. 执行流程:客户端请求 → 断言匹配 → 过滤链(前置过滤→微服务→后置过滤)→ 返回响应

三、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>

        <!-- ⚠️ 绝对不能引入 web 依赖,会冲突!-->
    </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: consumer-route          # 唯一标识
          uri: http://localhost:8080  # 目标地址
          predicates:
            - Path=/consumer/**       # 路径匹配

2. 服务名路由(推荐,支持负载均衡)

yaml

复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: consumer-route
          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 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.isNotBlank(ageStr)) {
                int age = Integer.parseInt(ageStr);
                return age > config.minAge && age < config.maxAge;
            }
            return false;
        };
    }

    @Data
    public static class Config {
        private int minAge;
        private int maxAge;
    }
}

配置

yaml

复制代码
predicates:
  - Path=/consumer/**
  - Age=18,60

六、过滤器(Filter)

1. 分类

  • GatewayFilter:局部过滤器,作用于单个路由
  • GlobalFilter:全局过滤器,作用于所有路由

2. 自定义局部过滤器(接口耗时统计)

java

运行

复制代码
package com.hg.filter;

import lombok.Data;
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 List<String> shortcutFieldOrder() {
        return Arrays.asList("enable");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!config.enable) {
                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");
            }));
        };
    }

    @Data
    public static class Config {
        private boolean enable;
    }
}

配置

yaml

复制代码
filters:
  - Log=true

3. 自定义全局过滤器(登录校验)

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.io.buffer.DataBuffer;
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.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 {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getQueryParams().getFirst("token");

        if (token == null) {
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> map = new HashMap<>();
            map.put("code", 401);
            map.put("msg", "请先登录");

            byte[] bytes = new byte[0];
            try {
                bytes = new ObjectMapper().writeValueAsString(map).getBytes(StandardCharsets.UTF_8);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            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);
    }
}

七、Gateway 整合 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.config;

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 SentinelConfig {

    @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);
    }
}

八、核心总结

  1. Gateway = 路由 + 断言 + 过滤器
  2. 不能加 web 依赖,基于 WebFlux 异步非阻塞
  3. 路由推荐使用 lb://服务名 实现负载均衡
  4. 断言用于匹配请求,过滤器用于处理请求
  5. 全局过滤器可做统一登录鉴权
  6. 整合 Sentinel 实现网关层统一限流
相关推荐
lclcooky3 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
William Dawson4 小时前
【一文吃透 Spring Boot 面向切面编程(AOP):实例\+实现\+注意事项】
java·spring boot
mldlds5 小时前
SpringBoot项目如何导入外部jar包:详细指南
spring boot·后端·jar
Devin~Y5 小时前
大厂Java面试实战:Spring Boot/Cloud + Redis/Kafka + K8s + RAG/Agent 追问全流程(小Y翻车记)
java·spring boot·redis·spring cloud·kafka·kubernetes·micrometer
Counter-Strike大牛6 小时前
SpringBoot中使用POI+EasyExcel批量导出主子表信息,以箱单为例
windows·spring boot·后端
weiwen14087 小时前
快递100 API 工具类封装实践:签名、请求与缓存防锁单
spring boot·spring·缓存
码农阿豪8 小时前
群晖部署Moodist配内网穿透穿透,把白噪音服务搬到公网上
数据库·spring boot·后端
MuzySuntree8 小时前
Ubuntu 下 Maven 构建 Spring Boot 项目报错 release version 17 not supported 解决方案
spring boot·ubuntu·maven
想不明白的过度思考者8 小时前
一个叫Swagger的工具,让写接口文档变成享受
java·spring boot·接口·swagger