Featured image of post Gateway

Gateway

Java微服务学习笔记之Gateway使用

1.创建网关模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    <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.配置规则

需**求1:客户端发送/api/order/转到service-order

**需求2:客户端发送/api/product/转到service-product

需求3:以上转发均有负载均衡效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
spring:
  cloud:
    gateway:
      routes: #      - id: service_a
        - id: order-route
          uri: lb://service-order
          predicates当请求路径以 /api/product/ 开头时请求会被路由到 service-product 服务: #            - Path=/service-a/**
            - Path=/api/order/**
        - id: product-route
          uri: lb://serivce-product
          predicates: #            - Path=/service-a/**
            - Path=/api/product/**

//id:唯一标识这个路由规则
//uri:lb://service-order  //表示使用 Spring Cloud LoadBalancer 进行客户端负载均衡
                            //service-order是服务注册中心中的服务名
//predicates:当请求路径以 /api/product/ 开头时,请求会被路由到 service-product 服务

这些路由规则有自己的执行循序默认从上往下执行如果想要执行更快可以使用order: 1规定执行顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- id: bingo-route          # 路由的唯一标识符
  uri: https://www.bing.com # 目标URL(直接转发到Bing)
  predicates:              # 路由匹配条件
    - name: Path           # 第一个条件路径匹配
      args:
        pattern: /search   # 只匹配以/search开头的路径
    - name: Query          # 第二个条件查询参数匹配
      args:
        param: q           # 检查名为"q"的查询参数
        regex: haha        # 该参数值必须匹配正则表达式"haha"

这个规则表示只有请求路径为search?q=haha请求才会被转发到bing.com中

2.1.自定义断言工厂

1.一个类的名字要写成xxxRoutePredicateFactory.Config,并且要继承AbstractRoutePredicateFactory,泛型为类名

2.必须重写父类的shortcutFieldOrder和apply方法,apply方法代码逻辑主要写业务代码逻辑

如以下代码是判断用户是否为vip用户的业务逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.example.gateway.predicate;

import jakarta.validation.constraints.NotEmpty;
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.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {

    public VipRoutePredicateFactory() {
        super(Config.class);
    }

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                //localhost/search?q=haha&user=Lsec
                ServerHttpRequest request = exchange.getRequest();
                String first = request.getQueryParams().getFirst(config.getParam());
                if (StringUtils.hasText(first) && first.equals(config.getValue())) {
                    return true;
                }
                return false;
            }
        };
    }

    /*
    * 可以配置的参数
    * */
    public static class Config{
        @NotEmpty
        private String param;
        @NotEmpty
        private String value;

        public String getParam() {
            return param;
        }
        public void setParam(String param) {
            this.param = param;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
}

//配置文件规则
            - name: Vip
              args:
                param: user
                value: Lsec

3.Filter过滤器

3.1.过滤器Filter基本使用

**路径重写Filter:**RewritePath,什么是路径重写,当你想使用/api/product这个基准路径访问业务时,需要在product模块的controller层代码添加对应的基准路径,有时你会觉得不方便,如果使用路径重写filter,你就可以不用在controller层添加基准路径的api,直接访问即可

1
2
3
4
5
6
7
8
9
        - id: product-route
          uri: lb://serivce-product
          predicates: #            - Path=/service-a/**
            - Path=/api/product/**
          filters:
            - RewritePath=/api/product/?(?<segment>.*),/$\{segment}
            //修改请求的路径后再转发到目标服务
            //请求 /api/product/123 → 重写为 /123 → 转发到 service-product/123
            //请求 /api/product/items → 重写为 /items → 转发到 service-product/items

**添加请求头响应头Filter:**AddRequestHeader/AddResponseHeader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
spring:
  cloud:
    gateway:
      routes: #      - id: service_a
        - id: order-route
          uri: lb://service-order
          predicates: #            - Path=/service-a/**
            - Path=/api/order/**
          filters:
            - RewritePath=/api/order/?(?<segment>.*),/$\{segment}
            - AddRequestHeader=X-Response-ABC, 123
            - AddResponseHeader=X-Response-Foo, Bar

就是在请求包中添加请求头和响应头

3.2.默认Filter

如果你的路由没有写Filter,但是存在默认Filter,路由默认会使用这个Filter

1
2
3
4
5
      default-filters:
        - name: AddRequestHeader
          args:
            name: X-Request-Foo
            value: Bar

3.3.全局Filter

想要实现全局filter,需要在实现GlobalFilter,并且重写filter方法

下面是一个计算请求时间的全局过滤器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.example.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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;

@Slf4j
@Component
public class RtGlobalFilter implements GlobalFilter {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String uri = request.getURI().toString();
        long start = System.currentTimeMillis();
        log.info("请求开始,uri:{},时间:{}ms", uri, start);
        /*==============================================*/
        Mono<Void> filter = chain.filter(exchange)
                .doFinally(result -> {
                    long end = System.currentTimeMillis();
                    log.info("请求结束,uri:{},时间:{}ms", uri, (end - start));
                });
        return filter;
    }
}

3.4.自定义过滤器工厂

自定义过滤器工厂和自定义断言工厂一样,可以看Spring Cloud自带的过滤器工厂是怎么设置的,根据样式仿写即可

可以看到系统的工厂需要继承AbstractNameValueGatewayFilterFactory这个父类工厂,并重写apply方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.UUID;


@Component
public class OnceTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<OnceTokenGatewayFilterFactory.Config> {
    public OnceTokenGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders headers = response.getHeaders();

            String value = config.getValue();
            if (value == null || value.isEmpty()) {
                value = UUID.randomUUID().toString(); // 默认生成 UUID
            } else if (value.equals("uuid")) {
                value = UUID.randomUUID().toString();
            } else if (value.equals("jwt")) {
                value = "jwt_token";
            }

            headers.add("once-token", value);
        }));
    }

    public static class Config {
        private String value; // 移除 @NotEmpty

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}

4.全局跨域(CORS)

4.1.什么是跨域

1
2
3
4
5
6
7
8
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: "*"# 支持的方法

经典面试题:微服务之间的调用是否经过网关

默认是不经过网关的,但是也可以经过网关,只需要将远程调用的地址改为网关地址,让网关来发送请求即可

By Lsec
最后更新于 Jun 09, 2025 12:37 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