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 实现网关层统一限流
相关推荐
Ting-yu1 分钟前
SpringCloud快速入门(11)---- Sentinel(异常处理)
java·spring boot·后端·spring·spring cloud·sentinel
_童年的回忆_2 分钟前
【Linux】安装Jenkins并且打包发布springboot项目
linux·spring boot·jenkins
彭于晏Yan10 分钟前
Maven 资源插件:非过滤文件后缀配置及风险规避
java·spring boot·maven
贫民窟的勇敢爷们8 小时前
SpringBoot整合AOP切面编程实战,实现日志统一记录+接口权限校验
java·spring boot·spring
吾疾唯君医12 小时前
Java SpringBoot集成积木报表实操记录
java·spring boot·spring·导出excel·积木报表·数据文件下载
正儿八经的少年15 小时前
Spring Boot 两种激活配置方式的作用与区别
java·spring boot·后端
疯狂成瘾者16 小时前
Spring Boot 项目中的 SMTP 邮件验证码服务技术解析
java·spring boot·后端
啃臭17 小时前
AOP和反射
java·spring boot
河阿里17 小时前
SpringBoot:Spring Task定时任务完整使用教学
java·spring boot·spring
五阿哥永琪20 小时前
从0开始做一个导出功能,完整流程
spring boot