Spring Cloud + Spring Boot 3 集成 Knife4j 网关聚合方案
版本要求组件版本说明Spring Boot3.5.4内置 Spring Framework 6.2Spring Cloud2023.0.3内置 Spring Cloud Gateway 4.1Spring Cloud Alibaba2023.0.1.0Nacos 服务发现springdoc-openapi2.8.6WebFlux 版聚合核心Knife4j4.6.0仅用 UI 皮肤不用 Gateway 聚合模块一、后端服务配置每个 MVC 服务独立接入 Knife4j与普通 Spring Boot 3 项目完全一样。1.1 pom.xml!-- Knife4j 文档MVC 版 --dependencygroupIdcom.github.xingfudeshi/groupIdartifactIdknife4j-openapi3-jakarta-spring-boot-starter/artifactIdversion4.6.0/version/dependency该依赖已包含 springdoc-openapi Swagger 注解 Knife4j UI无需额外添加。Knife4j groupId 变更说明Knife4j 4.5.0 起 groupId 从com.github.xiaoymin变为com.github.xingfudeshi。如果看到两个不同的 groupId它们是同一个项目的不同版本不是两个不同的包。版本兼容注意事项Knife4j 4.4.0 及更早版本自带的springdoc-openapi-starter-webmvc-ui版本较旧与 Spring Framework 6.2Spring Boot 3.3不兼容。表现为启动时报NoSuchMethodError或 OpenAPI 规范生成异常。如果你必须使用 Knife4j 4.4.0或更早版本需要排除自带的 springdoc手动指定兼容版本!-- Knife4j 4.4.0旧版groupId 为 xiaoymin --dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-openapi3-jakarta-spring-boot-starter/artifactIdversion4.4.0/versionexclusionsexclusiongroupIdorg.springdoc/groupIdartifactIdspringdoc-openapi-starter-webmvc-ui/artifactId/exclusion/exclusions/dependency!-- 手动指定 springdoc 兼容版本 --dependencygroupIdorg.springdoc/groupIdartifactIdspringdoc-openapi-starter-webmvc-ui/artifactIdversion2.7.0/version/dependency推荐做法直接使用 Knife4j 4.6.0groupIdcom.github.xingfudeshi自带的 springdoc 版本已兼容 Spring 6.2无需额外配置。1.2 分组配置Java Config每个服务创建一个GroupedOpenApiBean设置唯一 group 名称Configurationpublic class OpenApiConfig {Beanpublic OpenAPI openApi() {return new OpenAPI().info(new Info().title(服务名称).version(1.0));}Beanpublic GroupedOpenApi serviceGroup() {return GroupedOpenApi.builder().group(service-group-name) // 唯一标识网关聚合时引用.displayName(服务显示名称).packagesToScan(com.xxx.yyy) // 扫描 Controller 包.build();}}关键点group值在整个系统中唯一网关通过/v3/api-docs/{group}获取该服务的文档。1.3 拦截器放行Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authInterceptor).addPathPatterns(/**).excludePathPatterns(/doc.html,/webjars/**,/v3/api-docs/**,/swagger-resources/**,/error);}1.4 application.ymlspringdoc:swagger-ui:enabled: trueapi-docs:enabled: trueknife4j:enable: true二、网关配置qs-gateway网关是关键。不要用knife4j-gateway-spring-boot-starter它的聚合逻辑有 bug 且会干扰 springdoc 的端点注册。架构思路springdoc 负责聚合端点Knife4j 只管 UI 皮肤职责分离。2.1 pom.xml!-- SpringDoc WebFlux 版 — 核心在网关注册 /v3/api-docs 聚合端点 --dependencygroupIdorg.springdoc/groupIdartifactIdspringdoc-openapi-starter-webflux-ui/artifactIdversion2.8.6/version/dependency!-- Knife4j UI 皮肤 — 仅替换默认 Swagger UI不带聚合逻辑 --dependencygroupIdcom.github.xingfudeshi/groupIdartifactIdknife4j-openapi3-ui/artifactIdversion4.6.0/version/dependency为什么不用knife4j-gateway-spring-boot-starter问题说明group 匹配 bug手动模式下按 group 名查找资源失败报No OpenAPI resource found干扰 springdoc覆盖 springdoc 的RouterFunction注册导致/v3/api-docs/swagger-config被网关路由劫持2.2 application.ymlspring:cloud:gateway:routes:# ═══ API 文档代理路由 ═══# 位置必须在 catch-all 兜底路由之前# StripPrefix1去掉第一段服务前缀/client 或 /draw- id: service-a-api-docsuri: lb://service-apredicates:- Path/service-a/v3/api-docs/**filters:- StripPrefix1- id: service-b-api-docsuri: lb://service-bpredicates:- Path/service-b/v3/api-docs/**filters:- StripPrefix1# ═══ 业务路由 ═══- id: service-a-businessuri: lb://service-apredicates:- Path/client/draw/**, /client/prize/**# 兜底路由排在最后- id: fallbackuri: lb://service-defaultpredicates:- Path/**default-filters:- DedupeResponseHeaderAccess-Control-Allow-Origin Access-Control-Allow-Credentials# SpringDoc 聚合配置springdoc:swagger-ui:urls:- name: 服务A显示名称url: /service-a/v3/api-docs/group-a- name: 服务B显示名称url: /service-b/v3/api-docs/group-b几个容易踩的坑坑1RewritePath 的${}被 Spring 占位符吞掉# 错误写法 — ${remaining} 被 Spring 当作属性占位符解析失败 - RewritePath/service-a(?remaining/?.*), $\{remaining} # 正确写法 — StripPrefix 不涉及正则占位符不受影响 - StripPrefix1坑2API 文档路由必须在兜底路由之前Spring Cloud Gateway 按配置顺序匹配路由第一条匹配的生效。如果把/**兜底路由放在前面文档路径会被错误转发。坑3网关自身的 /v3/api-docs 被转发这是最隐蔽的问题。springdoc 通过RouterFunction在网关注册/v3/api-docs/swagger-config端点RouterFunction优先级ORDER-1高于 Gateway 路由ORDER1。但如果用了错误的依赖如springdoc-openapi-starter-webmvc-ui代替webflux-uiRouterFunction根本没注册请求就落到兜底路由被转发走了。验证方法访问http://gateway:port/v3/api-docs/swagger-config返回的 JSON 中如果出现后端服务的端口号如oauth2RedirectUrl: http://xxx:8085说明被转发了。正常情况应当只返回urls列表。2.3 认证白名单网关中的认证过滤器需要放行文档路径private static final ListString EXCLUDE_PATHS List.of(// 业务免认证路径 .../doc.html,/webjars/**,/v3/api-docs/**,/swagger-resources/**,/favicon.ico,// ⚠️ 带服务前缀的文档代理路径也要加/service-a/v3/api-docs/**,/service-b/v3/api-docs/**);坑4忘记加带服务前缀的路径。/v3/api-docs/**只匹配网关自身的文档端点/service-a/v3/api-docs/**是 Knife4j UI 通过网关代理请求后端文档时用的路径前缀不同必须单独加。三、请求链路浏览器 :8080/doc.html│├─① /v3/api-docs/swagger-config│ → 网关 springdoc RouterFunction 自己处理│ → 返回 JSON: [{name:服务A, url:/service-a/v3/api-docs/group-a}, ...]│├─② /service-a/v3/api-docs/group-a│ → Gateway 路由 StripPrefix1 去掉 /service-a│ → lb://service-a/v3/api-docs/group-a│ → 返回 OpenAPI JSON│├─③ /service-b/v3/api-docs/group-b│ → Gateway 路由 StripPrefix1 去掉 /service-b│ → lb://service-b/v3/api-docs/group-b│ → 返回 OpenAPI JSON│└─④ /webjars/**, /doc.html→ Knife4j UI 皮肤knife4j-openapi3-ui jar 内静态资源六层路径处理层层对照层请求路径处理后浏览器/service-a/v3/api-docs/group-a—Gateway Route 匹配/service-a/v3/api-docs/**命中StripPrefix1去掉/service-a/v3/api-docs/group-alb://转发→lb://service-a/v3/api-docs/group-a—service-a 接收/v3/api-docs/group-a—springdoc 返回groupgroup-a 的 OpenAPI JSON—浏览器拿到服务A 的所有 API 定义渲染到 Knife4j UI四、依赖关系总结网关 (WebFlux)├── springdoc-openapi-starter-webflux-ui ← /v3/api-docs 聚合端点└── knife4j-openapi3-ui ← UI 皮肤/doc.html不做聚合逻辑后端服务 (MVC)└── knife4j-openapi3-jakarta-spring-boot-starter ← 完整 Knife4j├── springdoc-openapi (传递)├── swagger-annotations (传递)└── knife4j UI (传递)一句话knife4j-gateway-spring-boot-starter设计上想做网关聚合UI一体化实际用起来和 springdoc 冲突。正确的做法是依赖分离——springdoc 管聚合knife4j-ui 管皮肤。