目录:

一、统一网关Gateway

1. 为什么需要网关

2. 网关的功能

1. 身份认证和权限校验

2. 服务路由、负载均衡

3. 请求限流

3. 网关的技术实现

4. 总结网关的作用

5. gateway快速入门

6. 搭建网关服务的步骤

一、统一网关Gateway

1. 为什么需要网关

在SpringCloud中,微服务直接摆在那里允许任何人都可以访问,不太安全。因此需要进行身份验证,一切请求先到网关Gateway再到微服务,验证过后再进行放行。但是放行过后,问题又来了,当用户放松请求处理业务时,网关肯定处理不了业务,需要把请求给对应的微服务;但是需要判断是发给order-service还是user-service进行处理?每一个微服务后面肯定有很多实例,所以还需要进行服务路由和负载均衡!允许用户的请求量限量是对微服务的一种保护机制。

2. 网关的功能

2.1 对用户请求做身份认证、权限校验;

2.2 将用户请求路由到微服务,并实现负载均衡;

2.3 对用户请求做限流。

3. 网关的技术实现

在SpringCloud中网关的实现包括两种:1Gateway;2Zuul。SpringCloudGateway是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。Zuul是基于Servlet的实现,属于阻塞式编程。

4. 总结网关的作用

对用户请求做身份认证、权限校验;将用户请求路由到微服务,并实现负载均衡;对用户请求做限流。

5. gateway快速入门

搭建网关服务的步骤:第一步:创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖。这里需要Nacos依赖是因为也要把网关Gateway也注入注册中心Nacos里!

第一步:添加网关依赖```xml

org.springframework.cloud

spring-cloud-starter-gateway

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

```

第二步:服务的启动需要启动类

```java

package cn.itcast.gateway;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);

}

}

```

第三步:application.yml中编写路由规则配置及nacos地址

网关搭建步骤

1. 创建项目,引入nacos服务发现和gateway依赖;

2. 配置application.yml,包括服务基本信息、nacos地址、路由;

3. 断言工厂

以下是重构后的代码:

```yaml

server:

port: 10010 # 服务端口

spring:

application:

name: gateway # 服务名称

cloud: nacos:

server-addr: localhost:8848 # nacos服务地址

gateway: # 服务路由配置

routes: # 表示规则

- id: userservice # 路由标识

# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址

uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称

predicates:

- Path=/user/** # 路径断言,判断是否以/user开头

- id: orderservice

uri: lb://order-service

predicates:

- Path=/order/**

```

网关路由可以配置的内容包括:

1. 路由ID:路由的唯一标识符;

2. URI:路由的目标地址,支持负载均衡(Load Balancer,简称LB)和HTTP两种;

3. Predicates:路由断言,用于判断请求是否符合要求。如果符合要求,则将请求转发到路由目标地址;

4. Filters:路由过滤器,用于处理请求或响应。

