前言
一个好用的文档工具对于程序员开发、联调会提升很大效率。今天要介绍的就是一款我感觉很不错的文档工具 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>
编写配置
只要注入 OpenAPI 的 Bean 即可
@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 Properties ,Swagger 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,但是有时候我们使用框架里面的类,有些属性并不希望他们显示出来,典型的就是自定义分页解析器的时候,参考我这篇文章 自定义参数解析器时多余字段显示 。此时 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
看到 *(必填) 、 minItem、mininum(最小值) 的提示了吧,再看我们点击 Try It Out 按钮后
swagger 给我们提供的默认请求体就已经列出了 List 元素最小个数 2 ,example 最小值 10,爱了爱了~~
Multiple Acceptable Schemas
咦,为什么这一栏的标题是英文呢?emmm 因为不知道该怎么描述,就把源码里面的关键注释搬过来了。。。
我举个例子,有些时候我们使用多态的思想开发,比如我现在接手的催收项目有 AP 的催收数据,CN 的催收数据,然后现在有一个催收详情接口,那 AP 和 CN 的催收详情页面显示的数据字段并不是完全一致的,但是它们又有一些公共字段。
当然我们可以选择定义两个 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 以后用到的话再来更新。