从 SpringFox 迁移到 SpringDoc

前言

一个好用的文档工具对于程序员开发、联调会提升很大效率。今天要介绍的就是一款我感觉很不错的文档工具 SpringDoc,去年在上家公司就已经从 SpringFox 迁移,使用 SpringDoc 作为接口文档,感觉还是很不错的,但据我了解目前似乎并不普及。现公司使用的还是 SpringFox ,其实严格说来也只是引入了该组件,并未充分发挥它的作用。于是我决定正好更换一整套 swagger 环境,并且给同事推广一下 SpringDoc ......

SpringDoc 简介

SpringDoc 是继 SpringFox 之后一款优秀的 swagger 整合工具,首先它是新出来的(我这个人对新出来的技术有莫名的好感~~),其次相对于 SpringFox 而言,SpringDoc 先支持 OpenAPI3、也支持 JSR303 规范、OAuth2、Spring WebFlux 等,下面让我们一起感受 SpringDoc 。当然任何一个新技术都推荐从官网学习,毕竟文章的内容大多来源于官网 SpringDoc 官网

从 SpringFox 迁移

如果要从 SpringFox 升级为 SpringDoc,很简单只有三个步骤,

引入 SpringDoc 依赖

首先删掉 SpringFox 依赖,引入 SpringDoc 依赖, 因为我很早就给公司换了 SpringDoc 只是文章最近才写,所以还是选用我当时的版本 1.5.10

 <dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-ui</artifactId>
   <version>1.5.10</version>
</dependency> 

编写配置

只要注入 OpenAPIBean 即可

@Bean
public OpenAPI openApi( @Value("${spring.application.name}") String applicationName,ObjectProvider<BuildProperties> buildProperties) {
    OpenAPI openAPI = new OpenAPI();
    // 基本信息
    openAPI.info(new Info().title(applicationName)
            .description("服务名称")
            .version(Optional.ofNullable(buildProperties.getIfAvailable()).map(BuildProperties::getVersion).orElse("1.0.0")));
    return openAPI;
}

注解更换

SpringDoc 稍微改动了相关注解,示例 SpringFox - SpringDoc 对应关系如下

  • @Api@Tag
  • @ApiIgnore@Parameter(hidden = true) or @Operation(hidden = true) or @Hidden
  • @ApiImplicitParam@Parameter
  • @ApiImplicitParams@Parameters
  • @ApiModel@Schema
  • @ApiModelProperty(hidden = true)@Schema(accessMode = READ_ONLY)
  • @ApiModelProperty@Schema
  • @ApiOperation(value = "foo", notes = "bar")@Operation(summary = "foo", description = "bar")
  • @ApiParam@Parameter
  • @ApiResponse(code = 404, message = "foo")@ApiResponse(responseCode = "404", description = "foo")

SpringDoc 初体验

进行了上面简单的迁移步骤,即可访问 http://localhost:8080/swagger-ui.html 文档页面,大致是这样

至此一个简单的swagger文档就做好了,SpringDoc提供了一系列的配置,包括是否可用,swagger-ui页面路径地址等等,在application.yml中输入springdoc查看自动提示

更多属性可参考官网 SpringDoc PropertiesSwagger Properties

自定义 Server 列表

默认情况下,Server 地址是获取的当前机器应用的 IP+Port,但是现如今除开发环境外我们服务几乎都是在 Docker 容器中,这样一来自动获取的时候可能会拿到容器内部的 IP 或者 宿主机 IP,此时使用 swagger 页面访问接口可能会发生 CORS 跨域错误。所以我们可以在向 Spring 容器注入 OpenAPI 时自己设置 server 列表

openAPI.servers(List.of(new Server().url("https://dev.gateway.yinsantech.cn/xx").description("xxx")));

在请求中添加授权

绝大多数情况下我们项目都是需要认证的,比较常见的一种是在 HttpHeader 中传 token ,那么想在 swagger 页面添加 header 我们可以在向 Spring 注入 OpenAPI 时,配置使用 swagger 接口要携带哪些 header

@Bean
public OpenAPI openApi(@Value("${spring.application.name}") String applicationName, ObjectProvider<BuildProperties> buildProperties) {
    OpenAPI openAPI = new OpenAPI();
    //添加header
    Map<String,SecurityScheme> map = new HashMap<>();
    map.put("x-auth-token",new SecurityScheme().type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name("x-auth-token"));
    openAPI.components(new Components().securitySchemes(map));
    map.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));

    // 基本信息
    openAPI.info(new Info().title(applicationName)
            .description("服务名称")
            .version(Optional.ofNullable(buildProperties.getIfAvailable()).map(BuildProperties::getVersion).orElse("1.0.0")));
    return openAPI;
}

配置完之后页面上右上角会出现一个Authorize按钮,点击之后会出现输入header的弹框

我们可以发现 SecurityScheme.In.HEADER枚举点进去之后还有 COOKIE,QUERY 也就是指定携带的 cookie 和查询参数。并不仅仅只能配置添加 header

但是仔细一想上述的硬编码显然不合适,因为我们不同的项目的 header 个数,名称都可能不一样,所以我们可以将它们配到配置文件中。首先自定义一个接受配置文件属性的类

