代碼參考:
Gitee:[https://gitee.com/xn2001/cloudcode/tree/master/07-cloud-gateway](https://gitee.com/xn2001/cloudcode/tree/master/07-cloud-gateway)
GitHub:[https://github.com/lexinhu/cloudcode/tree/master/07-cloud-gateway](https://github.com/lexinhu/cloudcode/tree/master/07-cloud-gateway)
Spring Cloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,該項(xiàng)目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等響應(yīng)式編程和事件流技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡(jiǎn)單有效的統(tǒng)一的 API 路由管理方式。
Gateway 網(wǎng)關(guān)是我們服務(wù)的守門神,所有微服務(wù)的統(tǒng)一入口。
網(wǎng)關(guān)的核心功能特性:
- 請(qǐng)求路由
- 權(quán)限控制
-
限流
權(quán)限控制:網(wǎng)關(guān)作為微服務(wù)入口,需要校驗(yàn)用戶是是否有請(qǐng)求資格,如果沒有則進(jìn)行攔截。
路由和負(fù)載均衡:一切請(qǐng)求都必須先經(jīng)過 gateway,但網(wǎng)關(guān)不處理業(yè)務(wù),而是根據(jù)某種規(guī)則,把請(qǐng)求轉(zhuǎn)發(fā)到某個(gè)微服務(wù),這個(gè)過程叫做路由。當(dāng)然路由的目標(biāo)服務(wù)有多個(gè)時(shí),還需要做負(fù)載均衡。
限流:當(dāng)請(qǐng)求流量過高時(shí),在網(wǎng)關(guān)中按照下流的微服務(wù)能夠接受的速度來放行請(qǐng)求,避免服務(wù)壓力過大。
在 SpringCloud 中網(wǎng)關(guān)的實(shí)現(xiàn)包括兩種:
- gateway
- zuul
Zuul 是基于 Servlet 實(shí)現(xiàn),屬于阻塞式編程。而 Spring Cloud Gateway 則是基于 Spring5 中提供的WebFlux,屬于響應(yīng)式編程的實(shí)現(xiàn),具備更好的性能。
入門使用
1、創(chuàng)建 SpringBoot 工程 gateway,引入網(wǎng)關(guān)依賴
2、編寫啟動(dòng)類
3、編寫基礎(chǔ)配置和路由規(guī)則
4、啟動(dòng)網(wǎng)關(guān)服務(wù)進(jìn)行測(cè)試
<!--網(wǎng)關(guān)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務(wù)發(fā)現(xiàn)依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
創(chuàng)建 application.yml 文件,內(nèi)容如下:
server:
port: 10010 # 網(wǎng)關(guān)端口
spring:
application:
name: gateway # 服務(wù)名稱
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 網(wǎng)關(guān)路由配置
- id: user-service # 路由id,自定義,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://userservice # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱
predicates: # 路由斷言,也就是判斷請(qǐng)求是否符合路由規(guī)則的條件
- Path=/user/** # 這個(gè)是按照路徑匹配,只要以/user/開頭就符合要求
我們將符合Path 規(guī)則的一切請(qǐng)求,都代理到 uri參數(shù)指定的地址。
上面的例子中,我們將 /user/** 開頭的請(qǐng)求,代理到 lb://userservice,其中 lb 是負(fù)載均衡(LoadBalance),根據(jù)服務(wù)名拉取服務(wù)列表,實(shí)現(xiàn)負(fù)載均衡。
重啟網(wǎng)關(guān),訪問 http://localhost:10010/user/1 時(shí),符合 /user/** 規(guī)則,請(qǐng)求轉(zhuǎn)發(fā)到 uri:http://userservice/user/1

多個(gè) predicates 的話,要同時(shí)滿足規(guī)則,下文有例子。
流程圖

路由配置包括:
1、路由id:路由的唯一標(biāo)示
2、路由目標(biāo)(uri):路由的目標(biāo)地址,http代表固定地址,lb代表根據(jù)服務(wù)名負(fù)載均衡
3、路由斷言(predicates):判斷路由的規(guī)則
4、路由過濾器(filters):對(duì)請(qǐng)求或響應(yīng)做處理
斷言工廠
我們?cè)谂渲梦募袑懙臄嘌砸?guī)則只是字符串,這些字符串會(huì)被 Predicate Factory 讀取并處理,轉(zhuǎn)變?yōu)槁酚膳袛嗟臈l件。
例如Path=/user/** 是按照路徑匹配,這個(gè)規(guī)則是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory類來處理的,像這樣的斷言工廠在 Spring Cloud Gateway 還有十幾個(gè)

官方文檔:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
一般的,我們只需要掌握 Path,加上官方文檔的例子,就可以應(yīng)對(duì)各種工作場(chǎng)景了。
predicates:
- Path=/order/**
- After=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] #2031年之后才能訪問
過濾器工廠
GatewayFilter 是網(wǎng)關(guān)中提供的一種過濾器,可以對(duì)進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)返回的響應(yīng)做處理。

Spring提供了31種不同的路由過濾器工廠。
官方文檔:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

下面我們以 AddRequestHeader 為例:

需求:給所有進(jìn)入 userservice 的請(qǐng)求添加一個(gè)請(qǐng)求頭:sign=xn2001.com is eternal
只需要修改 gateway 服務(wù)的 application.yml文件,添加路由過濾即可。
spring:
cloud:
gateway:
routes: # 網(wǎng)關(guān)路由配置
- id: user-service # 路由id,自定義,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址
uri: lb://userservice # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱
predicates: # 路由斷言,也就是判斷請(qǐng)求是否符合路由規(guī)則的條件
- Path=/user/** # 這個(gè)是按照路徑匹配,只要以/user/開頭就符合要求
filters:
- AddRequestHeader=sign, xn2001.com is eternal # 添加請(qǐng)求頭
如何驗(yàn)證,我們修改 userservice 中的一個(gè)接口
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "sign", required = false) String sign) {
log.warn(sign);
return userService.queryById(id);
}
重啟兩個(gè)服務(wù),訪問:http://localhost:10010/user/1
可以看到控制臺(tái)打印出了這個(gè)請(qǐng)求頭

當(dāng)然,Gateway 也是有全局過濾器的,如果要對(duì)所有的路由都生效,則可以將過濾器工廠寫到 default-filters 下:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=sign, xn2001.com is eternal # 添加請(qǐng)求頭
全局過濾器
上面介紹的過濾器工廠,網(wǎng)關(guān)提供了 31 種,但每一種過濾器的作用都是固定的。如果我們希望攔截請(qǐng)求,做自己的業(yè)務(wù)邏輯則沒辦法實(shí)現(xiàn)。
全局過濾器的作用也是處理一切進(jìn)入網(wǎng)關(guān)的請(qǐng)求和微服務(wù)響應(yīng),與 GatewayFilter 的作用一樣。區(qū)別在于 GlobalFilter 的邏輯可以寫代碼來自定義規(guī)則;而 GatewayFilter 通過配置定義,處理邏輯是固定的。
需求:定義全局過濾器,攔截請(qǐng)求,判斷請(qǐng)求的參數(shù)是否滿足下面條件
參數(shù)中是否有 authorization
authorization 參數(shù)值是否為 admin
如果同時(shí)滿足則放行,否則攔截。
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
// 測(cè)試:http://localhost:10010/order/101?authorization=admin
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 獲取第一個(gè) authorization 參數(shù)
String authorization = exchange.getRequest().getQueryParams().getFirst("authorization");
if ("admin".equals(authorization)){
// 放行
return chain.filter(exchange);
}
// 設(shè)置攔截狀態(tài)碼信息
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 設(shè)置攔截
return exchange.getResponse().setComplete();
}
// 設(shè)置過濾器優(yōu)先級(jí),值越低優(yōu)先級(jí)越高
// 也可以使用 @Order 注解
@Override
public int getOrder() {
return 0;
}
}
過濾器順序
請(qǐng)求進(jìn)入網(wǎng)關(guān)會(huì)碰到三類過濾器:DefaultFilter、當(dāng)前路由的過濾器、GlobalFilter;
請(qǐng)求路由后,會(huì)將三者合并到一個(gè)過濾器鏈(集合)中,排序后依次執(zhí)行每個(gè)過濾器.

排序的規(guī)則是什么呢?
- 每一個(gè)過濾器都必須指定一個(gè) int 類型的 order 值,order 值越小,優(yōu)先級(jí)越高,執(zhí)行順序越靠前。
- GlobalFilter 通過實(shí)現(xiàn) Ordered 接口,或者使用 @Order 注解來指定 order 值,由我們自己指定。
- 路由過濾器和 defaultFilter 的 order 由 Spring 指定,默認(rèn)是按照聲明順序從1遞增。
- 當(dāng)過濾器的 order 值一樣時(shí),會(huì)按照 defaultFilter > 路由過濾器 > GlobalFilter 的順序執(zhí)行。
跨域問題
不了解跨域問題的同學(xué)可以百度了解一下;在 Gateway 網(wǎng)關(guān)中解決跨域問題還是比較方便的。
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請(qǐng)求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請(qǐng)求 allowedOrigins: “*” 允許所有網(wǎng)站
- "http://localhost:8090"
allowedMethods: # 允許的跨域ajax的請(qǐng)求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請(qǐng)求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測(cè)的有效期
