Spring Cloud Alibaba(四)---Spring Cloud Gateway

Spring Cloud Gateway(服务网关)

一、灵魂拷问:为什么要请这位"霸道总裁"?(Gateway的介绍)

1. 为什么要用Gateway?(重点中的重点)

想象一下,你是一个前端Vue页面的负责人。在没有网关的"史前时代",你的日子是怎么过的?

  • 硬编码的痛 :你的代码里写满了 http://192.168.1.100:8080 这种硬邦邦的地址。万一后端微服务搬家了(IP变了)或者扩容了(端口变了),你得重新打包发布,简直是噩梦。
  • 负载均衡的梦魇:如果后端有三个微服务实例,你是要自己写轮询算法吗?还是在Nginx里配到头晕?

没有网关的架构图(惨状):

text 复制代码
      Vue前端 (满头大汗)
         |
    (硬编码IP:Port,写死在代码里)
         |
   -----------------------
   |          |          |
微服务1    微服务2    微服务3
(挂了)     (忙死)     (闲死)

有了Gateway之后:

Gateway就是微服务架构里的"前台接待"+"安保队长"。所有请求先找它,它来决定把你带到哪个房间。

2. 什么是Gateway?

  • 官方定义 :Spring Cloud官方基于 WebFlux (底层是Netty,也就是传说中的Reactor NIO) 开发的亲儿子,专门用来替换掉上一代老古董Zuul的。
  • 核心技能:路由(指路)、过滤(安检)、限流(防暴乱)。
  • 为什么比Zuul强? Zuul 1.x是同步阻塞的(一个人占一个线程,累死),Gateway是异步非阻塞的(Netty加持,少线程干大事),性能起飞。

3. 启动器(依赖)

记住这个咒语,千万别念错:

  • 要加spring-cloud-starter-gateway
  • 千万别加spring-boot-starter-web
  • 原因:Gateway是Netty驱动的,Web是Tomcat驱动的。这俩是死对头,加在一起会打架,启动直接报错教你做人。
二、搭建工程:给总裁安个家

1. pom.xml(招兵买马)

xml 复制代码
<dependencies>
    <!-- 网关核心 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 注册中心(让网关知道微服务在哪) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

2. application.yml(立规矩)

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.61.132:8848 # 告诉Nacos在哪,我要去登记
  application:
    name: api-gateway # 网关的大名

3. 启动类(剪彩仪式)

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient // 别忘了这个,不然就是个瞎子,找不到微服务
public class ApiGatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApp.class);
    }
}
三、路由(Routing):指路艺术

路由就是告诉网关:"来了这种请求,往哪里送"。

1. 内置断言工厂(Predicates)

断言就是判断条件。只有条件满足了(True),才进行路由转发。

  • lb://:这是神技!意思是"负载均衡",后面跟服务名,网关会自动去Nacos拉取服务列表,然后挑一个转发。再也不用写死IP了!
yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: sentinel-consumer # ID必须唯一,随便起,别重复
          # uri: http://127.0.0.1:80 # 这种硬编码方式已经被淘汰了
          uri: lb://sentinel-consumer # 负载均衡,去Nacos找 sentinel-consumer 服务
          predicates:
            - Path=/consumer/** # 只要路径是 /consumer/ 开头的,就归我管
            - After=2022-04-09T13:20:54.957+08:00[Asia/Shanghai] # 只有在这个时间之后的请求才放行(防穿越?)

2. 自定义断言工厂(DIY判断逻辑)

如果内置的不够用,比如你想搞个"年龄断言"(只有18-60岁才能访问),那就自己写。

  • 核心思路 :继承 AbstractRoutePredicateFactory
java 复制代码
// 伪代码示意,具体抄作业
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
    // 构造方法...
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 获取参数里的年龄
            String ageStr = exchange.getRequest().getQueryParams().getFirst("age");
            int age = Integer.parseInt(ageStr);
            // 判断是否在范围内
            return age >= config.getMinAge() && age <= config.getMaxAge();
        };
    }
    // 配置类...
}

配置使用:

yaml 复制代码
predicates:
  - Path=/consumer/**
  - Age=18,60 # 调用你的自定义断言
四、过滤(Filtering):安检与整容

路由是"去哪",过滤是"路上干什么"。比如:加个头、删个脚、查个证。

1. 内置过滤器工厂

最常用的是 StripPrefix,就是"脱衣舞"------把URL前面那一截去掉再转发。

  • 场景 :网关收到 /consumer/api/doSomething,但微服务只认识 /api/doSomething
  • 配置StripPrefix=1(去掉第一层 /consumer)。
yaml 复制代码
filters:
  - StripPrefix=1 

2. 自定义全局过滤器(GlobalFilter)------ 大内总管

这是"大内总管",对所有请求生效。通常用来做统一的鉴权、日志记录。

  • 核心接口GlobalFilter + Ordered
java 复制代码
package com.haogu.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.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;

/**
 * @author ShanYu
 * @version 1.0
 * @description:  自定义全局过滤器
 * 
 */

