文章目录

  • 一、介绍
  • 1.1 简介
  • 1.2 Gateway加入后的架构
  • 1.3 核心概念
  • 二、入门案例
  • 2.1 父工程pom.xml
  • 2.2 服务提供者
  • 2.2.1 pom.xml
  • 2.2.2 application.yml
  • 2.2.3 User.java
  • 2.2.4 UserMapper.java
  • 2.2.5 UserService
  • 2.2.6 UserServiceImpl.java
  • 2.2.7 UserController.java
  • 2.2.8 UserServiceApplication.java
  • 2.3 注册中心
  • 2.3.1 pom.xml
  • 2.3.2 application.yml
  • 2.3.3 EurekaServerApplication.java
  • 2.4 网关服务
  • 2.4.1 pom.xml
  • 2.4.2 application.yml
  • 2.4.3 GatewayServerApplication.java
  • 2.5 功能
  • 2.5 测试结果
  • 2.6 面向服务
  • 2.7 路由前缀
  • 2.7.1 介绍
  • 2.7.2 添加前缀
  • 2.7.3 去除前缀
  • 2.8 过滤器
  • 2.8.1 配置默认过滤器
  • 2.8.2 分类
  • 2.8.3 生命周期
  • 2.8.4 使用场景
  • 2.9 自定义过滤器
  • 2.9.1 自定义过滤器
  • 2.9.2 自定义局部过滤器
  • 2.9.2.1 过滤器
  • 2.9.2.2 过滤器 配置文件
  • 2.9.3 自定义全局过滤器
  • 2.9.3.1 过滤器
  • 2.9.3.2 测试结果
  • 2.10 总结和其他
  • 2.10.1 负载均衡
  • 2.10.2 Gateway跨域配置
  • 2.10.3 Gateway高可用
  • 2.10.4 与Feign的区别



一、介绍

1.1 简介

  • Spring Cloud Gateway是Spring官网基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关服务。
  • Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。
  • Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。
  • Spring Cloud Gateway是替代Netflix Zuul的一套解决方案。

1.2 Gateway加入后的架构

无论是来自客户端(pc、移动端)的请求,还是服务内部的调用。一切对服务的请求都可以经过网关,然后由网关来实现 鉴权、动态路由等等操作。此时Gateway(网关)就是我们服务的统一入口。

spring gateway CORS 允许特定origin_gateway

1.3 核心概念

  • 路由(route):由一个ID、一个目的URL(真实的服务地址)、一组断言工厂、一组Filter组成;如果路由断言为真,说明URL和配置路由匹配。
  • 断言(Predicate):Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的 ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义 匹配来自于HTTP Request中的任何信息,比如请求头和参数。
  • 过滤器(Filter):一个标准的Spring WebFilter。Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter(局部过滤器) 和 Global Filter(全局过滤器)。过滤器Filter将会对请求和响应进行修改处理

二、入门案例

2.1 父工程pom.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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kaikeba</groupId>
    <artifactId>springcloud-gateway</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>gateway-server</module>
        <module>eureka-server</module>
        <module>user-service</module>
    </modules>
    <!-- springboot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.18.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <springcloud.version>Greenwich.SR1</springcloud.version>
        <tk.mybatis.version>2.1.5</tk.mybatis.version>
        <mysql.connector.version>8.0.25</mysql.connector.version>
    </properties>

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

            <!-- tk-mybatis -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${tk.mybatis.version}</version>
            </dependency>

            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2 服务提供者

使用user-service当作服务提供者

2.2.1 pom.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-gateway</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>

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

</project>

2.2.2 application.yml

server:
  port: ${port:10000}

spring:
  application:
    name: user-service # 服务名
  datasource: # 数据源
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/springcloud
    driver-class-name: com.mysql.cj.jdbc.Driver

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka # 注册中心地址
  instance:
    ip-address: 127.0.0.1 # 显示的ip
    prefer-ip-address: true # 以ip的形式被调用
    lease-expiration-duration-in-seconds: 90 # 服务失效时间:默认90s
    lease-renewal-interval-in-seconds: 30 # 服务续约的时间间隔

2.2.3 User.java

package com.kaikeba.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.persistence.*;
import java.util.Date;

@Data
@Accessors(chain = true)
@Table(name = "tb_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "user_name")
    private String username;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;
    private Date birthday;
    private Date created;
    private Date updated;
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", birthday=" + birthday +
                ", created=" + created +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }
}

2.2.4 UserMapper.java

