小白学习spring-cloud(四): 集成spring-cloud-gateway

178 阅读3分钟

版本信息

  • java版本:17.0.8
  • maven版本 3.9.2
  • 操作系统:windows11
  • 项目依赖版本如下:
<properties>
    <java.version>1.8</java.version>
    <lombok.version>1.18.26</lombok.version>
    <log4j.version>1.2.17</log4j.version>
    <fastjson2.version>2.0.14</fastjson2.version>
    <fastjson.version>1.2.49</fastjson.version>
    <hutool-all.version>5.7.13</hutool-all.version>
    <commons-lang3.version>3.12.0</commons-lang3.version>
    <jjwt.version>0.9.0</jjwt.version>
    <nimbus-jose-jwt.version>9.23</nimbus-jose-jwt.version>
    <tencentcloud-sdk-java.version>3.1.270</tencentcloud-sdk-java.version>
    <spring-boot-maven-plugin.version>2.7.3</spring-boot-maven-plugin.version>
    <spring-cloud-oauth2.version>2.2.5.RELEASE</spring-cloud-oauth2.version>
    <mybatis-spring-boot.version>2.2.2</mybatis-spring-boot.version>
    <pagehelper-spring-boot.version>1.3.0</pagehelper-spring-boot.version>
    <spring-cloud.version>2021.0.4</spring-cloud.version>
    <!-- 覆盖SpringBoot中okhttp3的旧版本声明,解决MinIO 8.3.x的依赖冲突 -->
    <okhttp3.version>4.8.1</okhttp3.version>
    <!-- spring-boot原本的okhttp版本 -->
    <okhttp.version>3.14.9</okhttp.version>
    <spring-boot.version>2.6.12</spring-boot.version>
    <spring-boot-test.version>2.6.12</spring-boot-test.version>
    <spring-boot-security.version>2.6.12</spring-boot-security.version>
    <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
    <alibaba.nacos.version>1.4.2</alibaba.nacos.version>
</properties>

父项目完整pom如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>spring-cloud-learning</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>spring-cloud-learning</name>

    <properties>
       <java.version>1.8</java.version>
       <lombok.version>1.18.26</lombok.version>
       <log4j.version>1.2.17</log4j.version>
       <fastjson2.version>2.0.14</fastjson2.version>
       <fastjson.version>1.2.49</fastjson.version>
       <hutool-all.version>5.7.13</hutool-all.version>
       <commons-lang3.version>3.12.0</commons-lang3.version>
       <jjwt.version>0.9.0</jjwt.version>
       <nimbus-jose-jwt.version>9.23</nimbus-jose-jwt.version>
       <tencentcloud-sdk-java.version>3.1.270</tencentcloud-sdk-java.version>
       <spring-boot.version>2.6.12</spring-boot.version>
       <spring-boot-maven-plugin.version>2.7.3</spring-boot-maven-plugin.version>
       <spring-cloud-oauth2.version>2.2.5.RELEASE</spring-cloud-oauth2.version>
       <mybatis-spring-boot.version>2.2.2</mybatis-spring-boot.version>
       <pagehelper-spring-boot.version>1.3.0</pagehelper-spring-boot.version>
       <spring-boot-test.version>2.6.12</spring-boot-test.version>
       <spring-boot-security.version>2.6.12</spring-boot-security.version>
       <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
       <alibaba.nacos.version>1.4.2</alibaba.nacos.version>
       <spring-cloud.version>2021.0.4</spring-cloud.version>
       <!-- 覆盖SpringBoot中okhttp3的旧版本声明,解决MinIO 8.3.x的依赖冲突 -->
       <okhttp3.version>4.8.1</okhttp3.version>
       <!-- spring-boot原本的okhttp版本 -->
       <okhttp.version>3.14.9</okhttp.version>
    </properties>

    <dependencies>
       <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <scope>runtime</scope>
       </dependency>

       <dependency>
          <groupId>com.squareup.okhttp3</groupId>
          <artifactId>okhttp</artifactId>
          <version>${okhttp3.version}</version>
       </dependency>

       <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>${fastjson.version}</version>
       </dependency>

       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-validation</artifactId>
       </dependency>

       <dependency>
          <groupId>cn.hutool</groupId>
          <artifactId>hutool-all</artifactId>
          <version>${hutool-all.version}</version>
       </dependency>

       <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>${commons-lang3.version}</version>
       </dependency>

       <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>${log4j.version}</version>
       </dependency>

       <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>${lombok.version}</version>
          <optional>true</optional>
       </dependency>

       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
       </dependency>
    </dependencies>

    <!--
        https://start.spring.io/actuator/info
        cloud版本一定要和boot版本匹配
        必须以这种方式集成cloud-->
    <dependencyManagement>
       <dependencies>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-dependencies</artifactId>
             <version>${spring-boot.version}</version>
             <type>pom</type>
             <scope>import</scope>
             <exclusions>
                <exclusion>
                   <groupId>com.squareup.okhttp3</groupId>
                   <artifactId>okhttp</artifactId>
                </exclusion>
             </exclusions>
          </dependency>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <version>${spring-boot-test.version}</version>
             <scope>test</scope>
          </dependency>
          <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-dependencies</artifactId>
             <version>${spring-cloud.version}</version>
             <type>pom</type>
             <scope>import</scope>
          </dependency>

          <!-- spring cloud alibaba -->
          <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-alibaba-dependencies</artifactId>
             <version>${spring-cloud-alibaba.version}</version>
             <type>pom</type>
             <scope>import</scope>
          </dependency>
       </dependencies>
    </dependencyManagement>

    <build>
       <plugins>
          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
             <version>${spring-boot-maven-plugin.version}</version>
             <configuration>
                <skip>true</skip>
             </configuration>
          </plugin>
       </plugins>
    </build>
