这篇文章将从gateway的搭建、自动路由匹配、路由数组、跨域和路由过滤器五个方面对gateway项目展开讨论。
1、gateway的搭建
gateway的项目基本的搭建过程与消费者的搭建过程基本一致,细节部分可参考《nacos(四): 创建第一个消费者Conumer(单体)》。
搭建完成后,在pom.xml中引入网关需要用到的依赖项,如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>gateway</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencies>
\<!-- 服务发现 --\>
\<dependency\>
\<groupId\>com.alibaba.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-alibaba-nacos-discovery\</artifactId\>
\</dependency\>
\<!--消费者--\>
\<dependency\>
\<groupId\>org.springframework.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-loadbalancer\</artifactId\>
\<version\>3.1.5\</version\>
\</dependency\>
\<!--⽹关--\>
\<dependency\>
\<groupId\>org.springframework.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-gateway\</artifactId\>
\</dependency\>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.gateway.GatewayApplication</mainClass>
<skip>false</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
注意:在gateway项目中,不能引入spring mvc或者start web类的关于web的依赖,否则会报错"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway."。
另外,gateway的版本也需要进行匹配,在前几篇文章中,我们也一再强调微服务项目对于库的版本要求非常精细。版本稍微不一致可能会报出奇怪的问题,比如"Error processing condition on org.springframework.cloud.gateway.config.GatewayAutoConfiguration.propertiesRouteDefinitionLocator"。
接下来,在application.yml中,开启自动路由匹配:
server:
port: 8087
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
config:
server-addr: 127.0.0.1:8848
file-extension: properties
username: nacos
password: nacos
loadbalancer:
nacos:
enabled: true
gateway:
discovery:
locator:
enabled: true
application:
name: gateway
最后,我们在启动类上添加@EnableDiscoveryClient注解。
@SpringBootApplication
@EnableDiscoveryClientpublic class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
配置完成后,启动gateway,可以看到nacos的管理后台里gateway已经注册成功。

2、路由自动匹配
在文章的第1部分里,application.yml中开启的路的自动匹配功能。
如个例子:
当前在nacos中有一个product1服务,服务提提供了/hello的地址。
通过gateway,我们可以访问http://127.0.0.1:8087/product1/hello,通过gateway访问到product1的hello地址。如下图:

其中:
127.0.0.1:8087:分别是gateway的IP地址和端口号;
product1: 是注册在nacos的里的服务名;
/hello:则是product1服务提供的资源地址。
3、路由数组
在application.yml中配置routes配置节,可开启路由数组,如下
server:
port: 8087
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
config:
server-addr: 127.0.0.1:8848
file-extension: properties
username: nacos
password: nacos
loadbalancer:
nacos:
enabled: true
gateway:
discovery:
locator:
enabled: true
routes: - id: product1_hello uri: lb://product1 # 指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 order: 1 # 路由优先级 数字越低优先级越高 predicates: # 断言 - Path= /p1/\*\* # 当请求路径满⾜Path指定的规则时,才进⾏路由换发 filters: - StripPrefix=1 # 拼接好url之后去掉1层路径也就是p1 - id: order_route uri: lb://server-order order: 1 predicates: - Path=/order/\*\* filters: - StripPrefix=1 - AddRequestHeader=msg,abc
application:
name: gateway
上面的这个配置文件例子中,路由数组里配置了两个路由。各项配置的具体配置的意义,看文件中的注释。其中- StripPrefix=1配置项需要注意。
这一项的意思是,当用户访问http://127.0.0.1:8087/p1/hello,因为/p1/路径触发了第一个路由规则,路由去掉p1这一项,重新拼接为lb://product1/hello去获取访问的结果。
断言proeicates还有如果规则可以使用:

过滤器filters还有如下规则可以使用:

4、跨域
可以通过对application.yml的配置进行跨域项的设置,如下:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些⽹站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求⽅式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
5、路由过滤器
gateway也可以自定义自己的filter,下面用两个例子来说明过如何自定义filter。
示例一:判断请求参数中是否有authorization, authorization参数值是否为admin。如果同时满⾜则放⾏,否则拦截。
@Component
@Order(-1) // 用来控制优先级 数字越小优先级越高
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().get("authorization").get(0);
if (token.equals("admin")){
// 放行
return chain.filter(exchange);
}
// 拦截 禁止访问
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
示例二:判断请求参数中是否有token,如果没有则拦截并报错。
@Component
public class TokenFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest req= exchange.getRequest();
ServerHttpResponse resp= exchange.getResponse();
MultiValueMap<String, String> params= req.getQueryParams();
if(!params.containsKey("token")){
//输出错误信息
Map<String, Object> map = new HashMap<>();
map.put("msg", "Not Logged in!!!!");
map.put("code", 4000);
/*
//3.3作JSON转换
byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8
);
//3.4调用bufferFactory方法,生成DataBuffer对象
DataBuffer buffer = response.bufferFactory().wrap(bytes);
*/
//4.调用Mono中的just方法,返回要写给前端的JSON数据
DataBuffer buffer =resp.bufferFactory().wrap("error".getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
本文关于gateway的内容到这里就结束了,下一篇我们将一起讨论哨兵sentinel的使用: )