路由断言工厂Route Predicate Factory负责解析配置文件中的断言规则字符串,并将其转换为路由判断条件。例如,路径匹配规则“Path=/user/**”是通过org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。在Spring Cloud Gateway中,还有许多其他类似的断言工厂。

Spring提供了11种基本的Predicate工厂,如下所示:

| 名称 | 说明 | 示例 |

| --- | --- | --- |

| After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |

| Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |

| Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |

| Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |

| Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |

| Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org, **.anotherhost.org |

| Method | 请求方式必须是指定方式 | - Method=GET,POST |

| Path | 请求路径必须符合指定规则 | - Path=/red/{segment}, /blue/** |

| Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |

| RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24, 192.168.2.0/24 |

RemoteAddr=192.168.1.1/24

Weight

权重处理

详细的使用规则参考官网:Spring Cloud Gateway。如果此时路由规则不符合,浏览器页面可能会出现404错误!为了增加时间路由规则,您需要给order-service增加在2023年后访问才符合规则的断言条件。predicates中的配置项如下:

```yaml

predicates:

- Path=/order/** # 表示路径为/order开头的请求都符合规则

- After=2031-01-20T17:42:47.789-07:00[America/Denver] # 表明在2023年后访问才符合规则

```

执行结果:

1. PredicateFactory的作用是什么?

PredicateFactory的作用是读取用户定义的断言条件,对请求进行解析并做出判断。

2. Path=/user/**是什么含义?

Path=/user/**表示对路径以/user开头的请求进行解析,认为这些请求符合规则。

3. 过滤器工厂 GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。Spring提供了31种不同的路由过滤器工厂可供选择。例如,您可以使用过滤器工厂为所有进入user-service的请求添加一个名为Truth的请求头,值为"Itcast is freaking awesome!"。需要注意的是,key和value之间是以逗号的方式连接。

验证执行结果:在UserController中使用@RequestHeader注解就可以拿到请求头信息。

重构后的内容如下:

```java

@GetMapping("/{id}")

public User queryById(@PathVariable("id") Long id,

@RequestHeader(value = "Truth", required = false) String truth) {

// 进行打印

System.out.println("Truth: " + truth);

return userService.queryById(id);

}

@Component

public class GlobalFilter implements GatewayFilter, GlobalFilter {

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) throws Exception {

// 获取请求头信息

HttpHeaders headers = exchange.getRequest().getHeaders();

Optional truthHeader = headers.getFirst("Truth");

// 如果请求头中包含Truth字段

if (truthHeader.isPresent()) {

// 获取Truth值

String truthValue = truthHeader.get();

// 进行处理,例如打印等操作

System.out.println("Global Filter: Truth value is " + truthValue);

} else {

// 如果请求头中不包含Truth字段,可以执行其他操作,例如记录日志等

System.out.println("No Truth header found in the request");

}

// 继续执行过滤器链中的下一个过滤器,如果没有下一个过滤器则放行请求并返回空结果

return chain.filter(exchange).then();

}

}

```

在gateway启动类的同包下定义一个名为GlobalFilter的过滤器,我们需要实现以下功能:

1. 判断请求的参数中是否包含authorization字段,且其值为admin;

2. 如果满足条件,则放行请求;否则拦截请求。

首先,我们需要在filter中通过exchange参数获取request对象,然后调用request对象的getQueryParams方法获取到所有的请求参数。接着,从请求参数中通过authorization这个key获取value值admin。如果这个值存在,就调用chain执行链的filter方法,把exchange传下去;如果这个值不存在,就通过exchange参数获取到response对象,通过这个对象的setComplete方法进行拦截。在拦截之前还可以通过通过response方法设置状态码,增加用户的体验感!

为了将这个过滤器纳入Spring的管理,我们需要在过滤器上增加@Component注解组件扫描注解。同时,为了控制过滤器的执行顺序,我们还需要在过滤器上增加@Order注解。

下面是实现这个过滤器的代码:

```java

package com.example.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;

@Component

@Order(-1) // 设置过滤器的执行顺序,负数表示优先级高,越靠前执行

public class GlobalFilter implements GlobalFilter, Ordered {

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

String authorization = exchange.getRequest().getQueryParams().get("authorization");

if ("admin".equals(authorization)) { // 如果authorization值为admin,放行请求

return chain.filter(exchange);

} else { // 否则拦截请求,并设置状态码为401 Unauthorized

exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

return exchange.getResponse().setComplete(); // 完成响应

}

}

@Override

public int getOrder() { // 实现Ordered接口,用于设置过滤器的执行顺序

return -1; // 这里设置为负数,表示优先级高,越靠前执行

}

}

```

```java

package cn.itcast.gateway;

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.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.util.MultiValueMap;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

@Component // @Order(-1)

public class AuthrizeFilter implements GlobalFilter, Ordered {

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

// 第一步:获取所有参数

// 获取request对象

ServerHttpRequest request = exchange.getRequest();

// 获取所有请求参数

MultiValueMap params = request.getQueryParams();

// 第二步:根据authorization参数获取value值

String auth = params.getFirst("authorization");

// 第三步:判断请求参数的值是不是等于admin

if ("admin".equals(auth)){

// 是,放行

return chain.filter(exchange);

} else {

// 不是,拦截

// 结束之前设置状态码

exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

return exchange.getResponse().setComplete();

}

}

// 实现Ordered接口的方法也可以

@Override

public int getOrder() {

return -1;

}

}

```

进行访问时,如果不增加参数,将会出现401错误(未登录错误)。为了解决这个问题,我们需要实现全局过滤器。全局过滤器的作用是对所有路由生效的过滤器,可以自定义处理逻辑,比较灵活。

实现全局过滤器的步骤如下:

1. 实现GlobalFilter接口;

2. 添加@Order注解或实现Ordered接口;

3. 添加组件扫描@Component注解;

4. 编写处理逻辑。

在这三个过滤器中,请求进入网关会碰到三类过滤器:当前路由的过滤器、默认的过滤器、全局过滤器。接下来就分析一下这三个过滤器的执行顺序。

需要注意的是,这三个过滤器不是同一种类型,但是它们可以放到同一个集合当中进行排序。这是因为路由过滤器和默认过滤器默认配置方式相同,它们都是AddRequestHeaderGatewayFilterFactory对象,这个过滤器的工厂会读取配置文件生成一个真正的过滤器GatewayFilter。而在FilteringWebHandler类里面有一个FilteringWebHandler(过滤器适配器)这个类适配器实现了GatewayFilter接口,在适配器内部又接收了一个全局过滤器参数GlobalFiter。通过适配器模式进行传参当做GatewayFilter来使用,这样就建立了联系。因此,这三种过滤器都可以认为是GatewayFilter类型,同一种类型就可以放到List集合当中进行排序。

对于如何进行排序的问题,我们已经知道每一个过滤器都必须指定一个int类型的order值。order值越小,优先级越高,执行顺序越靠前。对于全局过滤器GlobalFilter,可以通过实现Ordered接口或者添加@Order注解来指定order值,由我们自己指定。

. 路由过滤器和默认过滤器的顺序

在Spring中,我们并没有指定路由过滤器和默认过滤器的执行顺序。实际上,它们的执行顺序是由Spring框架自动管理的。默认情况下,它们的执行顺序是按照声明顺序从1递增的。例如:

```java

// 声明顺序为1的默认过滤器

@Bean

public DefaultFilter defaultFilter() {

return new DefaultFilter();

}

// 声明顺序为2的路由过滤器

@Bean

public RouteFilter routeFilter() {

return new RouteFilter();

}

```

当过滤器的order值相同时,会按照 `defaultFilter > 路由过滤器 > GlobalFilter` 的顺序执行。具体实现细节可以参考源码:很清晰,可以自己看一下。

1. `org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()` 方法首先加载 `defaultFilters`,然后再加载某个路由的 `filters`,最后将它们合并。

2. `org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()` 方法会加载全局过滤器,与前面的过滤器合并后根据 `order` 排序,组织过滤器链。

6. 跨域问题处理

在微服务架构中,所有的请求都需要先经过网关,然后才能到达具体的微服务。因此,不需要在每个微服务中都处理跨域问题,只需要在网关中进行处理即可。跨域问题主要包括以下几种情况:

- 域名不同:`www.taobao.com`、`www.taobao.org`、`www.jd.com` 和 `miaosha.jd.com`;

- 域名相同,端口不同:`localhost:8080` 和 `localhost:8081`;

- 浏览器禁止跨域请求;

- 请求的发起者与服务端发生跨域;

- AJAX 请求被浏览器拦截的问题。

解决跨域问题的方案是使用 CORS(Cross-Origin Resource Sharing,浏览器去询问服务器的方式)。通过 axios 发送 GET 请求时,请求地址就是网关的地址。

Document

spring:

cloud:

gateway:

globalcors: # 全局的跨域处理

add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题

corsConfigurations:

'[/**]':

allowedOrigins: # 允许哪些网站的跨域请求

- "http://localhost:8090"

- "http://www.leyou.com"

allowedMethods: # 允许的跨域ajax的请求方式

- "GET"

- "POST"

- "DELETE"

- "PUT"

- "OPTIONS"

allowedHeaders: "*" # 允许在请求中携带的头信息

allowCredentials: true # 是否允许携带cookie

maxAge: 360000 # 这次跨域检测的有效期

请根据以下步骤进行配置以在8090端口上运行并在控制台查看报错请求:

1. 首先,确保您已经安装了所需的软件和依赖项。

2. 打开终端(命令提示符或PowerShell),输入以下命令以启动服务器:

```

your_server_name_here -p 8090

```

请将`your_server_name_here`替换为您的服务器名称。

3. 在另一个终端窗口中,打开浏览器并输入`http://localhost:8090`。这将在控制台中显示报错请求。

4. 根据报错信息,检查您的代码以找出可能的问题。如果您遇到问题,可以在此帖子下提供更多详细信息,以便我们为您提供帮助。

server:

port: 10010 # 服务端口

spring:

application:

name: gateway # 服务名称

cloud:

nacos:

server-addr: localhost:8848 # nacos服务地址

gateway: # 服务路由配置

routes:

- id: userservice # 路由标识

uri: http://127.0.0.1:8081 # 路由的目标地址,http是固定地址

uri: lb://user-service # 路由的目标地址,lb是负载均衡,后面跟服务名称

predicates:

- Path=/user/** # 路径断言,判断是否以/user开头

filters:

# 注意key和value之间是以逗号隔开

- AddRequestHeader=Truth,Itcast is freaking awesome!

# orderservice路由配置

- id: orderservice

uri: lb://order-service # 路由的目标地址,lb是负载均衡,后面跟服务名称

predicates:

- Path=/order/**

- Before=2031-01-20T17:42:47.789-07:00[America/Denver]

# 默认过滤器配置

- AddRequestHeader=Truth,Itcast is freaking awesome! # 注意key和value之间是以逗号隔开

globalcors: # 全局的跨域处理配置

add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(防止CORS浏览器询问服务器拦截)

corsConfigurations:

corsConfigurations: '[/**]': # 拦截所有的请求

allowedOrigins: # 允许哪些网站的跨域请求

- "http://localhost:8090"

- "http://www.leyou.com"

allowedMethods: # 允许的跨域ajax的请求方式

- "GET"

- "POST"

- "DELETE"

- "PUT"

- "OPTIONS"

allowedHeaders: "*" # 允许在请求中携带的头信息

allowCredentials: true # 是否允许携带cookie

maxAge: 360000 # 这次跨域检测的有效期,有效期内直接放行

以下是内容重构后的结果:

要实现跨域访问,需要重启网关并设置相应的端口。首先,打开Nginx的配置文件,找到对应的location块,并在其中添加如下代码:

```

add_header 'Access-Control-Allow-Origin' '*'; #允许任何域名访问该资源

add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; #允许请求方法

add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; #允许请求头

if ($request_method = 'OPTIONS') {

add_header 'Access-Control-Max-Age' 1728000; #预检请求的有效期为20分钟

add_header 'Content-Type' 'text/plain charset=UTF-8'; #响应数据类型

add_header 'Content-Length' 0; #响应数据长度

return 204; #返回204状态码表示成功处理请求(无响应内容)

} elseif ($request_method = 'POST') {

... #如果是POST请求,则进行相关处理

} elseif ($request_method = 'GET') {

... #如果是GET请求,则进行相关处理

} elseif ($request_method = 'OPTIONS') {

... #如果是OPTIONS请求,则进行相关处理

} elseif ($request_method = 'HEAD') {

... #如果是HEAD请求,则进行相关处理

} elseif ($request_method = 'PUT') {

... #如果是PUT请求,则进行相关处理

} elseif ($request_method = 'DELETE') {

... #如果是DELETE请求,则进行相关处理

} elseif ($request_method = 'TRACE') {

... #如果是TRACE请求,则进行相关处理

} elseif ($request_method = 'CONNECT') {

... #如果是CONNECT请求,则进行相关处理

} elseif ($request_method = 'PATCH') {

... #如果是PATCH请求,则进行相关处理

} elseif ($request_method = 'PURGE') {

... #如果是PURGE请求,则进行相关处理

} elseif ($request_method = 'TRACK') {

... #如果是TRACK请求,则进行相关处理

} elseif ($request_method = 'CACHE_CONTROL') {

... #如果是CACHE_CONTROL请求,则进行相关处理

} elseif ($request_method = 'IF_MODIFIED_SINCE') {

... #如果是IF_MODIFIED_SINCE请求,则进行相关处理

} elseif ($request_method = 'IF_NONE_MATCH') {

... #如果是IF_NONE_MATCH请求,则进行相关处理

} elseif ($request_method = 'IF_RANGE') {

... #如果是IF_RANGE请求,则进行相关处理

} elseif ($request_method = 'IF_UNMODIFIED_SINCE') {

... #如果是IF_UNMODIFIED_SINCE请求,则进行相关处理

} elseif ($request_method = 'MAX_AGE') {

... #如果是MAX_AGE请求,则进行相关处理

} elseif ($request_method = 'EXPIRES') {

... #如果是EXPIRES请求,则进行相关处理

} elseif ($request_method = 'LAST_MODIFIED') {

.