SPARROW ZOO · 接口文档

springdoc-openapi 集成说明

webmvc-ui 与 webflux-ui 的选择 阻塞 Servlet vs 非阻塞 Netty 配置指引
结论速览 本项目是 Servlet / MVC 栈,必须使用 springdoc-openapi-starter-webmvc-ui,而不是当前的 springdoc-openapi-starter-webflux-ui(版本均为 2.8.8)。 选错会导致 Swagger UI 能打开但拉取不到任何接口数据。

01两个依赖的本质区别

webmvc-ui 与 webflux-ui 并不是「UI 皮肤」的差异, 而是对应 Spring 生态里两套完全不同的 Web 技术栈:

维度 webmvc-ui webflux-ui
目标技术栈 Servlet 栈(阻塞式) Reactive 栈(响应式 / 非阻塞)
底层 Web 框架 spring-boot-starter-web + Tomcat/Jetty spring-boot-starter-webflux + Netty
请求 / 响应类型 jakarta.servlet.*(HttpServletRequest、ModelAndView) org.springframework.web.reactive.*(ServerWebExchange、Mono/Flux)
端点发现方式 扫描 RequestMappingHandlerMapping 上的 @RestController 扫描 RouterFunction Bean 及响应式 @RestController
文档端点注册 OpenApiResource 以 MVC Controller 方式暴露 /v3/api-docs 以 RouterFunction 方式暴露 /v3/api-docs

02核心区别:阻塞 Servlet vs 非阻塞 Netty

这是理解「为什么必须选对」的关键。两个依赖背后是两种并发处理模型,性能特征与编程模型截然不同。

阻塞 · Servlet + Tomcat

线程池模型

  • 每个请求占用一个独立线程(Tomcat 默认约 200 线程)
  • 线程在等待 IO(数据库、远程调用)时会阻塞挂起,无法处理其他请求
  • 并发量受线程池大小限制,线程过多导致内存与上下文切换开销大
  • 编程模型:命令式、顺序执行,简单直观、易调试,生态成熟
非阻塞 · WebFlux + Netty

事件循环模型

  • 基于 Reactor(Mono/Flux)+ Netty 事件循环
  • 少量线程(约等于 CPU 核数)即可承载大量并发连接
  • 等待 IO 时线程不阻塞,通过回调 / 事件驱动继续处理
  • 编程模型:响应式链式,高吞吐低占用,但复杂、难调试,生态相对少
为什么 springdoc 必须匹配 因为两套栈的「端点发现机制」不同:MVC 通过 RequestMappingHandlerMapping 拿 HandlerMethod,WebFlux 通过 RouterFunction 与响应式 Controller 拿路由。 springdoc 内部用不同代码路径去扫描,选错栈就无法发现接口。

03自动装配条件(字节码证据)

两个核心配置类都带有 @ConditionalOnWebApplication 门控,且类型互斥:

SpringDocWebMvcConfiguration   → @ConditionalOnWebApplication(type = SERVLET)
SpringDocWebFluxConfiguration  → @ConditionalOnWebApplication(type = REACTIVE)

也就是说:应用是哪种栈,就只会有对应的那个配置类被激活,另一个直接不生效。

04本项目属于哪一类

证据链非常明确,本项目是 Servlet / MVC 栈:

05用错的后果

因为 SpringDocWebFluxConfiguration 带 type=REACTIVE 条件, 在 Servlet 应用里根本不会被激活:

  1. /v3/api-docs 不会被注册 → Swagger UI 页面能打开(webjar 静态资源在), 但拉不到任何接口数据(空文档或 404)
  2. 自定义的 OpenAPI Bean 仍会注册(它只是元数据,不依赖 webflux), 但没有地方消费它来渲染文档

06依赖选择(正确配置)

把依赖从 webflux-ui 改为 webmvc-ui,版本保持 2.8.8:

<!-- passport-starter/pom.xml 与 bom/pom.xml -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>

07API 配置说明

7.1 官方 springdoc 属性(写在宿主 application.properties)

属性 默认值 说明
springdoc.api-docs.enabled true 是否暴露 /v3/api-docs
springdoc.api-docs.path /v3/api-docs api-docs 端点路径
springdoc.swagger-ui.enabled true 是否开启 Swagger UI
springdoc.swagger-ui.path /swagger-ui.html 访问入口(会 302 到 /swagger-ui/index.html)
springdoc.packages-to-scan 主类所在包 只扫描指定包(隔离时建议显式声明)
springdoc.paths-to-match /** 匹配的路径模式
# 是否暴露 /v3/api-docs
springdoc.api-docs.enabled=true
springdoc.api-docs.path=/v3/api-docs

# 是否开启 Swagger UI
springdoc.swagger-ui.enabled=true
springdoc.swagger-ui.path=/swagger-ui.html

# 只扫描指定包,避免扫到宿主业务包
springdoc.packages-to-scan=com.sparrow.passport
springdoc.paths-to-match=/**

7.2 自定义元信息属性(starter 提供的命名空间)

starter 通过 @ConfigurationProperties(prefix = "sparrow.passport.openapi") 暴露元信息,宿主项目可按需覆盖:

属性 默认值 说明
sparrow.passport.openapi.enabled true 是否注册 starter 提供的 OpenAPI 元信息 Bean
sparrow.passport.openapi.title Sparrow Community 文档标题
sparrow.passport.openapi.description Sparrow Community 文档描述
sparrow.passport.openapi.version 1.0 版本号
sparrow.passport.openapi.contact.name harry 联系人
sparrow.passport.openapi.contact.url http://www.sparrowzoo.com 联系人主页
sparrow.passport.openapi.contact.email zh_harry@163.com 联系人邮箱

7.3 starter 隔离机制

遗留配置提醒 现有 application.properties 中的 knife4j.enable / knife4j.production 是旧 knife4j 的遗留项,在 springdoc 下已失效, 应替换为 springdoc.api-docs.enabled / springdoc.swagger-ui.enabled。

08分组(GroupedOpenApi)—— 按应用隔离接口文档

springdoc 不会自动创建分组,需要通过 GroupedOpenApi Bean 显式声明。 对应关系:knife4j「分组」/ springfox Docket.groupName → springdoc GroupedOpenApi.group()。定义多个分组后, Swagger UI 右上角会出现下拉,按应用切换接口文档。

8.1 分组配置项

属性 默认值 说明
sparrow.passport.openapi.group passport 分组名(下拉中显示)
sparrow.passport.openapi.packages-to-scan com.sparrow.passport 该分组扫描的包,用于隔离

8.2 starter 内的分组 Bean

@Bean
@ConditionalOnMissingBean(name = "passportGroup")   // 按名覆盖,允许多分组共存
@ConditionalOnProperty(prefix = "sparrow.passport.openapi", name = "enabled", ...)
public GroupedOpenApi passportGroup(PassportOpenApiProperties properties) {
    return GroupedOpenApi.builder()
            .group(properties.getGroup())
            .packagesToScan(properties.getPackagesToScan())
            .build();
}

8.3 多应用共存示例

其它应用各定义一个不同 group 的分组,即可在同一下拉中隔离展示:

@Bean
public GroupedOpenApi fileGroup() {
    return GroupedOpenApi.builder()
            .group("file")
            .packagesToScan("com.sparrow.file")
            .build();
}
注意 定义分组后,该分组的 spec 地址变为 /v3/api-docs/{group}(例如 /v3/api-docs/passport),需确保该路径在认证放行列表内 (/v3/api-docs/** 已覆盖)。