//@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	String token = exchange.getRequest().getQueryParams().getFirst("token");
	if(token == null){
	    Map<String, Object> result = new HashMap<>();
	    result.put("status", 401);
	    result.put("msg", "未登录");
	    return response(exchange.getResponse(), result);
	}
	return chain.filter(exchange);//放行
    }

    /**
     * 往前端返回json串
     * @param response
     * @param data
     * @return
     */
    private Mono<Void> response(ServerHttpResponse response, Object data) {
	String jsonData = null;
	try {
	    jsonData = new ObjectMapper().writeValueAsString(data);
	} catch (JsonProcessingException e) {
	    e.printStackTrace();
	}
	DataBuffer buffer =
		response.bufferFactory().wrap(jsonData.getBytes(StandardCharsets.UTF_8));
	response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
	return response.writeWith(Mono.just(buffer));
    }

    //返回值越小该过滤器的执行优先级越高
    @Override
    public int getOrder() {
	return 0;
    }
}

3. 自定义局部过滤器工厂(GatewayFilterFactory)------ 贴身保镖

针对特定路由生效。比如只检查 /admin/** 的登录状态。

  • 核心接口AbstractGatewayFilterFactory
java 复制代码
package com.haogu.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
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;


/**
 * @author ShanYu
 * @version 1.0
 * @description:  自定义局部过滤器
 * 
 */
@Component
public class LoginGatewayFilterFactory extends AbstractGatewayFilterFactory {

    @Override
    public GatewayFilter apply(Object config) {
	return new GatewayFilter() {
	    @Override
	    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		String token = exchange.getRequest().getQueryParams().getFirst("token");
		if(token == null){
		    Map<String, Object> result = new HashMap<>();
		    result.put("status", 401);
		    result.put("msg", "未登录-局部");
		    return response(exchange.getResponse(), result);
		}
		return chain.filter(exchange);//放行
	    }
	};
    }

    /**
     * 往前端返回json串
     * @param response
     * @param data
     * @return
     */
    private Mono<Void> response(ServerHttpResponse response, Object data) {
	String jsonData = null;
	try {
	    jsonData = new ObjectMapper().writeValueAsString(data);
	} catch (JsonProcessingException e) {
	    e.printStackTrace();
	}
	DataBuffer buffer =
		response.bufferFactory().wrap(jsonData.getBytes(StandardCharsets.UTF_8));
	response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
	return response.writeWith(Mono.just(buffer));
    }
}
五、总结与避坑指南
  1. 响应式编程 :Gateway基于WebFlux,所以你会看到很多 MonoFlux。如果你习惯了Servlet的同步编程,刚开始会觉得脑子转不过弯。记住:return chain.filter(exchange) 是把接力棒交给下一个人。
  2. 依赖冲突 :再次强调,pom.xml 里别手贱加 spring-boot-starter-web,除非你想看控制台报错报错。
  3. Nacos联动lb://服务名 是灵魂,一定要确保Nacos里能查到这个服务,否则网关会报 404 Not Found 或者 Service Unavailable
  4. 过滤器顺序 :全局过滤器和局部过滤器是可以共存的,执行顺序由 getOrder() 决定。
相关推荐
Rust研习社2 小时前
Once、OnceCell、OnceLock:Rust 一次性初始化终极指南
后端·rust·编程语言
Rust研习社2 小时前
从入门到实践:Rust 异步编程完全指南
开发语言·后端·rust
GreenTea2 小时前
DeepSeek-V4 技术报告深度分析:基础研究创新全景
前端·人工智能·后端
用户8356290780512 小时前
使用 Python 自动管理 PowerPoint 幻灯片分节的方法
后端·python
逸风尊者2 小时前
XGBoost模型工程使用
java·后端·算法
ekuoleung3 小时前
量化平台中的 DSL 设计与实现:从规则树到可执行策略
前端·后端
小研说技术3 小时前
实时通信对比,一场MCP协议的技术革命
前端·后端·面试
ServBay3 小时前
2026年 Go 开发中没有它就不行的 10 个库
后端·go
SamDeepThinking4 小时前
别让一个超时的第三方http接口拖垮所有接口
java·后端·架构