文章目录
前言
如果你还没有学习OpenFeign可以看之前的博客微服务之配置中心Nacos,本篇博客是接着上一篇写的。
网关简介
概念

来看上面就是我们的微服务项目,上面的项目有问题
- 当前端调用我们的接口的时候端口号需要一直修改,ip也可能一直修改有点乱。
- 登录拦截:我们上面三个模块都需要登录拦截。
- 跨域问题需要对每一个服务进行改造
- 如果添加鉴权功能,需要对每一个服务进行改造
为了实现统一管理,就提出了网关的概念。
网关就是当前微服务项目的"统一入口 "
网关有哪些
网关有ZUUL
和GateWay
- Zuul :是 Netflix 公司开源的一款核心组件。
Spring Cloud 集成了 Netflix 的开源组件,创建了 Spring Cloud Netflix 子项目。其中 spring-cloud-starter-netflix-zuul使得开发者可以非常方便地在 Spring Boot 应用中使用 Zuul 来构建网关 - Spring Cloud Gateway: Spring 官方推出的网关项目。我们今天使用这个演示。
之前的SpringCloud没有网关,我们使用的是ZUUL,现在SpringCloud有了网关就是GateWay,
现在主推GateWay
网关能做什么
- 反向代理
- 鉴权
- 流量控制
- 熔断:之前学的
hystrix
做熔断降级针对的服务和服务之间,这个是网关和服务之间做熔断降级。 - 日志监控

核心概念
- Router(路由)
路由时构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由 - Predicate(断言)
断言说简单点,就是请求匹配条件。断言是定义匹配条件,如果请求符合条件,则该请求匹配断言所属的路由 - Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
Gateway搭建
创建cloud-gateway-8080模块

修改cloud-gateway-8080文件
修改pom文件
注意网关使用webflux依赖不能使用springweb依赖,二者不兼容。
整体的配置文件如下,看着复制不要全部复制走。
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.hsh</groupId>
<artifactId>cloud-gateway-8080</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-gateway-8080</name>
<description>cloud-gateway-8080</description>
<!-- 子级打包方式 -->
<packaging>jar</packaging>
<!-- 引用父级 -->
<parent>
<groupId>com.hsh</groupId>
<artifactId>cloud-alibaba-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<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>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencies>
<!--
注意网关使用webflux依赖不能使用springweb依赖,二者不兼容。
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- 引入nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引用gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 引用负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 父级有test可以不用引用-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
<!-- <scope>test</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.hsh.cloudgateway8080.CloudGateway8080Application</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
整理模块文件

