基于Nacos2.0使用Spring Cloud Gateway 2021.0

2,105 阅读4分钟

Spring Cloud Gateway 简介

Spring Cloud Gateway是基于Spring 5, Spring Boot 2, WebFlux和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等。

Spring Cloud Gateway 具有如下特性:

  • 基于Spring Framework 5, Project Reactor, WebFlux 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);

相关概念

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
  • Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
  • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

如何工作

image.png

客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 确定一个请求匹配一个 Route,它就会被发送到 Gateway Web Handler。此处理程序运行通过特定于请求的过滤器链发送请求。过滤器被虚线划分的原因是过滤器可以在代理请求发送之前或之后执行逻辑。执行所有“预”过滤器逻辑,然后发出代理请求。发出代理请求后,执行“post”过滤逻辑。

使用基于 Spring Cloud Alibaba 2021.1

添加依赖

根模块build.gradle中添加相关依赖

plugins {
    id 'java'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'org.springframework.boot' version '2.6.3'
}

ext {
    set('springCloudAlibabaVersion', "2021.1")
    set('springCloudVersion', "2021.0.0")
    set('log4j2Version', "2.17.1")
}

allprojects {
    apply plugin: 'java'

    group 'com.f'
    version '1.0.0'

    // 设置编译和运行的jre兼容版本
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    repositories {
        mavenLocal()
        maven {
            url 'https://repo.huaweicloud.com/repository/maven/'
        }
    }
}

// 应用于所有依赖的子项目
subprojects {
    // 应用插件
    apply plugin: "org.springframework.boot"
    apply plugin: "io.spring.dependency-management"

    dependencyManagement {
        // 导入maven依赖管理的bom
        imports {
            mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
        dependencies {
            dependencySet(group: 'io.jsonwebtoken', version: '0.9.1') {
                entry 'jjwt'
            }
            dependencySet(group: 'org.apache.commons', version: '3.12.0') {
                entry 'commons-lang3'
            }
            dependencySet(group: 'org.mapstruct', version: '1.4.2.Final') {
                entry 'mapstruct'
            }
            dependencySet(group: 'com.baomidou', version: '3.4.3.4') {
                entry 'mybatis-plus-extension'
                entry 'mybatis-plus-boot-starter'
                entry 'mybatis-plus-boot-starter'
            }
            dependencySet(group: 'com.alicp.jetcache', version: '2.6.2') {
                entry 'jetcache-starter-redis-lettuce'
            }
            dependencySet(group: 'com.lmax', version: '3.4.4') {
                entry 'disruptor'
            }
            dependencySet(group: 'com.google.guava', version: '30.1-jre') {
                entry 'guava'
            }
            dependencySet(group: 'commons-io', version: '2.11.0') {
                entry 'commons-io'
            }
            dependencySet(group: 'com.alibaba', version: '1.2.78') {
                entry 'fastjson'
            }
            dependencySet(group: 'com.github.shalousun', version: '2.2.9') {
                entry 'smart-doc-maven-plugin'
            }
            dependencySet(group: 'org.apache.logging.log4j', version: "${log4j2Version}") {
                entry 'log4j-api'
                entry 'log4j-core'
                entry 'log4j-jul'
                entry 'log4j-to-slf4j'
                entry 'log4j-slf4j-impl'
            }
        }
    }

    /**
     * 为所有模块指定公共的依赖
     * 注意这里有些默认不用再指定版本了,已经由上面的dependencyManagement管理了
     */
    dependencies {
        annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
        annotationProcessor "org.projectlombok:lombok"
        compileOnly "org.springframework.boot:spring-boot-configuration-processor"
        compileOnly "org.projectlombok:lombok"
        testAnnotationProcessor "org.projectlombok:lombok"
        testCompileOnly "org.projectlombok:lombok"
        testImplementation "org.springframework.boot:spring-boot-starter-test"
        testImplementation 'org.junit.jupiter:junit-jupiter-api'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
    }

    configurations.all {
        exclude group: 'org.apache.tomcat.embed', module: 'tomcat-embed-websocket'
        exclude group:'org.springframework.boot', module: 'spring-boot-starter-logging'
        exclude module: 'logback-classic'
        exclude module: 'logback-core'
    }

    test {
        useJUnitPlatform()
    }
}

gateway模块build.gradle中添加相关依赖

dependencies {
    implementation(project(':api'))
    implementation(project(':common:common-core'))
    implementation "org.apache.commons:commons-lang3"
    implementation "com.lmax:disruptor"
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    // 解决lb:// 不生效
    implementation 'org.springframework.cloud:spring-cloud-loadbalancer'
    implementation 'org.springframework.boot:spring-boot-starter-log4j2'
    // 解决bootstrap 配置无法读取问题
    implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
    implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery'
    implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config'
    implementation 'io.jsonwebtoken:jjwt'
}

spring boot 2.4 需要添加 spring-cloud-starter-bootstrap 才可以读取bootstrap.yml配置。

需要添加spring-cloud-loadbalancer才能使用lb://xxx转发

添加bootstrap.yml项目配置

spring:
  application:
    # 应用名称
    name: gateway
  profiles:
    # 环境配置
    active: dev
  main:
    allow-circular-references: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    default-property-inclusion: non_null
  cloud:
    nacos:
      config:
        group: gateway
        username: nacos
        password: nacos
        namespace: dev
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yml

Gateway 提供了两种不同的方式用于配置路由,一种是通过yml文件来配置,另一种是通过Java Bean来配置,下面我们分别介绍下。

nacos 配置中心配置(dataId:gateway.yml group: gateway)

server:
  port: 8080
  # 启用压缩
  compression:
    enabled: true
    mime-types: application/json
  http2:
    enabled: true
  # 优雅关机
  shutdown: graceful

# Spring
spring:
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: 127.0.0.1:8848
        namespace: dev
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true

项目启动类GatewayApplication

package com.f;

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

/**
 * 启动类
 *
 * @author liuf
 * @date 2022/1/25 11:37
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
@EnableDiscoveryClient
public class GatewayApplication {

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

过滤器使用 GlobalFilter

package com.f.filter;

import com.f.config.MyGatewayProperties;
import com.f.constant.Constant;
import com.f.utils.GatewayUtils;
import com.f.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 网关鉴权
 *
 * @author liuf
 * @date 2022年1月25日
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    private final MyGatewayProperties gatewayProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        if (gatewayProperties.getNotAuthUris().contains(request.getURI().getPath())) {
            return chain.filter(exchange);
        }
        String token = request.getHeaders().getFirst(Constant.TOKEN);
        if (StringUtils.isEmpty(token)) {
            return GatewayUtils.responseToLogin(exchange.getResponse());
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            return GatewayUtils.responseToLogin(exchange.getResponse());
        }
        String userKey = JwtUtils.getUserKey(claims);
        String userid = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
        if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
            return GatewayUtils.responseToLogin(exchange.getResponse());
        }

        // 设置用户信息到请求
        GatewayUtils.addHeader(mutate, Constant.USER_KEY, userKey);
        GatewayUtils.addHeader(mutate, Constant.USER_ID, userid);
        GatewayUtils.addHeader(mutate, Constant.USER_NAME, username);
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    @Override
    public int getOrder() {
        // 数值越小越先执行 pre
        return -100;
    }
}

过滤器要确定好顺序,实现Ordered接口或加@Order(100)注解。值越小先执行请求过滤,响应过滤的顺序刚好相反。

参考

项目

f-cloud