Spring Cloud Alibaba 路由网关(Gateway)

路由网关统一访问接口

什么是 Spring Cloud Gateway

Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式。Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

Spring Cloud Alibaba 路由网关(Gateway)_第1张图片

Spring Cloud Gateway 功能特征

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 动态路由
  • Predicates 和 Filters 作用于特定路由
  • 集成 Hystrix 断路器
  • 集成 Spring Cloud DiscoveryClient
  • 易于编写的 Predicates 和 Filters
  • 限流
  • 路径重写

Spring Cloud Gateway 工程流程

Spring Cloud Alibaba 路由网关(Gateway)_第2张图片

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。

pom

com.wsl hello-spring-cloud-alibaba-dependencies 1.0.0-SNAPSHOT 4.0.0 iot-gateway iot-gateway 网关

    
    
        org.springframework.boot
        spring-boot-starter
    
    
        org.springframework.boot
        spring-boot-starter-actuator
    
    
    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-nacos-discovery
    
    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-sentinel
    
    
        org.springframework.cloud
        spring-cloud-starter-openfeign
    
    
    
        org.springframework.cloud
        spring-cloud-loadbalancer
        3.0.2
    
    
        org.springframework.cloud
        spring-cloud-starter-gateway
    
    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-nacos-config
    
    

    
    
        javax.servlet
        javax.servlet-api
    
    



    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
    

主要增加了 org.springframework.cloud:spring-cloud-starter-gateway 依赖

Application

package com.huitu.iot;

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

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class IotGatewayApplication {

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

}

application.yml

spring:
application:
# 应用名称
name: iot-gateway
cloud:
# 使用 Naoos 作为服务注册发现
nacos:
discovery:
#nacos注册地址
server-addr: localhost:8180
#注册服务的ip,默认是内网ip,当只需要内网访问时可以注释掉
ip: localhost
#命名空间
namespace: 5709f890-96d1-42ed-b2ca-sdafewqasdf
# 使用 Sentinel 作为熔断器
sentinel:
transport:
port: 18721
dashboard: localhost:18080
# 路由网关配置
gateway:
# 设置与服务注册发现组件结合,这样可以采用服务名的路由策略
discovery:
locator:
enabled: true
# 配置路由规则
routes:
# 采用自定义路由 ID(有固定用法,不同的 id 有不同的功能,详见:https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters)
- id: IOT-CONSUMER
# 采用 LoadBalanceClient 方式请求,以 lb:// 开头,后面的是注册在 Nacos 上的服务名
uri: lb://iot-consumer
# Predicate 翻译过来是“谓词”的意思,必须,主要作用是匹配用户的请求,有很多种用法
predicates:
# Method 方法谓词,这里是匹配 GET 和 POST 请求
- Method=GET,POST
server:
port: 18087

目前无效

feign:
sentinel:
enabled: true

目前无效

management:
endpoints:
web:
exposure:
include: “*”

配置日志级别,方便调试

logging:
level:
org.springframework.cloud.gateway: debug

依次运行 Nacos 服务:iot-provider、iot-consumer、iot-gateway

打开浏览器访问:http://localhost:18087/iot-consumer/test/hi浏览器显示

Hello Nacos Discovery Hi Feign i am from port 18085

注意:请求方式是http://路由网关IP:路由网关Port/服务名/

至此说明 Spring Cloud Gateway 的路由功能配置成功

Spring Cloud Gateway 支持两种不同的用法:

编码式

properties、yml 配置

编码

项目创建成功后,直接配置一个 RouteLocator 这样一个 Bean,就可以实现请求转发。

