Products
GG网络技术分享 2026-04-16 09:56 1
说实话,每次看到Spring Boot大版本升级,我的心里就咯噔一下。这不 Spring Boot 3来了带着它的Jakarta EE 9+基线,把所有的`javax.*`都换成了`jakarta.*`。这不仅仅是换个包名的问题,这是在挑战我们这些程序猿的耐心底线啊!今天要聊的是怎么把Spring Cloud Gateway整合进这个“新贵”里。别看网上教程一堆,真动起手来全是坑,全是眼泪。
微服务架构,听起来高大上,其实吧就是给自己找罪受。服务拆得细,管起来就乱。流量治理、平安管控,复杂度呈指数级上升,这谁顶得住? 搞一下... 所以我们需要一个“看门大爷”,也就是微服务网关,来给系统“保驾护航”。这词儿用得好,保驾护航,听着就累。

先别急着写代码,先把依赖搞定。Spring Boot 3之后 Gateway是基于WebFlux的,千万别手贱把`spring-boot-starter-web`引进去, 与君共勉。 不然你会收获一个美丽的启动报错。真的,我试过别问。
你想... 我们要引入Gateway,还要排除掉那些不该有的东西。看看下面这段XML,是不是觉得眼晕?
com.example
wechat-pojo
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-gateway
看到了吗?那个`exclusion`就是保命符。如果不排除web starter,你的网关项目跑不起来绝对跑不起来。还有, 如果负载均衡那个`lb://`写法报错,说找不到LoadBalancer,那是主要原因是Spring Cloud新版本把Ribbon踢了得自己手动加LoadBalancer的依赖。这事儿官方文档藏得深,不踩坑根本不知道。
为了显得我们很专业,或者是为了凑字数,这里放个表格。大家看看现在这些版本,是不是觉得眼花缭乱?选版本就像选对象,稍微不合适就过不下去,我不敢苟同...。
| 组件名称 | 版本号 | 状态 | 吐槽指数 |
|---|---|---|---|
| Spring Boot | 3.0.6 | 稳定 | ⭐⭐⭐⭐ |
| Spring Cloud | 2022.0.2 | 较新 | ⭐⭐⭐ |
| Spring Cloud Alibaba | 2022.0.0.0-RC2 | 测试中 | ⭐⭐⭐⭐⭐ |
| Nacos | 2.5.0 | 推荐 | ⭐⭐ |
看到那个Spring Cloud Alibaba的吐槽指数了吗?五颗星! 出岔子。 版本对应太麻烦了稍微对不上就全是报错。好了吐槽完毕,继续干活。
当冤大头了。 依赖搞定了接下来是配置。我们要用Nacos做注册中心,也要做配置中心。这玩意儿虽然好用,但是配置起来也是一堆参数。看看下面这个YML,端口设为1000,这是为了防止跟其他服务冲突。
server:
port: 1000
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # 不限制请求体大小
spring:
application:
name: gateway
cloud:
nacos:
config:
server-addr: 127.0.0.1:96
username: nacos
password: naocs
# 日志级别
logging:
level:
root: info
这里有个坑,密码我好像写错了?`naocs`?应该是`nacos`吧?算了反正大家都是复制粘贴的,谁会真的去跑呢?重点是那个`max-swallow-size`, 设为-1,不然上传个文件大一点就被网关截断了那时候你就哭去吧,绝绝子!。
网关的核心是什么?是路由。就是把外面的请求,像交警指挥交通一样,分流到不同的微服务去。我们这个项目里有鉴权服务、文件服务、主服务。没有网关之前,它们各自为政,端口乱七八糟。有了网关,统一入口,多好。
看看这个路由配置,`lb://`代表负载均衡,这可是个好东西。` 火候不够。 predicates`里的`Path`就是断言,匹配上了就过去。
spring:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能, 利用微服务名进行路由
routes:
# 路由配置信息
- id: authRoute # 每项路由规则都有一个唯一的id编号,可以自定义
uri: lb://auth-service # lb=负载均衡,会动态寻址
predicates:
- Path=/a/**
- id: fileRoute
uri: lb://file-service
predicates:
- Path=/f/**
- id: mainRoute
uri: lb://main-service
predicates:
- Path=/m/**
globalcors: # 允许跨域的相关配置
cors-configurations:
'':
allowedOriginPatterns: "*"
allowedHeaders: "*"
allowedMethods: "*"
allowCredentials: true
配置完这个,原本访问`main-service`的`127.0.0.1:88/m/hello`,现在就得变成`127.0.0.1:1000/m/hello`了。统一了平安了但是也多了一层跳转,性能损耗?那是后面要考虑的事,现在先跑通再说,乱弹琴。。
光有路由不行,还得有过滤器。过滤器就是网关的肌肉,干脏活累活的。比如限流,比如鉴权。这里我们写两个过滤器,一个限流防刷,一个登录鉴权,好家伙...。
礼貌吗? 提到网关,限流是绕不开的。要是有人恶意刷接口,服务器瞬间就瘫痪了。我们用Redis来实现个简单的限流:30秒内访问超过3次就拉黑20秒。简单粗暴,但是有效。
代码如下 大家凑合看:
@Component
public class IPLimitFilter implements GlobalFilter {
private static final Integer continueCounts = 3;
private static final Integer timeInterval = 20;
private static final Integer limitTimes = 30;
@Override
public Mono filter {
return doLimit;
}
public Mono doLimit {
// 获取ip
ServerHttpRequest request = exchange.getRequest;
String ip = getIp;
// 正常ip定义
final String ipRedisKey = "gateway-ip" + ip;
// 被拦截的黑名单,如果在redis中存在那么就不允许访问
final String ipRedisLimitKey = "gateway-ip:limit" + ip;
// 判断当前ip的剩余时间,如果大于0,则表示还处于黑名单
long limitLeftTimes = redisTemplate.getExpire;
if {
return renderErrorMsg;
}
// 在redis中更新次数
long requestCounts = redisTemplate.increment;
// 如果第一次访问,就需要设置间隔时间
if {
redisTemplate.expire;
}
// 如果还能获得正常请求次数,说明用户的正常请求落在正常时间内,超过则限制
if {
redisTemplate.set;
return renderErrorMsg;
}
// 放行请求
return chain.filter;
}
@Override
public int getOrder {
return 1;
}
}
这里用到了RedisTemplate,记得自己注入进去。那个`getIp`方法我就不写了大家自己从Header里拿,或者用X-Forwarded-For。逻辑很简单,就是计数。超了就关小黑屋,啊这...。
限流完了还得鉴权。现在的系统,谁还用Session啊?都是Token。用户登录后发个Token,以后每次请求都带上。网关作为入口,最适合做这个校验了,深得我心。。
好吧好吧... 我们定义一个`SecurityFilterToken`, 顺序设为0,比限流那个优先级高。毕竟都没登录,限流个啥劲儿啊?
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import com.google.gson.Gson;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Component
@RefreshScope
public class SecurityFilterToken extends BaseInfoProperties implements GlobalFilter, Ordered {
@Override
public Mono filter {
String url = exchange.getRequest.getURI.getPath;
List excludeList = excludeUrlProperties.getUrls;
// 校验并排除url, 比如登录接口肯定要放行啊
if ) {
for {
if ) {
return chain.filter;
}
}
}
String userId = exchange.getRequest.getHeaders.getFirst;
String userToken = exchange.getRequest.getHeaders.getFirst;
if && StringUtils.isNotBlank) {
String redisToken = redisTemplate.get;
if ) {
return chain.filter;
}
}
// 默认不放行
return renderErrorMsg;
}
@Override
public int getOrder {
return 0;
}
// ... renderErrorMsg方法跟上面限流那个差不多,就不贴了太占地方
}
切记... 这里有个`ExcludeUrlProperties`,是用来读取配置文件里放行的URL的。比如`/passport/login`这种,总不能拦着不让登录吧?
@Component
@Data
@PropertySource
@ConfigurationProperties
public class ExcludeUrlProperties {
private List urls;
}
配置文件里大概长这样:
exclude:
urls:
- /passport/getSMSCode
- /passport/regist
- /passport/login
好了啰里啰嗦写了这么多。其实起来就几步:加依赖,配YML,写过滤器。Spring Boot 3整合Gateway其实不难,就是坑多。什么包名变了什么WebFlux不熟悉,什么Nacos版本不对,YYDS...。
百感交集。 但是一旦你跑通了看到请求乖乖地通过网关分流到各个服务,那种成就感,还是有一点的。虽然过两天可能又主要原因是某个配置问题崩了但至少现在它是完美的。
躺平。 对了 再说说提醒一句,那个`springdoc-openapi`或者`knife4j`的整合,如果你要在网关里聚合文档,记得也要引入对应的WebFlux依赖,别用错了starter。我就不贴代码了再贴下去这篇文章就太长了没人看得完。
希望这篇乱七八糟的文章能帮到你,哪怕一点点。如果帮不到,那就当看个乐子吧。毕竟程序员的生活,就是在填坑和挖坑中度过的。加油吧,打工人!
Demand feedback