@Component
@Data
@ConfigurationProperties(prefix = SwaggerProperties.PREFIX)
public class SwaggerProperties {
  public static final String PREFIX = "swagger";

  private Map<String, SecurityScheme> securitySchemes;
  private List<Server> servers;
}

然后在 application.yml中配置

swagger:
  securitySchemes:
    x-user-id:
      type: APIKEY  #类型
      in: HEADER    #放 header 里面
      name: X-USER-ID # header - key
  servers:
    - url: http://127.0.0.1:8011/hive-collection-admin #服务器 URL
      description: local

针对不同的项目,配置不同的 header 个数,headerKey、server 地址等。

页面类替换显示

使用 swagger 之后,接口参数类型或者响应类都会被解析显示到页面上成为 schema,但是有时候我们使用框架里面的类,有些属性并不希望他们显示出来,典型的就是自定义分页解析器的时候,参考我这篇文章 SpringMVC 参数解析器 和 Spring 类型转换 此时SpringDoc给我们提供了一个功能是类型替换,把我们不希望显示的类替换成我们希望显示的类。

SpringDocUtils.getConfig().replaceWithClass(Page.class, PageRequest.class);

值得注意的是 swagger 页面 Page 类型被替换成 PageRequest,所以分页接口返回值要使用 IPage 类型,不能用 Page,否则 swagger 页面显示也会被替换。

JSR 303 规范支持

如果对 JSR 303 规范还不清楚的可以参考我这篇文章 参数校验神器 hibernate-validator 配合统一异常处理 当我们使用 JSR 规范注解校验参数的时候,例如

@Data
public class MessageSendRequest {
  @Schema(description = "要发送的用户id和手机号")
  @Size(min = 2)
  private List<SmsUserSendInfo> userList;

  @Schema(description = "产品 AP/CN")
  @NotNull
  private ProductEnum product;
}

观察swagger页面上的schema

看到*(必填) 、minItemmininum(最小值) 的提示了吧,再看我们点击Try It Out按钮后

swagger 给我们提供的默认请求体就已经列出了 List 元素最小个数 2example 最小值 10,爱了爱了~~

Multiple Acceptable Schemas

咦,为什么这一栏的标题是英文呢?emmm 因为不知道该怎么描述,就把源码里面的关键注释搬过来了。。。

我举个例子,有些时候我们使用多态的思想开发,比如我现在接手的催收项目有 AP 的催收数据,CN 的催收数据,然后现在有一个催收详情接口,那 APCN 的催收详情页面显示的数据字段并不是完全一致的,但是它们又有一些公共字段。

当然我们可以选择定义两个 Dto 类,定义两个接口分别开发,但是这样不是很二吗。。所以嘛,我们可以定义一个父类存放公共字段,两个子类去继承扩展私有字段。

然后接口上定义一个类型区分一下两个产品的案件即可

@GetMapping("/{product}/{id}/detail")
public CaseDetailResponse detail(
    @PathVariable("product") ProductEnum product, @PathVariable("id") Long id) {
  return caseReviewerService.detail(product, id);
}

接口返回值用父类兼容,看似很完美,但是查看swagger页面会发现,schema上只有父类中的公共字段。。那么前端的小伙伴就没法知道AP/CN两个案件私有的字段是哪些了。这时候就要说到Multiple Acceptable Schemas。我们可以将两个schema都显示在页面上,通过以下配置

@Operation(
    summary = "催收案件详情",
    responses = {
      @ApiResponse(
          content = {
            @Content(
                schema = @Schema(oneOf = {ApCaseDetailResponse.class, CnCaseDetailResponse.class}))
          })
    })

查看swagger页面会发现两个schema都列出来了

网关聚合

在现如今流行 SpringCloud 微服务的天下,通常我们一个项目可能会有很多微服务,以电商为例,订单、支付、商品、搜索、物流等等,如果每个微服务都是用一个单独的 url 地址去访问 swagger 页面,那体验也太差了。SpringDoc 提供了聚合功能,我们可以在 gateway 网关这里聚合所有的微服务 swagger。以后浏览器就可以只收藏一个网关的 swagger 地址了。

上图是将所有的微服务swagger聚合到网关,想看哪个服务的swagger文档,Select a definition即可。具体配置也很简单,首先肯定是要在网关引入SpringDoc依赖,然后配置聚合

springdoc:
  api-docs:
    enabled: false #生产环境关闭
  swagger-ui:
    enabled: false #生产环境关闭
    urls-primary-name: gateway # 主 definition
    urls:
      - name: gateway
        url: /v3/api-docs
      - name: auth-center
        url: /ac/v3/api-docs
        #......

不要忘记配置生产环境禁用swagger哈。。。

结语

本篇文章简单介绍了 SpringDoc 基本使用,以及常见的优化使用的方法。还有很多高级姿势例如整合 Security,WebFlux 以后用到的话再来更新。

如果这篇文章对你有帮助,记得点赞加关注。你的支持就是我继续创作的动力!

发布于 2022-06-16 18:10