修改后如下
修改配置文件
将application.properties修改为application.yml,然后修改内容
yml
server:
port: 8080
spring:
# 数据库配置 这里之所以使用数据库配置 是因为gateway的pom文件引用了父级,而父级又引用了common,common引用了mybatis-plus ,所以需要配置数据库,这冗余操作
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
application:
name: cloud-gateway-8080 # 服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册服务地址
password: nacos
username: nacos
gateway: # 网关配置 和nacos是同一级不要写错位置
discovery:
locator: # 注意:新版本无法使用,权限不足,需要配置自定义路由。
enabled: true #表明gateway开启服务注册和发现的功能,ateway为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
lower-case-service-id: true #请求路径的服务名改为小写
routes: # 自定义路由 和gateway下的discovery是同一级不要写错位置
- id: bill-consumer-7790 # 路由id,不能重复 ,一般写服务名,因为服务名是唯一的
uri: lb://bill-consumer-7790 #服务名 lb为负载均衡 也可以使用http:IP+服务端口 此时就需要通过127.0.0.1:8080/bill-consumer-7790/bill/find访问
predicates:
- Path=/ts/** # 路径需要携带/ts 即需要通过127.0.0.1:8080/bill-consumer-7790/ts/bill/find访问
filters:
- StripPrefix=1 # 路由去掉前缀信息 使用内置过滤器 这个是将路径的bill-consumer-7790去掉,即通过127.0.0.1:8080/ts/bill/find访问即可
# - id: bill-consumer-7791 这就是配置多个
# uri: lb://bill-consumer-7791
# predicates:
# - Path=/ts/**
注意yml格式 空格 关闭gateway开启服务注册和发现的功能 否则自定义不生效
routes 后面的路由可以配置多个,相当于配置个数组,一个-开头的配置就是其中的一个数组元素
路由主要有四个配置:
- 路由id(id)
- 路由目标(uri)
- 路由断言(predicates):判断路由的规则,
- 路由过滤器(filters):对请求或响应做处理
访问路径:ip+gateway端口号+路由断言+路径
修改父级pom文件
父级引用子集模块
xml
<!-- 引用子级模块 -->
<modules>
<!-- ..... -->
<!-- 引入cloud-gateway-8080 -->
<module>cloud-gateway-8080</module>
</modules>
<dependencies>
<!-- ...... -->
<!--
注释掉spring-boot-starter-web
因为上面说了cloud-gateway-8080子模块引入了网关,
而网网关使用webflux依赖不能使用springweb依赖,二者不兼容。
-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!--</dependency>-->
</dependencies>
除了网关和common的其他模块都引入web

引入后记得刷新。
之所以common不引入,是因为gateway的pom文件引用了父级,而父级又引用了common,common引用了springweb,这还会导致有冲突
测试
启动nacos
启动bill-consumer-7790
启动bill-provider-7780 记得把这个数据库连接密码改成你的账号密码
启动cloud-gateway-8080
输入网址访问路径:ip+gateway端口号+路由断言+路径
即http://localhost:8080/ts/bill/find?id=1
结果如下
路由过滤器

客户端请求先找到路由,路由匹配时经过过滤器层层筛选,最终访问到微服务。
配置默认局部过滤器
请求头过滤器配置示例(局部过滤器)语法如下:
yml
filters: # 过滤器配置
- AddRequestHeader=headerMsg,hsh # 添加请求头
修改cloud-gateway-8080的配置文件
yml
server:
port: 8080
spring:
# 数据库配置 这里之所以使用数据库配置 是因为gateway的pom文件引用了父级,而父级又引用了common,common引用了mybatis-plus ,所以需要配置数据库,这冗余操作
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
application:
name: cloud-gateway-8080 # 服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册服务地址
password: nacos
username: nacos
gateway: # 网关配置 和nacos是同一级不要写错位置
discovery:
locator: # 注意:新版本无法使用,权限不足,需要配置自定义路由。
enabled: true #表明gateway开启服务注册和发现的功能,ateway为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
lower-case-service-id: true #请求路径的服务名改为小写
routes: # 自定义路由 和gateway下的discovery是同一级不要写错位置
- id: bill-consumer-7790 # 路由id,不能重复 ,一般写服务名,因为服务名是唯一的
uri: lb://bill-consumer-7790 #服务名 lb为负载均衡 也可以使用http:IP+服务端口 此时就需要通过127.0.0.1:8080/bill-consumer-7790/bill/find访问
predicates:
- Path=/ts/** # 路径需要携带/ts 即需要通过127.0.0.1:8080/bill-consumer-7790/ts/bill/find访问
filters:
- StripPrefix=1 # 路由去掉前缀信息 使用内置过滤器 这个是将路径的bill-consumer-7790去掉,即通过127.0.0.1:8080/ts/bill/find访问即可
- AddRequestHeader=headerMsg,hsh # 添加请求头
修改bill-consumer-7790的BillController
BillController
文件在com/hsh/billconsumer7790/controller
下
java
package com.hsh.billconsumer7790.controller;
import com.hsh.billconsumer7790.service.api.BillService;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("bill")
public class BillController {
@Resource
private BillService billService;
@RequestMapping("/find")
public Object find(@RequestHeader String headerMsg, @RequestParam("id")Integer id){
// 使用@RequestHeader读取响应头
System.out.println("headerMsg:"+headerMsg);
return billService.findBillListById(id);
}
}
测试
- 启动nacos
- 启动bill-provider-7780
- 启动bill-consumer-7790
- 启动cloud-gateway-8080
- 访问
http://localhost:8080/ts/bill/find?id=1
- 查看idea控制台
默认过滤器配置示例(全局过滤器)
default-filters 的配置和routes平级。
只要配置在 default-filters 下面的过滤器,会对routes配置的所有路由都生效。
yml
default-filters: # 配置全局过滤器
- AddRequestHeader=globalMsg,hsh # 添加请求头
修改cloud-gateway-8080的配置文件
yml
server:
port: 8080
spring:
# 数据库配置 这里之所以使用数据库配置 是因为gateway的pom文件引用了父级,而父级又引用了common,common引用了mybatis-plus ,所以需要配置数据库,这冗余操作
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
application:
name: cloud-gateway-8080 # 服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册服务地址
password: nacos
username: nacos
gateway: # 网关配置 和nacos是同一级不要写错位置
discovery:
locator: # 注意:新版本无法使用,权限不足,需要配置自定义路由。
enabled: true #表明gateway开启服务注册和发现的功能,ateway为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
lower-case-service-id: true #请求路径的服务名改为小写
routes: # 自定义路由 和gateway下的discovery是同一级不要写错位置
- id: bill-consumer-7790 # 路由id,不能重复 ,一般写服务名,因为服务名是唯一的
uri: lb://bill-consumer-7790 #服务名 lb为负载均衡 也可以使用http:IP+服务端口 此时就需要通过127.0.0.1:8080/bill-consumer-7790/bill/find访问
predicates:
- Path=/ts/** # 路径需要携带/ts 即需要通过127.0.0.1:8080/bill-consumer-7790/ts/bill/find访问
filters:
- StripPrefix=1 # 路由去掉前缀信息 使用内置过滤器 这个是将路径的bill-consumer-7790去掉,即通过127.0.0.1:8080/ts/bill/find访问即可
- AddRequestHeader=headerMsg ,hsh # 添加响应头
default-filters: # 配置全局过滤器
- AddRequestHeader=globalMsg,hsh # 添加请求头
# - id: bill-consumer-7791 这就是配置多个
# uri: lb://bill-consumer-7791
# predicates:
# - Path=/ts/**
修改bill-consumer-7790的BillController
BillController
文件在com/hsh/billconsumer7790/controller
下
java
package com.hsh.billconsumer7790.controller;
import com.hsh.billconsumer7790.service.api.BillService;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("bill")
public class BillController {
@Resource
private BillService billService;
@RequestMapping("/find")
public Object find(@RequestHeader String headerMsg,@RequestHeader String globalMsg, @RequestParam("id")Integer id){
System.out.println("headerMsg:"+headerMsg);
System.out.println("globalMsg:"+globalMsg);
return billService.findBillListById(id);
}
}
测试
- 启动nacos
- 启动bill-provider-7780
- 启动bill-consumer-7790
- 启动cloud-gateway-8080
- 访问
http://localhost:8080/ts/bill/find?id=1
- 查看idea控制台
自定义全局路由过滤器
有时候SpringCloudGateWay提供的过滤器工厂不能满足自己的要求。
可能有时候需要在过滤时做一些其它的逻辑操作
。
那么这时候可以选择使用java代码自定义全局过滤器。
实现有两种方式
- 创建GateWayFilter类 实现接口GlobalFilter和Ordered:
- 这个有缺陷,网关模块无法获取配置的默认过滤器的值。
- 过滤链的顺序通过写重写
Ordered
接口的getOrder
的值决定了过滤器的执行顺序。数值越大优先级越低, 负的越多, 优先级越高。
- 创建GateWayFilter类 实现接口GlobalFilter+Order注解:
- 能获取网关模块配置的默认过滤器的值。
- 过滤链的顺序通过写@Order(值)的大小决定了过滤器的执行顺序。数值越大优先级越低, 负的越多, 优先级越高。
我们演示这一种
在cloud-gateway-8080模块新建GateWayFilter
我们在com/hsh/cloudgateway8080
文件下新建filter文件夹
在filter文件夹下新建一个叫做GateWayFilter的类
java
package com.hsh.cloudgateway8080.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author xrkhy
* @date 2025/10/18 20:32
* @description
*/
@Order(1) // 设置过滤器执行顺序
@Component
public class GateWayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进入网关过滤器");
// 1.获取请求参数
//1.这里的request并不是servlet中的request
System.out.println("请求路径:"+exchange.getRequest().getPath());
System.out.println("请求方法:"+exchange.getRequest().getMethod());
System.out.println("请求参数:"+exchange.getRequest().getQueryParams());
System.out.println("循环打印请求头信息"+exchange.getRequest().getHeaders());
// 打印请求头的 headerMsg
System.out.println("请求头的默认局部过滤器headerMsg:"+exchange.getRequest().getHeaders().getFirst("headerMsg"));
// 打印请求头的 globalMsg
System.out.println("请求头的默认全局过滤器globalMsg:"+exchange.getRequest().getHeaders().getFirst("globalMsg"));
// 打印前端传过来的token
System.out.println("请求头中的token:"+exchange.getRequest().getHeaders().getFirst("token"));
//2.返回值是一个多键的map集合、也就是说这个map集合的键可以重复
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
// 2.获取http://localhost:8080XXX?id=1的id参数
String id = queryParams.getFirst("id");
System.out.println("id========="+id);
if("3".equals(id)){
System.out.println("id为3的请求进入");
return chain.filter(exchange);// 放行
}
// 4.拦截
// 4.1.禁止访问,设置状态码
// 修改状态码 返回没有权限 HttpStatus.FORBIDDEN=403 是个枚举类
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
测试
- 启动nacos
- 启动bill-provider-7780
- 启动bill-consumer-7790
- 启动cloud-gateway-8080
- 打开请求工具
postman
或者apifox
或者apipost
,我使用的是apifox
,输入访问路径http://localhost:8080/ts/bill/find?id=3
并输入token的值
- 再次输入访问路径
http://localhost:8080/ts/bill/find?id=1
并输入token的值
网关的跨域问题
使用CORS方式。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
方式一:配置application.yml文件(不推荐)
yml
spring:
cloud:
gateway:
globalcors: # 全局的跨域配置
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
# options请求 就是一种询问服务器是否浏览器可以跨域的请求
# 如果每次跨域都有询问服务器是否浏览器可以跨域对性能也是损耗
# 可以配置本次跨域检测的有效期maxAge
# 在maxAge设置的时间范围内,不去询问,统统允许跨域
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 允许在请求中携带cookie
maxAge: 360000 # 本次跨域检测的有效期(单位毫秒)
# 有效期内,跨域请求不会一直发option请求去增大服务器压力
方式二:使用编码方式定义配置类(推荐)
java
package com.hsh.cloudgateway8080.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Configuration
public class CorsConfig {
private static final String MAX_AGE = "18000L";
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
// 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
// 获取请求头
ServerHttpResponse response = ctx.getResponse();
// 获取响应对象
HttpMethod requestMethod =
requestHeaders.getAccessControlRequestMethod(); // 获取请求方式对象
HttpHeaders headers = response.getHeaders();
// 获取响应头
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
requestHeaders.getOrigin()); // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins)
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
requestMethod.name()); // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods)
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
// 允许在请求中携带cookie(相当于yml中的allowCredentials)
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
// 允许在请求中携带的头信息(相当于yml中的allowedHeaders)
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
// 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge)
if (request.getMethod() == HttpMethod.OPTIONS) {
// 直接给option请求反回结果
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
// 不是option请求则放行
};
}
}