package com.kaikeba.mapper;

import com.kaikeba.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
}

2.2.5 UserService

package com.kaikeba.service;

import com.kaikeba.entity.User;

public interface UserService {
    /**
     * 根据主键查询
     */
    User findById(Integer userId);
}

2.2.6 UserServiceImpl.java

package com.kaikeba.service.impl;

import com.kaikeba.entity.User;
import com.kaikeba.mapper.UserMapper;
import com.kaikeba.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User findById(Integer userId) {
        return userMapper.selectByPrimaryKey(userId);
    }
}

2.2.7 UserController.java

package com.kaikeba.controller;

import com.kaikeba.entity.User;
import com.kaikeba.service.UserService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;

    @RequestMapping("/{userId}")
    public String searchByUserId(@PathVariable Integer userId) {
        User user = userService.findById(userId);
        return user.toString();
    }
}

2.2.8 UserServiceApplication.java

package com.kaikeba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class);
    }
}

2.3 注册中心

2.3.1 pom.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-gateway</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

2.3.2 application.yml

server:
  port: 8080

spring:
  application:
    name: eureka-server

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/wureka  # 服务注册的端口
    register-with-eureka: false  # b不注册自己
  server:
    enable-self-preservation: false # 关闭自我保护
    eviction-interval-timer-in-ms: 6000 #失效服务扫描时间间隔

2.3.3 EurekaServerApplication.java

package com.kaikeba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class);
    }
}

2.4 网关服务

2.4.1 pom.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-gateway</artifactId>
        <groupId>com.kaikeba</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway-server</artifactId>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

