概述
前面理清了网关的职责与架构位置,这里动手把 mf-gateway 模块搭出来:依赖、引导类、路由配置、跨域处理,四步走完,让客户端请求统一走网关入口。
纲要
- 工程变更清单:新建
mf-gateway模块 + 父工程注册 - 搭建四步
- 创建模块与 pom 依赖(Gateway 是 WebFlux 体系,不能引 starter-web)
- 引导类(零特殊注解的原因)
- 路由配置(
routes数组逐项拆解:id / uri / predicates / StripPrefix) - CORS 跨域配置(reactive
CorsWebFilter与 MVC 的区别) - 验证:改端口、改一级路径,经网关访问 C 端与管理端接口
- 教学版与终态差异:路由托管 Nacos 配置中心
- 踩坑清单与常见误解澄清
工程变更清单
这里只做一件事:新增一个网关模块。改动范围如下:
| 变更项 | 位置 | 说明 |
|---|---|---|
新建模块 mf-gateway | mf/mf-gateway/ | 网关独立工程,不承载业务 |
| 父工程注册 | mf/pom.xml | <modules> 中加入 mf-gateway |
| pom 依赖 | mf-gateway/pom.xml | gateway + nacos-discovery + nacos-config + mf-commons |
| 引导类 | GatewayApplication.java | 普通 @SpringBootApplication |
| 跨域配置 | config/CorsConfig.java | reactive 版 CorsWebFilter |
| 鉴权过滤器 | filters/AuthFilter.java | 这里已就位但不展开,下一篇的主角 |
| 配置文件 | application.yml(教学版) | 端口、注册中心、路由、跨域 |
搭完之后网关模块的结构:
mf
├── pom.xml # 父工程,<modules> 中注册 mf-gateway
├── mf-gateway
│ ├── pom.xml
│ └── src/main
│ ├── java/com/mf/gateway
│ │ ├── GatewayApplication.java # 引导类
│ │ ├── config
│ │ │ └── CorsConfig.java # 跨域过滤器
│ │ └── filters
│ │ └── AuthFilter.java # 统一鉴权(下一篇展开)
│ └── resources
│ └── application.yml # 教学版:本地路由配置
├── mf-app-server # C 端服务,已注册到 Nacos
└── mf-admin # 管理端服务,已注册到 Nacos
父工程 mf/pom.xml 中的注册:
<modules>
<module>mf-autoconfig</module>
<module>mf-model</module>
<module>mf-dubbo</module>
<module>mf-commons</module>
<module>mf-app-server</module>
<module>mf-gateway</module>
</modules>
创建模块与 pom 依赖
在 IDEA 中右键父工程 → New Module,模块名 mf-gateway,然后替换 pom:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mf</artifactId>
<groupId>com.wb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mf-gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 网关核心依赖,底层是 WebFlux + Netty -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 注册中心:lb:// 转发依赖它拉取服务列表 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos 配置中心依赖支持(终态托管路由用,教学版可先注释) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.wb</groupId>
<artifactId>mf-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
四个依赖各司其职:
| 依赖 | 作用 | 缺了会怎样 |
|---|---|---|
spring-cloud-starter-gateway | 路由转发、断言、过滤器全套能力 | 模块就是个空壳 |
nacos-discovery | 把网关注册进 Nacos,并让 lb:// 能拉到服务列表 | 转发时报 Unable to find instance |
nacos-config | 从配置中心拉取路由配置(终态用) | 教学阶段用不到,注释也不影响启动 |
mf-commons | 引入 JwtUtils 等工具,为下一篇统一鉴权做准备 | 鉴权过滤器编译不过 |
这里有个必须醒目提示的坑:不要往网关模块里加 spring-boot-starter-web。Spring Cloud Gateway 构建在 WebFlux(Reactor + Netty)之上,而 spring-boot-starter-web 代表 Servlet(Tomcat)体系,两者同时存在于 classpath 时,Spring Boot 启动会直接报错:
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway.
Please remove spring-boot-starter-web dependency.
这是网关工程最高频的启动冲突。顺手检查一下传递依赖:如果 mf-commons 或其他内部模块传递引入了 starter-web,网关模块要加 exclusion 排掉。业务服务(app-server、admin)都是 Servlet 体系,唯独网关是响应式的,记住这个不对称,很多奇怪问题都能对上号。
引导类
package com.mf.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);
}
}
只有一个 @SpringBootApplication,没有任何网关相关注解,这是很多人第一次写网关时的疑惑点:路由能力从哪来?
答案是自动装配。spring-cloud-starter-gateway 的 jar 里带了 spring.factories(自动配置清单),Spring Boot 启动时扫描到它,就会装配 RouteLocator、RoutePredicateHandlerMapping、GlobalFilter 链这一整套网关基础设施。所以引导类不需要 @EnableXxx 开关,能力随依赖到位自动生效。对比之下,Dubbo 那种要 @EnableDubbo 显式开启的风格,Gateway 把开关做在了依赖里。
注意包名:工程实际用的是 com.mf.gateway(讲义示例里是 com.wb.gateway,属于早期模板残留,以工程为准)。
路由配置
网关的核心工作就一条:请求进来,按规则转发到后端服务。这个工程有两个后端 web 层,C 端 mf-app-server 和管理端 mf-admin,所以需要两条路由。示例版直接写在本地 application.yml 里,改完即可运行:
server:
port: 8888
spring:
application:
name: mf-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.136.160:8848
gateway:
routes:
# 手机端访问
- id: mf-app-server
uri: lb://mf-app-server
predicates:
- Path=/app/**
filters:
- StripPrefix=1
# 管理后台
- id: mf-admin
uri: lb://mf-admin
predicates:
- Path=/admin/**
filters:
- StripPrefix=1
#自定义配置,定义不需要校验token的连接
gateway:
excludedUrls: /user/login,/user/loginVerification,/system/users/verification,/system/users/login
一条路由四要素,逐项拆开看:
| 要素 | 示例值 | 含义 |
|---|---|---|
id | mf-app-server | 路由唯一标识,随意命名,日志里按它区分路由 |
uri | lb://mf-app-server | 转发目标。lb = Load Balance,不是写死 IP:端口,而是从 Nacos 注册中心拉取该服务名的实例列表,做负载均衡后选一台转发。服务扩容、换端口都不用改网关配置 |
predicates | Path=/app/** | 断言:请求路径以 /app/ 开头才命中这条路由 |
filters | StripPrefix=1 | 转发前剥掉第一段路径前缀再发给目标服务 |
StripPrefix=1 最值得单独说。客户端请求网关的是 http://gateway:8888/app/user/login,但 mf-app-server 上真实存在的接口是 /user/login,/app 这一段只是给网关做路由分类用的「转发路径」,后端根本没有它。所以转发前必须把第一段削掉:
客户端 → 网关: GET /app/user/login
StripPrefix=1 削掉 /app
网关 → 后端: GET /user/login (lb 负载均衡选一台 mf-app-server)
| 请求 | 命中路由 | 削前缀后转发到 |
|---|---|---|
/app/user/login | mf-app-server | lb://mf-app-server/user/login |
/app/movements/all | mf-app-server | lb://mf-app-server/movements/all |
/admin/users | mf-admin | lb://mf-admin/users |
整个转发链路:
末尾的 gateway.excludedUrls 是自定义配置,声明「登录、验证码这几个接口不走 token 校验」。它本身不影响路由,是给下一篇的 AuthFilter 读的,@Value("${gateway.excludedUrls}") 会把这一行注进去。
CORS 跨域配置
网关是所有请求的统一入口,跨域处理放这一层最合适,后端服务就不用各自配了。
先明确两套体系的区别:MVC(Servlet)体系的跨域过滤器是 org.springframework.web.cors.CorsFilter,而 Gateway 是 WebFlux(reactive)体系,必须用 org.springframework.web.cors.reactive.CorsWebFilter。从 IDE 自动导包时很容易导成 MVC 那个,运行时不会报「类不存在」那么明显的错,但跨域就是不生效,排查时先看 import。
package com.mf.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* 跨域支持
*/
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*"); // 放行所有请求方式
config.addAllowedOrigin("*"); // 放行所有来源域名
config.addAllowedHeader("*"); // 放行所有请求头
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config); // 对所有路径生效
return new CorsWebFilter(source);
}
}
这里有个二选一的坑:Gateway 其实还支持纯配置方式的跨域(yml 里的 spring.cloud.gateway.globalcors),讲义的教学版 yml 里恰好把这段也拷了进来。globalcors 配置和自定义 CorsWebFilter 只能留一个。两个都上时,同一次预检请求会被处理两遍,响应里出现重复的 Access-Control-Allow-Origin 头,浏览器判定非法而直接报跨域错误,症状恰好是「配了跨域还跨域」。这里推荐保留 Filter 方案(逻辑集中、可控),把 yml 里的 globalcors 段删掉。
教学版完整 yml 若保留 globalcors,长这样(二选一,别和 Filter 共存):
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true # 解决 OPTIONS 预检请求被拦截的问题
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
验证
启动顺序:Nacos → mf-app-server → mf-admin → mf-gateway。打开 Nacos 控制台的服务列表,确认 mf-app-server、mf-admin、mf-gateway 三个服务都在。
改客户端请求地址。之前客户端直连 mf-app-server(比如 http://192.168.0.2:18080),现在统一改走网关:
| 配置项 | 改前(直连) | 改后(走网关) |
|---|---|---|
| 请求地址 | http://ip:18080 | http://ip:8888 |
| 一级路径 | 无(直接接口路径) | /app(或管理端 /admin) |
以客户端登录为例:客户端的请求链接是可配置的「基础地址 + 接口路径」,基础地址配成 http://ip:8888/app,登录时自动拼上接口路径,形成 http://ip:8888/app/user/loginVerification 这样的完整请求。要点只有两条:端口换成 8888,一级路径加上 /app。请求进入网关后命中 Path=/app/**,削掉 /app 转发到 mf-app-server 的 /user/loginVerification,链路正好闭合。漏了这两处任何一处,路由都匹配不上。
用模拟器点一遍登录、获取验证码,功能正常即说明转发通了。管理端同理,Nginx 把页面请求代理到 http://127.0.0.1:8888/admin,经网关转给 mf-admin。
观察路由日志。把 org.springframework.cloud.gateway 的日志级别调到 DEBUG,转发详情一目了然:
logging:
level:
org.springframework.cloud.gateway: debug
启动后请求一次 /app/user/loginVerification,控制台能看到类似输出:
Route matched: mf-app-server
RouteDefinition: id = mf-app-server, uri = lb://mf-app-server
Filter: StripPrefix parts=1
日志里「Route matched」对应断言命中,「StripPrefix parts=1」对应前缀削除,把上面讲的四要素和运行时行为对上号。
顺带验证鉴权伏笔:拿 Postman 不带 token 请求一个非白名单接口。当前工程里 filters/AuthFilter.java 已经就位(GlobalFilter + Ordered),它会校验 Authorization 请求头里的 JWT,校验失败直接返回 401 和 {"errCode":401,"errMessage":"用户未登录"}。这个过滤器的完整逻辑下一篇网关:统一鉴权思路展开,这里只需要知道:路由管「转发到哪」,鉴权管「该不该放行」,两者在网关里是分层的。
示例版与终态的差异
上面按示例版把路由写在本地的 application.yml 里,好处是启动即用,适合理解路由结构。工程终态做了升级:路由连同跨域、excludedUrls 全部托管到 Nacos 配置中心,模块里只留一个精简的 bootstrap.yml 负责引导:
spring:
profiles:
active: dev
application:
name: mf-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.136.160:8848
config:
server-addr: 192.168.136.160:8848
file-extension: yml
启动时 Spring Cloud 按 应用名 + profile + file-extension(即 mf-gateway-dev.yml)去 Nacos 拉配置。为什么多此一举?改路由不用重启网关。新增一条路由、调整一个转发规则,以前要改 yml、重新打包、重启服务;托管到配置中心后,在 Nacos 控制台改完发布即生效。配置中心怎么做到动态刷新,后续Nacos 配置中心一篇专门讲,这里先留个印象。
两版对比:
| 对比项 | 教学版 | 终态 |
|---|---|---|
| 路由配置位置 | 本地 application.yml | Nacos 配置中心 |
| 引导文件 | 不需要 | bootstrap.yml |
| nacos-config 依赖 | 可注释 | 必须引入 |
| 修改路由 | 改文件 + 重启 | 控制台改配置,动态生效 |
踩坑清单
| 坑 | 症状 | 原因与解法 |
|---|---|---|
引入 spring-boot-starter-web | 启动直接失败,提示 MVC 与 Gateway 不兼容 | Gateway 基于 WebFlux,排除 starter-web 及其传递依赖 |
忘配 StripPrefix=1 | 网关转发成功但后端 404 | 后端收到 /app/user/login,而它只有 /user/login;把第一段前缀削掉再转发 |
uri 写成 http://ip:端口 | 能通但失去负载均衡,扩容/迁移要改网关 | 写死单实例;改用 lb://服务名,由注册中心动态解析 |
| CORS 双重配置 | 浏览器报跨域,响应头重复 | globalcors 与自定义 CorsWebFilter 二选一 |
| Nacos 未启动 | 转发报 Unable to find instance for mf-app-server | lb:// 拉不到服务列表,先起 Nacos 再确认服务已注册 |
常见误解澄清
| 误解 | 事实 |
|---|---|
| 网关和业务服务一样是 Web 服务,加 starter-web 没问题 | Gateway 是 WebFlux 响应式栈,加了 starter-web 必然启动冲突 |
引导类上少了 @EnableGateway 之类的注解,路由不会生效 | 网关能力靠 starter 的自动装配,引导类零特殊注解 |
lb:// 是协议前缀,随便写个名字就行 | 后面的名字必须是 Nacos 里注册的服务名,lb 表示从注册中心拉实例做负载均衡 |
Path=/app/** 的 /app 是后端接口的一部分 | 它只是路由分类前缀,StripPrefix=1 会在转发前削掉,后端接口路径里没有它 |
| 跨域配置越多越好,globalcors 和 Filter 都配上更保险 | 双重配置导致响应头重复,浏览器反而判定跨域失败 |
配置文件里的 excludedUrls 是 Gateway 的官方配置项 | 它是自定义配置,只有配合下一篇的 AuthFilter 才有意义 |
官方文档
- Spring Cloud Gateway 官方文档
- Route Predicate Factories(Path 断言)
- RewritePath / StripPrefix GatewayFilter
- Spring Cloud Alibaba Nacos
- CORS 支持(Spring Framework)
总结
- 搭网关四步:建模块配依赖 → 写引导类 → 配路由 → 处理跨域;引导类零特殊注解,能力全部来自 starter 自动装配。
- 路由四要素记牢:
id标识、uri用lb://服务名走负载均衡、predicates的 Path 断言做分类、StripPrefix=1削掉转发前缀。 - 两条路由对应两个后端:
/app/**→mf-app-server,/admin/**→mf-admin,客户端请求统一改走 8888 端口并带上一级路径。 - 跨域用 reactive 的
CorsWebFilter,与 yml 的globalcors二选一。 - 教学版路由在本地 yml,终态托管 Nacos 配置中心实现改路由不重启;工程里已就位的
AuthFilter是下一篇的主角,请求该不该放行,由统一鉴权说了算。
253



被折叠的 条评论
为什么被折叠?