@Component
public class GatewayController {
//编码式转发
@Bean
RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().
route(“gongjie”,j -> j.path("/get").uri(“http://httpbin.org”))
.build();
}
}

这里只需要提供 RouteLocator 这个 Bean,就可以实现请求转发。配置完成后,重启项目,访问:http://localhost:18087/get

配置

properties

spring.cloud.gateway.routes[0].id=gongjie
spring.cloud.gateway.routes[0].uri=http://httpbin.org
spring.cloud.gateway.routes[0].predicates[0]=Path=/get

yml

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Path=/get

访问:http://localhost:18087/get实现同样的效果

Predicate

route的组成部分:

id:路由的ID

uri:匹配路由的转发地址

predicates:配置该路由的断言,通过PredicateDefinition类进行接收配置。

order:路由的优先级,数字越小,优先级越高。

spring cloud gateway 通过谓词(Predicate)来匹配来自用户的请求

  • After 通过时间匹配:

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- After=2022-03-17T01:01:01+08:00[Asia/Shanghai]

表示,请求时间在 2022-03-17T01:01:01+08:00[Asia/Shanghai] 时间之后,才会被路由。

除了 After 之外,还有两个关键字:

Before,表示在某个时间点之前进行请求转发

Between,表示在两个时间点之间,两个时间点用 , 隔开

  • Method 通过请求方式匹配:

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Method=GET

这个配置表示只给 GET 请求进行路由

  • Path 通过请求路径匹配:(重点)

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Path=/get

表示路径满足 get这个规则,都会被进行转发到http://httpbin.org/get

比如:http://localhost:18087/get

  • Query 通过参数进行匹配:

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Query=name

表示请求中一定要有 name 参数才会进行转发,否则不会进行转发。

也可以指定参数和参数的值。

例如参数的 key 为 name,value 必须要以 java 开始

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Query=name,java.*

  • Header 通过请求头匹配

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Header=requestId

表示请求头中一定要有 requestId 参数才会进行转发,否则不会进行转发。

  • RemoteAddr 通过ip地址匹配

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- RemoteAddr=47.105.198.54/24

  • host 通过host匹配

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Host=**.gongjie.top

  • Cookie 通过Cookie匹配

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Cookie=CookieName, .*jie.*

这里判断cookie的 CookieName是否包含"jie" 是的话就路由到对应的服务上去

  • 组合使用

    当它们同时存在于同一个路由时,请求必须同时满足所有的谓词条件才被这个路由匹配。

    注意:一个请求满足多个路由的谓词条件时,请求只会被首个成功匹配的路由转发

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: http://httpbin.org
predicates:
- Query=name,java.*
- Method=GET
- After=2021-01-01T01:01:01+08:00[Asia/Shanghai]

Filter

Spring Cloud Gateway 中的过滤器分为两大类:

  • GatewayFilter:应用到单个路由或者一个分组的路由上。

  • GlobalFilter:应用到所有的路由上。

    正在上传…重新上传取消

AddRequestParameter 过滤器使用:

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: lb://APPUSER
filters:
- AddRequestParameter=name,gong
predicates:
- Cookie=CookieName, .*jie.*

自动带上了参数name并且值为gong。

如果你有多个服务需要进行转发,配置多个routes规则就行

spring:
cloud:
gateway:
routes:
- id: gongjie
uri: lb://APPUSER
filters:
- AddRequestParameter=name,gong
predicates:
- Cookie=CookieName, .*jie.*
- id: yuanj
uri: lb://APPPAY
filters:
- AddRequestParameter=name,Pay
predicates:
- Method=GET

一个转发到APPUSER,一个转发到APPPAY。

自定义GatewayFilter

/**
* 统计某个或者某种路由的处理时长
*/
@Component
public class customGatewayFilter implements GatewayFilter, Ordered {
private static final Logger log = LoggerFactory
.getLogger( customGatewayFilter.class );
private static final String COUNT_START_TIME = “countStartTime”;

@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //得到当前时间
    Instant now = Instant.now();
    //Instant.now()使用的是UTC时间,会与北京时间相差八小时
    now.plusMillis(TimeUnit.HOURS.toMillis(8));
    //now.toEpochMilli()  毫秒数
    exchange.getAttributes().put(COUNT\_START\_TIME, now.toEpochMilli());
    return chain.filter(exchange).then(
            Mono.fromRunnable(()->{
                long startTime = exchange.getAttribute(COUNT\_START\_TIME);
                long endTime = (Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8)).toEpochMilli() - startTime);
                log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms");
            })
    );
}

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

}