2.4.2 application.yml

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
          uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          # 路由断言:配置映射路径
          predicates:
            - Path= /user/**

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

2.4.3 GatewayServerApplication.java

package com.kaikeba;

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

@EnableDiscoveryClient // 开启eureka注册发现功能
@SpringBootApplication
public class GatewayServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServerApplication.class);
    }
}

2.5 功能

我们的现在的案例实现的功能就是将地址:http://localhost:9090/ 的所有带有"/user"路径的请求,全部代理到 http://127.0.0.1:10000 这个地址;

2.5 测试结果

user-service自己的查询结果:

spring gateway CORS 允许特定origin_gateway_02


通过网关服务访问的接口:

spring gateway CORS 允许特定origin_spring cloud_03

2.6 面向服务

我们上面的示例中,网关配的代理服务的地址我们是写死的 http://127.0.0.1:10000 ;如果同一个服务有多个实例的话,这样做显然不合理。 比较合理的方式应该是根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由!
所以,我们的配置文件可以改造成如下:

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
          #uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          uri: lb://user-service
          # 路由断言:配置映射路径
          predicates:
            - Path= /user/**

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

其中,uri: lb://user-service的意思是,网关将会使用LoadBalancerClient 把 user-service 通过eureka解析为实际的主机和端口,来进行ribbon负载均衡(动态路由代理);

测试结果,同样是可行的:

spring gateway CORS 允许特定origin_gateway_04

2.7 路由前缀

2.7.1 介绍

当客户端的请求地址与微服务的服务地址不一致的时候,我们可以通过配置路径过滤器来实现路径前缀的添加和去除;

提供服务的地址:http://localhost:10000/user/{id}

  • 添加前缀:就是对请求地址添加前缀路径之后,再作为代理的服务地址;
    如:http://127.0.0.1:10010/8 --> http://127.0.0.1:9091/user/8
    添加前缀路径 /user
  • 去除前缀:将请求地址中路径去除一些前缀路径之后再作为代理的服务地址;如:
  • http://127.0.0.1:10010/api/user/8 --> http://127.0.0.1:9091/user/8
  • 去除前缀路径/api

2.7.2 添加前缀

在网关gateway中通过 - PrefixPath 添加前缀,如下,是添加 /user 前缀;

spring:
	cloud:
		gateway:			
			routes:
				filters:
 				- PrefixPath=/user  # 添加前缀

完整的yml配置:

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
         # uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          uri: lb://user-service
          # 路由断言:配置映射路径
          predicates:
            - Path= /**          
          filters:
            - PrefixPath=/user  # 添加前缀

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

测试结果:

spring gateway CORS 允许特定origin_xml_05


通过 PrefixPath=/xxx 来指定了路由要添加的前缀。 也就是:

  • PrefixPath=/user http://localhost:10010/8 --> http://localhost:9091/user/8
  • PrefixPath=/user/abc http://localhost:10010/8 --> http://localhost:9091/user/abc/8

2.7.3 去除前缀

在网关gateway中通过 - StripPrefix 添加前缀,如下,是添加 /user 前缀;

spring:
	cloud:
		gateway:			
			routes:
				filters:
 				- StripPrefix=1  # 去除前缀:1 表示去除一个前缀,2表示去除两个,以此类推

完整的yml配置:

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
         # uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          uri: lb://user-service
          # 路由断言:配置映射路径
          predicates:
            - Path=/api/user/**
          filters:
          	- StripPrefix=1 # 去除前缀:1 表示去除一个前缀,2表示去除两个,以此类推
           
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

测试结果:

spring gateway CORS 允许特定origin_spring_06

2.8 过滤器

Gateway作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作往往是通过网关提供的过滤器来实现的。前面的 路由前缀 的功能也是使用过滤器实现的。
Gateway常见的过滤器有几十个,常见的有以下几个:

过滤器名称

说明

ddRequestHeader

为匹配到的请求添加Header

AddRequestParameters

为匹配上的请求路由添加参数

AddResponseHeader

为从网关返回的响应添加Header

AddResponseHeader

为对从网关返回的响应添加Header

其他的不列举,详细的说明参考官网

spring gateway CORS 允许特定origin_spring_07

2.8.1 配置默认过滤器

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
         # uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          uri: lb://user-service
          # 路由断言:配置映射路径
          predicates:
            #- Path= /user/**
            #- Path= /**
            - Path=/api/user/**
          filters:
           # - PrefixPath=/user  # 添加前缀
            - StripPrefix=1 # 去除前缀:1 表示去除一个前缀,2表示去除两个,以此类推
            #- MyParam=name

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

再访问 http://localhost:9090/api/user/2 的话;就可以从其响应中查看到如下信息:

spring gateway CORS 允许特定origin_java_08

2.8.2 分类

  • 局部过滤器:通过 spring.cloud.gateway.routes.filters 配置在具体路由下,只作用在当前路由上;如果配置spring.cloud.gateway.default-filters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器 的
    实现上都是要实现GatewayFilterFactory接口。
  • 全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可。

2.8.3 生命周期

Spring Cloud Gateway 的 Filter 的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。“pre”和 “post”分别会在请求被执行前调用和被执行后调用

spring gateway CORS 允许特定origin_java_09

2.8.4 使用场景

  • 请求鉴权:一般 GatewayFilterChain 执行filter方法前,如果发现没有访问权限,直接就返回空。
  • 异常处理:一般 GatewayFilterChain 执行filter方法后,记录异常并返回。
  • 服务调用时长统计: GatewayFilterChain 执行filter方法前后根据时间统计。

2.9 自定义过滤器

需求:将http://localhost:10010/api/user/8?name=lxs中的参数name值获取并返回;
ps:并且参数名是可变的,也就是不一定每次都是name;需要可以通过配置过滤器的时候做到配置参数名。

2.9.1 自定义过滤器

  1. 通过配置文件,配置要返回的值的参数名;
  2. 实现对应配置的过滤器;
  3. 自定义局部过滤器要实现GatewayFilterFactory接口,或继承其子类,并且在配置文件中进行配置;
  4. 全局过滤器要实现GlobalFilter接口,不用配置;

2.9.2 自定义局部过滤器

2.9.2.1 过滤器
package com.kaikeba.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
/**
 * 参数过滤器
 */
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.ConfigParam> {

    public MyParamGatewayFilterFactory() {
        super(ConfigParam.class);
    }

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


    @Override
    public GatewayFilter apply(ConfigParam config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            if(request.getQueryParams().containsKey(config.param)) {
                request.getQueryParams().get(config.param).forEach((str) -> {
                    System.out.printf("------ 局部过滤器获得参数 " + config.param + " = " + str);
                });
            }
            return chain.filter(exchange); // 执行请求,在这个方法之前是前置过虑器,之后是后置过滤器
        });
    }

    /**
    * 读取过滤器配置的参数
    */
   public static class ConfigParam{
       // 获取配置在application.yml配置文件中的过滤器参数
       private String param;

       public String getParam() {
           return param;
       }

       public void setParam(String param) {
           this.param = param;
       }
   }
}
2.9.2.2 过滤器 配置文件

我们看到所有的过滤器都是“过滤器名+GatewayFilterFactory”,配置的时候配置 “- 过滤器名”;所以我们的配置就是 ”- MyParam = 参数名“