</project>

新建名为spring-cloud-gateway的子模块

项目结构如下图:

image.png

经典配置中的核心概念

  • 先通过一个典型的简化版配置来了解几个核心概念,假设Spring Cloud Gateway应用正在运行,监听10005端口,一旦有远程请求来到10005端口,下面的配置就会生效了,三个核心概念,以及每个配置的作用,请参考中文注释:
server:
  port: 10005
    
spring:
  cloud:
    gateway:
      # 核心概念1:路由,一个路由代表一个处理逻辑,
      # 该逻辑里面包含三个元素:匹配条件(是否该此路由处理)、真实处理地址、过滤器
      routes:
        # id要确保唯一性
      - id: add_request_header_route
      	# 真实处理地址,请求一旦确定是当前路由处理,就会转发到这个地址去
        uri: https://example.org
        # 核心概念2:谓语或者断言,作用是判断请求是否由当前路由处理
        predicates:
          # 这是断言的一种,检查请求的Cookie中mycookie的值是否等于mycookievalue
        - Cookie=mycookie,mycookievalue
        # 核心概念3:过滤器,请求前和请求后都可以有过滤器处理请求响应数据
        filters:
          # 这个过滤器的作用是在请求header中添加一个键值对,值等于"aaabbbccc"
        - AddRequestHeader=X-Request-Red, aaabbbccc
  • 上述配置信息中的predicates是简化版配置,和完整配置对比效果如下,简单的说就是把一行拆成了三项:name、args.name、args.regexp

image.png

  • 引入依赖
<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>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- bootstrap 文件生效 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • 配置bootstrap.yml
server:
  port: 10005

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
        routes:
            - id: path_route
              # 匹配成功后,会被转发到8082端口,至于端口后面的path,会直接使用原始请求的
              # 例如http://127.0.0.1:10005/nacos/test,会被转发到http://127.0.0.1:9001/nacos/test
              uri: http://127.0.0.1:9001
              predicates:
                # 根据请求路径中带有"/nacos/",就算匹配成功
              - Path=/nacos/**
      discovery:
        locator:
          # 开启这个,可以使用lb://nacos服务列表的服务名,转发请求
          enabled: true
    nacos:
      discovery:
        server-addr: localhost:8848
  • 如果要转发到其他域名下,需要创建配置类解决跨域问题:
package com.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}
  • 启动类:
package com.gateway;

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

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
  • 最后是单元测试类,请注意,由于Spring Cloud Gateway使用了webflux技术栈,因此不能用常见的MockMvc来模拟请求,几个注解也值得注意,另外也要注意WebTestClient的expectStatus、expectBody等API的用法:
package com.gateway;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
public class GatewayTest {

    @Autowired
    private WebTestClient webClient;

    @Test
    void testHelloPredicates() {
        webClient.get()
                .uri("/nacos/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // 验证状态
                .expectStatus().isOk()
                // 验证结果,注意结果是字符串格式
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains("hello")));
    }
}

请确保nacos-provider应用已经启动,再运行上面创建的GatewayTest.java,得到结果如下,测试通过,spring-cloud-gateway的功能符合预期,成功的将请求转发到nacos-provider应用,并且成功收到响应:

image.png

至此,《Spring Cloud Gateway实战》系列的准备工作已经完成,而且开发了一个简单的应用体验最基本的Spring Cloud Gateway功能,接下来的文章,咱们一起实战更多基本功能。