前面的内容就不过多的回顾了,可以翻阅一下前面的spring cloud alibaba 完整实现系列,本章我们需要在原有基础上加入gateway网关的使用,以及常见的断言及过滤器设置,至于网关是什么,为什么要使用本章也不会详解,可自行百度,我们暂时还是以搭建为主,文末有源码链接

1.新建一个项目,并加入启动类及yml文件

2.引入gateway的依赖(因为在父pom中引入了spring cloud的版本,所以此次不需要指定具体版本,版本问题可以参考第一篇,注意gateway是属于spring cloud 而不是alibaba

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

3.现在可以直接启动gateway项目了

 报错了,当然下面错误也很明显,因为网关是不需要web环境的,所以不能依赖spring boot,前面我们在父pom中直接引入了spring boot的依赖

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

现在使用gateway就需要把这个依赖放到各个需要的子目录中,删除父pom中的依赖,如下图

 此时启动,完成,那么gateway集成就完成了,简单伐。歪歪歪?就没了?发现这个玩意集成了也没啥用处呀。不着急,我们一步步的来

现在我们来修改yml文件,刚才没有贴yml,我只是在yml中写了当前的端口,以及服务名

 

但是现在网关没办法使用,网关的作用是将客户端发送的请求进行转发到对应服务器,我现在有一个用户和日志模块,那他怎么知道要转发到那一台?

我们先来尝试将服务转发到用户模块(静态路由

server:
  port: 8880 #自定义端口

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service #唯一标识,建议配合服务名
          uri: http://localhost:8881 #匹配后提供的路由地址
          predicates:
            - Path=/user/**  #断言,路径相匹配的进行路由

如果是user/**所有请求,转发到8881下

这个就是网关的简单使用,但是这个会有问题,首先我们是把端口写死的,而且只能匹配user/路径,如果有其他的还得加,我还有日志服务,或者还有其他服务,这个配置不就越写越多了。。。看来生产用这种并不合理

 要在变动的服务中间找到对应的内容,最好的方式就是我们的nacos,服务注册了,我们只需要找到对应的注册服务,端口啥的都不用管了:

  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service #使用nacos本地负载均衡策略
          #断言规则
          predicates:
            - Path=/**
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #本地nacos地址

重启再次访问,也能够搞定,通过nacos中注册的user-service服务名,也能转发到。那么log服务咋搞?现在我们也需要给log服务做一个路由转发,因为我现在是匹配得所有请求,如果后面我还有一个什么服务,两个都有一个userController而且两个路径都是user/怎么办呢?

所以我们的yml还不行

server:
  port: 8880 #自定义端口

spring:
  application:
    name: api-gateway
#  cloud:
#    gateway:
#      routes:
#        - id: user-service #唯一标识,建议配合服务名
#          uri: http://localhost:8881 #匹配后提供的路由地址
#          predicates:
#            - Path=/user/**  #断言,路径相匹配的进行路由
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service #使用nacos本地负载均衡策略
          #断言规则
          predicates:
            - Path=/user-service/**
          filters:
            - StripPrefix=1 #去除上层路径

        - id: log-service
          uri: lb://log-service #使用nacos本地负载均衡策略
          #断言规则
          predicates:
            - Path=/log-service/**
          filters:
            - StripPrefix=1
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #本地nacos地址

那么两个服务各自转发到各自的服务上这样就可以解决刚才的问题,当然还可以直接开启注册中心路由功能,这样下面的id什么的都可以不用配置了,两种都可以,在实际使用时,上面这种还更多一点,毕竟相对灵活,断言规则及过滤器根好控制下面是自动注册中心的(根据需求自行选择):

server:
  port: 8880 #自定义端口

spring:
  application:
    name: api-gateway
#  cloud:
#    gateway:
#      routes:
#        - id: user-service #唯一标识,建议配合服务名
#          uri: http://localhost:8881 #匹配后提供的路由地址
#          predicates:
#            - Path=/user/**  #断言,路径相匹配的进行路由
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启注册中心路由
    #      routes:
#        - id: user-service
#          uri: lb://user-service #使用nacos本地负载均衡策略
#          #断言规则
#          predicates:
#            - Path=/user-service/**
#          filters:
#            - StripPrefix=1 #去除上层路径
#
#        - id: log-service
#          uri: lb://log-service #使用nacos本地负载均衡策略
#          #断言规则
#          predicates:
#            - Path=/log-service/**
#          filters:
#            - StripPrefix=1
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #本地nacos地址

至此,我们集成gateway就完成了,现在我们各个服务的请求都可以从网关进入进行分发。下面我们来看一下关于断言及过滤器

断言

        我们先要明白什么是断言,gateway中断言的作用

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。

断言就是说: 在 什么条件下 才能进行路由转发 gateway有很多内置的断言工厂:

1. 基于请求时间的断言

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间之内

-After=2022-01-24T23:59:59.789+08:00[Asia/Shanghai]

2. ip地址断言

-RemoteAddr=192.168.1.1/24

3. cookie 断言

cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=name, 正则

4. header 断言

是否具有给定名称且值与正则表达式匹配。

-Header=X-Request-Id, \d+

5. host 断言

主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.baidu.com

6. Method 断言

-Method=GET

7. path 断言(上面我们就有使用这种断言方式)

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment} 基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具

有给定名称且值与正则表达式匹配。

-Query=name, 正则   

 8. 权重断言

接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发(同一服务多个配置才会使用)

-Weight= group1, 1

其实自带的断言工厂就够我们使用了,当然也可以自定义断言工厂我这儿写了两个自定义的断言工厂,代码其实是很简单,调试搞清楚数据就明白如何去调整自己想要的效果了

package com.andy.gateway.route;

import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

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

/**
 * 自定义断言规则工厂
 * 实现步骤:
 *      1.创建java类,类名必须以RoutePredicateFactory结尾
 *      2.继承AbstractRoutePredicateFactory 类
 *      3.编写Config内部类,构建断言参数
 *      4.泛型调整为Config
 *      5.重写apply(断言规则逻辑代码) 及 shortcutFieldOrder(断言参数及顺序传入)
 *      6.构造方法调用super传入Config
 *      7.修改yml
 *          #断言规则
 *           predicates:
 *             - name: Custom
 *
 * 这个代码没必要去记,我们还可以找到 AbstractRoutePredicateFactory 的实现类,随便找个复制一下,修改使用也可以
 */
@Component
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return (exchange ->{
            MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
            int sex = Integer.parseInt(queryParams.getFirst("sex"));
            int age = Integer.parseInt(queryParams.getFirst("age"));
            //具体业务判断,false访问失败   true 继续
            if(sex == 0 && age>18 && age<28){
                System.out.println("美女请进");
                return true;
            }
            System.out.println(sex+"---"+config);
            return false;
        });
    }

    /**
     * 传入参数字段,及顺序
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        //参数名称及顺序
        return Arrays.asList("sex","age");
    }

    @Data
    static class Config{
        private int sex;
        private int age;
    }
}
package com.andy.gateway.route;

import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

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

/**
 * 自定义 Gateway 断言 Auth    后面必须为 RoutePredicateFactory
 */
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {

    public static final String AUTHO_KEY = "name";
    public static final String AUTHO_VALUE = "value";
    public AuthRoutePredicateFactory() {
        super(Config.class);
    }

    /**
     * 表示配置填写的顺序,例如:- Auth=zhangsan,xxx, zhangsan 代表 AUTHO_KEY , xxx 代表 AUTHO_VALUE
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(AUTHO_KEY,AUTHO_VALUE);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // 如果 Header 中携带了某个值,进行 header 的判断
        return (exchange -> {
            // 获取请求 header
            HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
            // 获取指定 header
            List<String> headerList = httpHeaders.get(config.getName());
            //header中是否包含zhangsan 参数,值为xxx
//            if(headerList.contains(config.value)){
                return true;
//            }
//            return false;
        });
    }

    /**
     * 获取的是yml里面的值
     */
    @Data
    public static class Config{
        private String name;
        private String value;
    }
}

这上面是两个自定义断言,第一个实现的是请求参数必须带sex 和 age,然后必须是女,18-28才进行路由转发 。第二个是找请求头中必须包含 参数 zhangsan 而且值为xxx进行转发,yml的改动如下:

          predicates:
            - Path=/user-service/**
            - Auth=zhangsan,xxx
#            - name: Custom 启用Custom自定义断言

过滤器

         作用:过滤器就是在请求的传递过程中,对请求和响应做一些手脚

先看内置的(懒得去统计了,贴一个别人写的吧,需要使用的就对照一下):

Spring Cloud Gateway 内置的过滤器工厂_beishuibo1517的博客-CSDN博客本文基于Spring Cloud Greenwich SR2[TOC]内置的过滤器工厂这里简单将Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使用。如下:过滤器工厂作用参数AddRequestHeader为原始请求添加HeaderHeader的名称及值AddRequestParameter为原...https://blog.csdn.net/beishuibo1517/article/details/100963839下面还有一个官网的,自行选择吧:

Spring Cloud Gatewayicon-default.png?t=M0H8https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

实现一个自定义的:

 

package com.andy.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定义一个全局过滤器
 *      实现 globalfilter , ordered接口
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * 执行过滤器中的业务逻辑
     *     对请求参数中的token进行判断
     *      如果存在此参数:代表已经认证成功
     *      如果不存在此参数 : 认证失败.
     *  ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
        //1.获取请求参数token
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        //2.判断是否存在
//        if(token == null) {
//            //3.如果不存在 : 认证失败
//            System.out.println("没有登录");
//            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//            return exchange.getResponse().setComplete(); //请求结束
//        }
        //4.如果存在,继续执行
        return chain.filter(exchange); //继续向下执行
    }

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

我把关键代码注释了,可以做参考,放开后是需要传入token就认为请求成功

其实断言和过滤器有点像,而且有些功能可以说两个都能实现。断言更像是门槛,过滤器则可以在请求中间做一些其他的事情

最后贴一下源码:

链接: https://pan.baidu.com/s/1rMR8OQvAHcNYYqOmnC1naA?pwd=id6h

提取码: id6h

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