SpringCloud 微服务架构与 Gateway

注:完整笔记可在 https://github.com/WuSangui571/cloud-demo 中的 README.md 文件浏览,此处发表的是经由 AI 润色过的精简版。

1. Gateway 概述

在包含订单、商品、支付、物流等多个微服务实例的业务场景中,前端需记忆众多服务地址。为简化管理,引入 Gateway 作为所有服务的统一入口。前端只需访问网关地址,网关根据请求路径通过服务注册与发现机制(例如 Nacos)转发至目标微服务。

image-20250928162634307.png

2. 路由配置

2.1 需求与实现

  • 需求:

    1. /api/order/** 路由至 service-order

    2. /api/product/** 路由至 service-product

    3. 实现负载均衡。

2.2 配置步骤

  1. 创建 Gateway 模块

    • servicemodel 同级目录下新建 gateway 模块。

  2. 添加依赖

<!-- 网关依赖 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 注册中心 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
  1. 编写主入口程序

package com.sangui.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* @author sangui
* @createTime 2025-09-28
* @description Gateway 主入口程序
* @version 1.0
*/
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayMainApplication {
   public static void main(String[] args) {
       SpringApplication.run(GatewayMainApplication.class, args);
  }
}
  1. 配置基础信息

spring:
application:
  name: gateway
cloud:
  nacos:
    server-addr: 127.0.0.1:8848
server:
port: 80
  1. 定义路由规则

    • application.yaml 或独立 application-route.yaml 中配置:

spring:
cloud:
  gateway:
    routes:
      - id: order-route
        uri: lb://service-order
        predicates:
          - Path=/api/order/**
        order: 1
      - id: product-route
        uri: lb://service-product
        predicates:
          - Path=/api/product/**
        order: 2
      - id: bing-route
        uri: https://cn.bing.com
        predicates:
          - Path=/**
        order: 999
profiles:
  include: route
  1. 调整服务路径

    • 修改 service-orderservice-product 的 Controller 添加前缀:

package com.sangui.order.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/order")
@RestController
public class OrderController {
   @GetMapping("/writeDb")
   public String writeDb() {
       return "writeDb success";
  }
}
package com.sangui.product.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/product")
@RestController
public class ProductController {
   @GetMapping("/product/{id}")
   public Product getProduct(@PathVariable("id") Long productId) {
       return new Product();
  }
}
  • 注意:Feign 客户端需逐一调整路径,例如:

package com.sangui.order.feign;

import com.sangui.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
   @GetMapping("/api/product/product/{productId}")
   Product getProductById(@PathVariable("productId") Long productId);
}
  1. 验证

    • 访问 http://localhost:80/api/order/create?userId=1&productId=2,实现负载均衡路由。

3. 断言配置

3.1 断言类型

  • Gateway 使用 RoutePredicateFactory 实现断言,支持多种类型:

名称参数(个数/类型)作用
After1/datetime指定时间之后
Before1/datetime指定时间之前
Between2/datetime指定时间区间
Cookie2/string,regexp匹配 Cookie 值
Header2/string,regexp匹配请求头值
HostN/string匹配 Host 值
MethodN/string匹配请求方法
Path2/List<String>,bool匹配路径,支持尾部 /
Query2/string,regexp匹配请求参数
RemoteAddr1/List<String>匹配请求来源(CIDR)
Weight2/string,int按权重负载均衡
XForwardedRemoteAddr1/List<String>匹配 X-Forwarded-For 来源

3.2 断言写法

  • 完整写法

spring:
cloud:
  gateway:
    routes:
      - id: order-route
        uri: lb://service-order
        predicates:
          - name: Path
            args:
              patterns: /api/order/**
              matchTrailingSlash: true
  • 简写

predicates:
- Path=/api/order/**

3.3 自定义断言

  • 示例:定义 Vip 断言,匹配 user=sangui 参数。

package com.sangui.gateway.predicate;

import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;

import java.util.List;
import java.util.function.Predicate;

/**
* @author sangui
* @createTime 2025-09-28
* @description 自定义 Vip 断言工厂
* @version 1.0
*/
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
public VipRoutePredicateFactory() {
super(Config.class);
}

@Override
public List<String> shortcutFieldOrder() {
return List.of("param", "value");
}

@Override
public Predicate<ServerWebExchange> apply(Config config) {
return (GatewayPredicate) exchange -> {
String paramValue = exchange.getRequest().getQueryParams().getFirst(config.param);
return StringUtils.hasText(paramValue) && paramValue.equals(config.value);
};
}

@Validated
@Getter
@Setter
public static class Config {
@NotEmpty
private String param;
@NotEmpty
private String value;
}
}
  • 配置:

spring:
cloud:
gateway:
routes:
- id: bing-route
uri: https://cn.bing.com
predicates:
- Path=/search
- Query=q,haha
- Vip=user,sangui
  • 验证:访问 http://localhost/search?q=haha&user=sangui 路由至 Bing。

4. 过滤器配置

4.1 路径重写

  • 问题:若服务路径无 /api/order/ 前缀,访问 /api/order/readDb 会 404。

  • 解决方案:使用 RewritePath 过滤器。

spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://service-order
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/order/?(?<segment>.*), /${segment}
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/?(?<segment>.*), /${segment}
  • 验证:访问 http://localhost/api/order/writeDb 成功。

4.2 自定义过滤器

  • 添加响应头

filters:
- RewritePath=/api/order/?(?<segment>.*), /${segment}
- AddResponseHeader=sangui, blog

image-20250929151201306.png

  • 默认过滤器

spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=sangui, blog
routes:
# ...
  • 全局过滤器

    • 示例:记录请求耗时。

package com.sangui.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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* @author sangui
* @createTime 2025-09-29
* @description 记录请求耗时全局过滤器
* @version 1.0
*/
@Slf4j
@Component
public class RtGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
long start = System.currentTimeMillis();
log.info("请求 [{}] 开始,时间: {}", request.getURI(), start);
return chain.filter(exchange).doFinally(res -> {
long end = System.currentTimeMillis();
log.info("请求 [{}] 结束,耗时: {}ms", request.getURI(), end - start);
});
}

@Override
public int getOrder() {
return 0;
}
}
  • 自定义过滤器工厂

    • 示例:添加一次性令牌。

package com.sangui.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
* @author sangui
* @createTime 2025-09-29
* @description 一次性令牌自定义过滤器
* @version 1.0
*/
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
String token = "uuid".equalsIgnoreCase(config.getValue()) ? UUID.randomUUID().toString() : "Test Token";
response.getHeaders().add(config.getName(), token);
}));
}
}
  • 配置:

spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://service-order
filters:
- OnceToken=X-Response-Token, uuid

image-20250929154415833.png

5. 全局跨域配置

  • 单体服务:使用 @CrossOrigin 或配置 CorsFilter Bean。

  • 微服务:通过 Gateway 配置:

spring:
cloud:
  gateway:
    globalcors:
      cors-configurations:
        '[/**]':
          allowed-origin-patterns: '*'
          allowed-headers: '*'
          allowedMethods: '*'
  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录