上述代码中,getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。需要将自定义的GatewayFilter 注册到router中,代码如下:

@Bean
RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes()
.route(“customGongj”,
j -> j.path("/get").filters(f -> f.filter(new CustomGatewayFilter())
.addRequestHeader(“auth”,“gongjie”)).uri(“http://httpbin.org”)
)
.build();
}

访问http://localhost:18087/get可看到效果,控制台有日志输出

2022-03-17 15:00:28.691 INFO 10364 — [ctor-http-nio-4] com.gongj.gateway.CustomGatewayFilter : /get: 28801395ms

自定义过滤器工厂

自定义GatewayFilter又有两种实现方式,一种是上面的直接实现GatewayFilter接口,另一种是自定义过滤器工厂(继承AbstractGatewayFilterFactory类) , 选择自定义过滤器工厂的方式,可以在配置文件中配置过滤器了。

@Component
public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory {
private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilterFactory.class );
private static final String COUNT_START_TIME = “countStartTime”;
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (!config.isEnabled()) {
return chain.filter(exchange);
}
exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(COUNT_START_TIME);
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": “)
.append(System.currentTimeMillis() - startTime)
.append(“ms”);
sb.append(” params:").append(exchange.getRequest().getQueryParams());
log.info(sb.toString());
}
})
);
};
}

@Override
public List shortcutFieldOrder() {
    return Arrays.asList("enabled");
}

public CustomerGatewayFilterFactory() {
    super(Config.class);
    log.info("Loaded GatewayFilterFactory \[CustomerGatewayFilterFactory\]");
}
public static class Config {
    /\*\*
     \* 控制是否开启统计
     \*/
    private boolean enabled;

    public Config() {}

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}

}

application.yml 中 网关路由配置如下:

spring:
cloud:
gateway:
routes:
- id: customer_route
uri: http://httpbin.org
filters:
- Customer=true
- AddRequestHeader=auth,gongjie123
predicates:
- Method=GET

访问http://localhost:18087/get可看到headers里面自动加入了"Auth": “gongjie123”, 控制台打印:

2022-03-17 15:25:59.705 INFO 12836 — [ctor-http-nio-3] c.g.g.CustomerGatewayFilterFactory : /get: 437ms params:{}

Global filter

Spring Cloud Gateway框架内置的GlobalFilter如下:

Spring Cloud Alibaba 路由网关(Gateway)_第3张图片

内置的 GlobalFilter 能够满足大多数的需求了,但是如果遇到特殊情况,内置满足不了我们的需求,还可以自定义GlobalFilter。

自定义GlobalFilter

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。

/**
* 鉴权过滤器
*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst(“token”);

    if (token == null || token.isEmpty()) {
        ServerHttpResponse response = exchange.getResponse();

        // 封装错误信息
        Map responseData = Maps.newHashMap();
        responseData.put("code", 401);
        responseData.put("message", "非法请求");
        responseData.put("cause", "Token is empty");

        try {
            // 将信息转换为 JSON
            ObjectMapper objectMapper = new ObjectMapper();
            byte\[\] data = objectMapper.writeValueAsBytes(responseData);

            // 输出错误信息到页面
            DataBuffer buffer = response.bufferFactory().wrap(data);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
    return chain.filter(exchange);
}

/\*\*
 \* 设置过滤器的执行顺序
 \*
 \* @return
 \*/
@Override
public int getOrder() {
    return Ordered.LOWEST\_PRECEDENCE;
}

}

访问http://localhost:18087/get将返回

{“code”:401,“cause”:“Token is empty”,“message”:“非法请求”}

加上token可正常访问http://localhost:18087/get?token=1

你可能感兴趣的:(java,java,后端)