傻傻分不清的近义词辨析

提到 Spring 自动生成 API 文档,多数人脑中就浮现出几个单词:Swagger, OpenAPI, Springfox… 所以首先有必要搞清楚他们的关系。

  • OpenAPI 是一个联合组织,负责制定标准的、开放的 API 描述规范。使用 openAPI 规范的 API 描述文件通常可以兼容各种相关软件或系统。
  • Swagger 一开始是一个组织,制定了最流行的 API 描述格式,并将其捐献给 OpenAPI 组织衍化成最初的 OpenAPI 标准。同时提供一些列的周边工具,包括著名的 swagger-ui。后来被 SmartBear 公司收购,又提供了 Swagger Hub 等商业产品。
  • Springfox 是一个适用于 Spring 的库,能够自动化生成 API 描述文件,支持 Swagger2, Swagger3 以及 OpenAPI3 三种格式。
  • Springdoc 是另一个适用于 Spring 的库,能生成 OpenAPI3 格式的 API 描述文件。

总结一下就是 OpenAPI 是一个联盟与规范,SpringfoxSpringdoc 都是这个规范的实现。Swagger 是一个公司,贡献了许多好用的工具并且参与制定了 OpenAPI 规范。

Why Springdoc

显然的,Springfox 近期不是很活跃,光是 Springfox3.x 发布,就距离上一次过去了两年之久。不过这里要为它正名一下,如今 Springfox 通用支持 Swagger3 与 OpenAPI3 规范。 而相比之下,Springdoc 项目非常活跃,文档也更加全面,尽管只支持 OpenAPI3,但鉴于这是最新的标准化规范,所以已经足够了。

此外 Springdoc 还额外支持 Spring WebFlux。

从 Springfox 迁移

迁移过程没有太多技术含量,官方文档 已经写的挺详细了,总共分为三部。

  1. 添加 Springdoc 依赖:implementation("org.springdoc:springdoc-openapi-ui:1.5.9")
  2. 删除 Springfox 的相关依赖。
  3. 替换注解与部分配置。文档中已经列出了非常详细的注解对应方案。

唯一要注意的就是,是的,你没看错,原 @ApiModel, @ApiModelProperty 都替换为 @Schema。里面部分参数名有所不同,建议 @ApiModelProperty(value="xxx") 替换为 @Schema(description="xxx")

对于 Kotlin 有个大坑! 记得额外添加 implementation("org.springdoc:springdoc-openapi-kotlin:1.5.9") 这个依赖,其实文档中也写了的只是不在迁移指南里。否则进入 Swagger-ui 中会发现除了一个外,其他属性都不生效了。

添加默认响应

在 Springfox 中,我们可以用类似下面的代码添加全局的响应结构:

@Bean
fun createApi(): Docket {
    val responses = listOf<Response>(
        ResponseBuilder().code("401").description("Unauthorized (未登录)").build(),
        ResponseBuilder().code("403").description("Forbidden (登录但无权操作)").build()
    )

    return Docket(DocumentationType.OAS_30)
        .useDefaultResponseMessages(false)
        .globalResponses(HttpMethod.GET, responses)
        .globalResponses(HttpMethod.POST, responses)
        .globalResponses(HttpMethod.DELETE, responses)
        // 其他操作
        .build()
}

后起之秀 Springdoc 自然也可以实现,而且更加灵活了。正因为更加灵活了,所以写起来稍微麻烦了一点:

@Bean
fun customizeOperation(): OpenApiCustomiser {
    return OpenApiCustomiser { openApi ->
        // 取得默认响应码的 schema
        val schema = openApi.components.schemas[HttpErrorResp::class.java.simpleName]
        
        openApi.paths.values.forEach { pathItem ->
            pathItem.readOperations().forEach { operation ->
                // 添加默认响应码
                if ("401" !in operation.responses)
                    createApiResp("Unauthorized (未登录)", schema)
                        .also { operation.responses.addApiResponse("401", it) }
                if ("403" !in operation.responses)
                    createApiResp("Forbidden (登录但无权操作)", schema)
                        .also { operation.responses.addApiResponse("403", it) }
            }
        }
    }
}

private fun createApiResp(description: String, schema: Schema<Any>?): ApiResponse {
    val mediaType = io.swagger.v3.oas.models.media.MediaType()
        .schema(schema)
    return ApiResponse().description(description).content(
        Content().addMediaType(MediaType.APPLICATION_JSON_VALUE, mediaType)
    )
}

对于 Springdoc 我们需要构造一个 OpenApiCustomiser 对象,用来对最终生成的 Api 进行修饰。首先我们读取已被识别的 schema 取得所需要的那个响应体。当然,根据工程实际情况,你也可以实例化一个 Schema 对象而不是获取一个现成的。

然后遍历所有的 paths,对于每一项,遍历它所有的 operation,然后依次给它们添加默认响应就可以了。这里进行了额外判断,如果响应码不存在那么再添加,否则会覆盖掉 API 定义的响应。这也看出 Springdoc 的灵活之处,此处可以添加任意逻辑,对 API 进行任意修改。比 Springfox 那种固定 API 的默认强大许多。

配置项

最后,Springdoc 非常人性化地提供了众多配置项,可以直接写在 propertiesyaml 文件中。众所周知 Swagger2 默认的 url 是 .../swagger-ui.html,然鹅 Swagger3 变成了 .../swagger-ui/。Springdoc 默认使用 Swagger2 的格式,让部分小伙伴很是难受(没错,就是我了。那么只需要…

springdoc:
  swagger-ui:
    path: /swagger-ui

搞定! 更多配置项请移步 →官方文档 白白咯~