概述:SpringCloud Gateway 除了路由功能之外与Zuul一样还有过滤器功能,用来对接口调用进行过滤处理。根据过滤器在调用接口的位置分为前置过滤器(pre)和后置过滤器(post)。根据处理的范围又分为全局过滤器(GlobalFilter)和局部过滤器(gatewayFilter)

一、相关概念

生命周期:

PRE(前置): 这种过滤器在请求路由之前被调用,我们可以利用其做统一鉴权,记录调用信息入参等功能。

POST(后置):这种过滤器在请求从微服务端返回后执行,我们用来统计收集调用信息,记录日志等功能,以及修改response中的属性信息。

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_Gateway

作用范围分类:

局部过滤器(GatewayFilter):针对单个的服务路由,可以对访问的url过滤,也可以根据对请求参数进行拦截过滤。SpringCloud Gateway 内部实现了很多不同的局部过滤器,比如上一章节《SpringCloud 微服务网关Gateway 动态路由配置》中使用的 - RewritePath 则使用的为 RewritePathGatewayFilterFactory.class 类来实现其功能。其他的还有:

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_spring_02

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_spring_03

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_spring_04

全局过滤器(GlobalFilter):作用与所有路由信息。用户可以自定义自己的GlobalFilter。全局过滤器通过用来实现统一鉴权,安全校验等功能。使用的场景比较多。SpringCloud内部也有很多已经实现全局过滤器的实现类。

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_System_05

二、使用全局过滤器实现统一鉴权

自定义全局过滤器需要实现GlobalFilter、Ordered 接口。在getOrdered方法中返回过滤器执行的优先级,在filter 方法中编写执行逻辑。

return chain.filter(exchange): 表示继续向后执行。

return exchange.getResponse().setComplete(): 表示拦截停止执行立即请求返回。

如下代码示例根据传参是否包含access-token参数作为是否登录的逻辑进行判断过滤:

package com.xiaohui.gateway.filter;

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

@Component
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * 过滤器实现的业务逻辑。
     * return chain.filter(exchange) 表示继续向下执行
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器-----接口之前");
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        if(StringUtils.isEmpty(token)){//未登录返回
            System.out.println("没有登录....");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_System_06

spring集成流 通道 过滤器 转换器 路由器 切分器 springgateway过滤器_自定义_07

三、局部过滤器实现计时功能

编写局部自定义过滤器com/xiaohui/gateway/filter/ElapsedFilter.java

package com.xiaohui.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class ElapsedFilter implements GatewayFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put("timeBegin", System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute("timeBegin");
                    if (startTime != null) {
                        System.out.println(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
                    }
                })
        );
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

主启动类或其他配置类中注入RouteLocator 实现添加自定义路由信息。

package com.xiaohui.gateway;

import com.xiaohui.gateway.filter.ElapsedFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

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

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/path1/path2/**")
                        .filters(f -> f.stripPrefix(2)
                                .filter(new ElapsedFilter())
                                .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
                        .uri("lb://cloud-payment-service")
                        .order(1)
                        .id("fluent_customer_service")
                ).build();
    }
}

服务提供者接口服务名称:cloud-payment-service

Gateway 访问接口:http://127.0.0.1:8080/path1/path2/payment/get/2?access-token=111

第二部分和第三部分配置文件一样如下:

server:
  port: 8080
spring:
  application:
    name: api-gateway-server #服务名称
  cloud:
    gateway:
      routes:
      #配置路由: 路由id,路由到微服务的uri,断言(判断条件)
        - id: product-service #保持唯一
          #uri: http://127.0.0.1:8001 #目标为服务地址
          uri: lb://cloud-payment-service # lb:// 根据服务名称从注册中心获取请求地址路径
          predicates:
            #- Path=/payment/** #路由条件 path 路由匹配条件
            - Path=/product-service/** #给服务名称前加上一个固定的应用分类路径 将该路径转发到 http://127.0.0.1:8001/payment/get/1
          filters: #配置路由过滤器  http://127.0.0.1:8080/product-service/payment/get/1 -> http://127.0.0.1:8001/payment/get/1
            - RewritePath=/product-service/(?<segment>.*),/$\{segment} #路径重写的过滤器,在yml中$写为 $\
      # 配置自动根据微服务名称进行路由转发 http://127.0.0.1:8080/cloud-payment-service/payment/get/1
      discovery:
        locator:
          enabled: true #开启根据服务名称自动转发
          lower-case-service-id: true #微服务名称已小写形式呈现

#eureka 注册中心
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka1.com:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip进行注册