server:
  port: 9090

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user-service-route #路由id,随便写
         # uri: http://127.0.0.1:10000 # 代理的微服务地址,真实的微服务地址
          uri: lb://user-service
          # 路由断言:配置映射路径
          predicates:
             - Path=/api/user/**
          filters:
            - StripPrefix=1 # 去除前缀:1 表示去除一个前缀,2表示去除两个,以此类推
            - MyParam=name
      # 默认过滤器,对所有路由都生效
      default-filters:
        - AddResponseHeader=X-Response-Foo, Bar
        - AddResponseHeader=abc-myname,lxs

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka
  instance:
    prefer-ip-address: true

测试结果:

spring gateway CORS 允许特定origin_java_10

2.9.3 自定义全局过滤器

需求:编写全局过滤器,在过滤器中检查请求中是否携带token请求头。如果token请求头存在则放行;如果token
为空或者不存在则设置返回的状态码为:未授权也不再执行下去。

2.9.3.1 过滤器

除了要实现GlobalFilter接口外,还要实现Order接口,这个接口的作用是定义过滤器的优先级,getOrder() 的返回值越小,优先级越高。

package com.kaikeba.filter;

import io.netty.util.internal.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 全局过虑器,
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if(StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); // 直接让过滤器链结束
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 值越小,优先级越高
        return 1;
    }
}
2.9.3.2 测试结果

没有token请求头,返回401错误:

spring gateway CORS 允许特定origin_gateway_11


添加token参数,请求成功:

spring gateway CORS 允许特定origin_gateway_12

2.10 总结和其他

2.10.1 负载均衡

Gateway中默认集成了Ribbon负载均衡和Hystrix熔断机制,而且自动配置有默认值;如熔断时间只有1s;当然,我们也可以显式的手动配置,下面是常用的配置项:
Hystris熔断:

hystrix:
	command:
		default:
			execution:
				isolation:
					thread:
						timeoutInMilliseconds: 6000 #服务降级超时时间,默认1S

Ribbon负载均衡

ribbon:
	ConnectTimeout: 1000 # 连接超时时长
	ReadTimeout: 2000 # 数据通信超时时长
	MaxAutoRetries: 0 # 当前服务器的重试次数
	MaxAutoRetriesNextServer: 0 # 重试多少次服务的所有实例(当前实例也会再试一次)

2.10.2 Gateway跨域配置

  • 跨域:如果发送请求时访问的地址与服务所在的服务器的域名、ip、或端口不一致,就叫做跨域请求;当存在跨域问题时,若不解决,就会是服务出现错误。常见的如前端访问后台服务;现在的前后端分离的开发则必然会涉及到跨域问题。
  • 一般来说,网关都是微服务的统一入口,所以在调用的时候,必然会存在跨域问题;如我们的案例中 http://localhost:9090 跳转访问 http://localhost:20000 其中端口不同,所以也算是跨域请求。
  • 若在访问Spring Cloud Gateway网关服务的时候出现跨域问题,可以在网管服务中通过配置解决,设置课通过的跨域请求:配置如下:
spring:
	cloud:
		globalcors: # 表示进行跨域配置
			corsConfigurations:
			'[/**]':
				#allowedOrigins: * #  * 表示允许全部的访强求通过
				allowedOrigins:
					- "http://docs.spring.io" # 允许来自于这个网址的请求进行跨域请求
				allowedMethods:
					- GET # 允许GET类型的请求通过

我们上面的这段配置的意思是:允许来自 http://docs.spring.io 的 GET 类型的请求通过。
allowedOrigins:指定允许访问的服务器地址,配成 ip:port 也是可以的(如:http://localhost:10000 );

2.10.3 Gateway高可用

  • 启动多个Gateway服务注册到注册中心,形成集群,若是服务内访问Gateway,自动负载均衡;
  • 外部访问是,客户端(PC端、移动端)无法通过Eureka进行负载均衡,此时需要通过其他的服务网关对Gateway进行代理(比如Nginx等)。

2.10.4 与Feign的区别

  • Gateway作文整个应用的流量入口,接受所有的请求(pc、移动端等),并且将不同的请求转发至不同的微服务模块,起作用可以当做一个微服务内部的nginx;
  • Gateway大部分情况下手机up们还自拍鉴权和服务端的流量控制;
  • Feign是将当前微服务的部分服务接口暴露出来,主要用于微服务之间的服务调用。