随着微服务多个模块的部署,每个服务所在的服务器可能不同,产生多个地址,为了方便维护这些地址,需要一个网关来统一服务地址,同时也可以通过网关统一认证鉴权。
网关挡在众多微服务前面,做路由转发、监控、限流、鉴权等功能。
SpringCloudGateway
是基于WebFlux
框架实现的,而WebFlux
底层使用了Netty
通信框架。
SpringCloudGateway
核心的概念是路由
、Predicate
断言、Filter
过滤器
SpringCloudGateway
需要使用SpringBoot2.0
以上版本,并且不能再Tomcat
、Jetty
等Servlet
容器中运行,只能是jar
包运行。
复制一个项目,修改后项目包含三个模块,一个网关,一个用户,一个商品服务。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
注意:不要引入
spring-boot-starter-web
依赖,否则会报错
application.yml
配置文件配置网关路由server:
port: 8051
spring:
application:
name: gateway-server
cloud:
gateway:
routes: # 路由,可配置多个
- id: module_one # 路由id,唯一即可,默认是uuid
uri: http://localhost:8041 # 真实服务地址
order: 1 # 路由优先级,数值越小优先级越高,默认为 0
predicates: # 断言
- Path=/moduleone/** # 路径匹配
- id: module_two
uri: http://localhost:8031
order: 1
predicates:
- Path=/moduletwo/**
注意
predicates:- Path=/module8031/**
的Path
是大写开头
如此配置效果:地址以/moduletwo
开头就路由到http://localhost:8031
服务,
以/moduleone
开头就路由到http://localhost:8041
服务。
其实就是把
协议
、ip
、端口
給替换掉,去访问真实的服务
@RestController
@RequestMapping("/moduleone")
public class TestController {
@Value("${server.port}")
private String port;
@RequestMapping("/test")
public String test() {
return "return from module test,服务端口:" + port;
}
}
@RestController
@RequestMapping("/moduletwo")
public class TestController {
@Value("${server.port}")
private String port;
@RequestMapping("/test")
public String test() {
return "return from module test,服务端口:" + port;
}
}
现在网关是能够正常使用了,但是路由配置都写在配置文件上,如果后续要修改或新增的话,需要修改配置文件然后重启项目,这样非常不方便。于是整合Nacos
非常有必要,这样gateway
服务可以直接通过应用名称拉取到服务的地址。
Nacos
上怎么注册服务到nacos
参考:https://blog.csdn.net/qq_31856061/article/details/126420140
注意:应用名不能带
_
,原因在下面
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
@EnableDiscoveryClient
bootstrap.yml
配置文件application.yml
里面的配置剪切到bootstrap.yml
中,再修改一下server:
port: 8051
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: 124.221.89.209:8848 # Nacos 注册中心地址
gateway:
discovery:
locator:
enabled: true # 让网关服务可以发现 nacos 中的服务,自动拉取数据
routes:
- id: moduleOne
uri: lb://moduleOne # 从注册中心拉取 moduleOne 的地址
order: 1
predicates:
- Path=/moduleone/**
- id: moduleTwo
uri: lb://moduleTwo # 从注册中心拉取 moduleTwo 的地址
order: 1
predicates:
- Path=/moduletwo/**
gateway
中配置uri
有三种方式:
- 服务地址:
http://localhost:8031
- websocket:
ws://localhost:8031
- 注册中心:
lb://服务应用名称
注意: 用
lb://服务应用名称
这种方式,服务名不能有_
,不然会识别不了报错
如:Invalid host: lb://module_two
gateway
的正则指定服务名字符只能是a-zA-Z
Predicate
断言就是一个判断逻辑,返回值为布尔类型,如果为真才会往下进行路由。
内置断言工厂是SpringCloudGateway
自带的断言工厂,可以直接通过配置文件配置。
AfterRoutePredicateFactory
-设定日期参数,允许在指定日期时间之后的请求通过。- After=2022-04-15T14:48.421+08:00[Asia/Shanghai]
BeforeRoutePredicateFactory
-设定日期参数,允许在指定日期之前的请求通过- Before=2022-04-15T14:48.421+08:00[Asia/Shanghai]
BetweenRoutePredicateFactory
-设定时间区间,只允许日期在区间内的请求通过- Between=2022-04-15T14:48.421+08:00[Asia/Shanghai],2022-04-20T14:48.421+08:00[Asia/Shanghai]
CookieRoutePredicateFactory
-设定cookie
名称和cookie
值(或正则表达式),判断请求是否含有对应的cookie
且其值是否匹配,匹配才会通过- Cookie=mycookie,mycookievalue
HeaderRoutePredicateFactory
-设定请求头名称和请求头值(或正则表达式),判断请求是否含有对应的请求头且其值是否匹配,匹配才会通过- Header=Phonenum,\d+
HostRoutePredicateFactory
-设定主机名host
,允许符合的请求通过。- Host=**.echoo.com
MethodRoutePredicateFactory
-设定请求方式,允许匹配的请求通过。- Methode=GET
PathRoutePredicateFactory
-设定路径规则,判断请求地址是否满足设定的路径规则,满足通过- Path=/user/*
QueryRoutePredicateFactory
-设定参数名和参数值(或正则表达式),允许符合的请求通过。- Query=name,z.
RemoteAddrRoutePredicateFactory
-设定IP
地址段,判断请求主机地址是否能匹配上。- RemoteAddr=89.220.54.25/24
WeightAddrRoutePredicateFactory
-设定权重分组名称和权重值,同一个分组名的路由根据权重值转发 routes:
- id: moduleOne
uri: lb://moduleOne # 从注册中心拉取
order: 1
predicates:
- Path=/module/** # 服务匹配路径是一样的
- Weight=module_group,7 # 权重分组为 module_group,权重=7
- id: moduleTwo
uri: lb://moduleTwo # 从注册中心拉取
order: 1
predicates:
- Path=/module/** # 服务匹配路径是一样的
- Weight=module_group,3 # 权重分组为 module_group,权重=3
像上面那样配置,断言的路径是一样的都是
/module/**
,通过同一个权重分组module_group
,不同的权重值,使得70%
的请求进入moduleOne
服务,30%
的请求进入moduleTwo
服务
除了内置的断言外,还可以通过代码实现自己的断言逻辑。
id
参数 @RequestMapping("/test")
public String test(@RequestParam Integer id) {
return "return from moduleOne test,服务端口:" + port + ",id:" + id;
}
id
在指定范围内的请求通过import org.apache.commons.lang.ArrayUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/** 自定义断言工厂 */
@Component
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
public static final String[] KEY_ARRAY = {"minId", "maxId"};
/** 构造器 */
public CustomRoutePredicateFactory() {
super(CustomRoutePredicateFactory.Config.class);
}
/** 快捷配置字段 */
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY_ARRAY);
}
/** 断言逻辑 */
@Override
public Predicate<ServerWebExchange> apply(final CustomRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
String id = serverWebExchange.getRequest().getQueryParams().getFirst("id");
if (null != id) {
int idNum = Integer.parseInt(id);
return config.getMinId() < idNum && config.getMaxId() > idNum; // 允许通过
}
return false;
}
@Override
public String toString() { // 重写 toString,不通过时输出自己指定的提示
return String.format("minId:%d,maxId:%d", config.getMinId(), config.getMaxId());
}
};
}
/** 配置类 */
@Validated
public static class Config {
private Integer minId;
private Integer maxId;
public Integer getMinId() {
return minId;
}
public void setMinId(Integer minId) {
this.minId = minId;
}
public Integer getMaxId() {
return maxId;
}
public void setMaxId(Integer maxId) {
this.maxId = maxId;
}
}
}
gateway:
discovery:
locator:
enabled: true # 让 SpringCloudGateway 可以发现 nacos 中的服务
routes:
- id: moduleOne
uri: lb://moduleOne # 从注册中心拉取
order: 1
predicates:
- Path=/moduleone/**
- id: moduleTwo
uri: lb://moduleTwo # 从注册中心拉取
order: 1
predicates:
- Path=/moduletwo/**
- Custom=90,100 # 使用自定义断言,minId=90 maxId=100
- 通过内置断言可以通过规律看到,断言工厂的名称都是
功能名
+RoutePredicateFactory
,而在配置文件里配置断言是只要指定功能名就行,所以这里是- Customer
127.0.0.1:8051/moduletwo/test?id=95
,参数在区间内,允许通过127.0.0.1:8051/moduletwo/test?id=102
,参数不在区间内,请求拒绝SpringCloudGateway
的过滤器可以在请求和响应之间加入一些自定义的逻辑。
其中过滤器又分为全局过滤器和局部过滤器,局部过滤器只能作用于某一个路由上。
过滤器和断言一样除却内置过滤器外,也支持用户自定义过滤器。
SpringCloudGateway
提供的内置局部过滤器
AddRequestHeader
Header
及其值,为原始请求添加Header
,缩略配置如下- AddRequestHeader=Token,5461519844
AddRequestParameter
- AddRequestParameter=name,mary
AddResponseHeader
Header
及其值,为响应添加Header
,缩略配置如下- AddResponseHeader=Token,5461519844
DedupeResponseHeader
Header
及其值,删除响应头中的重复值,缩略配置如下- DedupeResponseHeader=SAMPLE-NAME SAMPLE-AGE
注意:如果指定多个
Header
,用空格隔开。
内置过滤器很多,这里只列出了以上几个。因为配置方法和使用都比较简单,所以需要直接去看文档使用即可。点击打开官方文档
与之前的自定义断言相似,过滤器是配置名
+GatewayFilterFactory
的固定配合。
id
参数范围做一个过滤import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {
public static final String[] KEY_ARRAY = {"minId", "maxId"};
/** 构造器 */
public CustomGatewayFilterFactory() {
super(CustomGatewayFilterFactory.Config.class);
}
/** 快捷配置字段 */
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY_ARRAY);
}
/** 过滤逻辑 */
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String id = exchange.getRequest().getQueryParams().getFirst("id");
if (null != id) {
int idNum = Integer.parseInt(id);
if (config.getMinId() < idNum && config.getMaxId() > idNum) {
return chain.filter(exchange);// 允许通过
}
}
// 条件不满足
byte[] tips = ("拒绝访问id=" + id + "的数据").getBytes(StandardCharsets.UTF_8); // 准备提示信息,并转成字节数组
DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(tips); // 把提示字节数组放入响应的缓冲区
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); // 设置响应码
return exchange.getResponse().writeWith(Flux.just(wrap)); // 返回提示
}
};
}
/** 配置类 */
public static class Config {
private Integer minId;
private Integer maxId;
public Integer getMinId() { return minId; }
public void setMinId(Integer minId) { this.minId = minId; }
public Integer getMaxId() { return maxId; }
public void setMaxId(Integer maxId) { this.maxId = maxId; }
}
}
...
gateway:
discovery:
locator:
enabled: true # 让 SpringCloudGateway 可以发现 nacos 中的服务
routes:
- id: moduleOne
uri: lb://moduleOne # 从注册中心拉取
order: 1
predicates:
- Path=/moduleone/**
filters:
- Custom=10,20