WebFlux 中使用 Swagger


因为工作需要,重新调研了 Swagger。WebFlux 使用 Swagger 和之前有一些不同,而且我发现网络上基本没有这方面的中文资料,因此写篇文章记录一下。


Swagger 是非常常用的 API 文档工具,就我观察,好像所有需要写接口文档的 Java 项目都在使用它。从某种程度上讲,Swagger 可以代表接口文档,因为它实在是太主流了,而且它提出的规范也变成了行规,更何况它还在积极向前迭代。

网上对于 Swagger 和行业规范 OpenAPI 的描述有点混乱,我试着整理了一下:

  • Swagger 在 2011 年出现,目前(2020.7)的版本是 2.X
  • Swagger 将自己的规范捐献给 Linux 基金会,改名为 OpenAPI Specification(OAS),目前的版本是 3
  • 网络上普遍将 Swagger 1.X 称为 2.0,而将 2.X 称为 3.0,具体原因不明,因此搜索最新的 Swagger 时,你需要搜索 Swagger 3.0(或者放弃用中文搜索)
  • Swagger 1.X 和 2.X 在逻辑上是一致的,在使用上注解名都换了,在规范上做了更新(但是用户感受不到)

大家使用 Swagger 基本都是在 Spring 项目中使用的,但是 Spring 官方并没有集成 Swagger,因此目前大家使用的都是开源世界的第三方轮子。有两个比较常见的依赖库:

  • SpringFox,最常见,但是至今它还没有支持 Spring 5,因此如果想在 WebFlux 中使用 Swagger,你还不能选择这个。
  • SpringDoc,支持 WebFlux,本篇文章就是在说怎么使用 SpringDoc。

SpringDoc 有一大堆的 Swagger 实现,适配了诸如 Spring MVC、Spring WebFlux、Swagger-UI 等等一堆的内容,官方给的 demo 里面就有接近十个不同的项目,我也没太看明白,在反复尝试之后,得出一组在 WebFlux 上能够使用的 Maven 依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-core</artifactId>
<version>1.4.3</version>
</dependency>

(具体的官方文档可以点击 SpringDoc

理论上引入 Maven 依赖之后就可以直接使用了,启动地址是 …/swagger-ui.html(在我们的项目里是 …/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/,不太明白原因)。

SpringDoc 的各项参数基本都可以在 application.properties 中进行配置,就比如上面的地址,默认配置就是:

1
springdoc.swagger-ui.path=/swagger-ui.html

更多的配置信息可以参考官方文档《Configuration of springdoc-openapi》


Swagger 2.X 使用的注解全都变了,尽管使用逻辑没变,但是名字都不一样了。

如果你对原来的 Swagger 很熟悉,那么可以参考《Migrating from SpringFox》,这是 SpringDoc 官方整理的一份文档,描述了怎么从 SpringFox 迁移过来(因为 SpringFox 使用旧的 Swagger 1.X,这文档整理得可以说是杀人诛心了233)

如果你对原来的 Swagger 不是很熟悉,或者印象不深了,可以参考 Swagger 2.X 在 github 上的官方使用文档《Swagger 2.X Annotations》

国内使用 Swagger 2.X 的导读文章还比较少,唯一一篇我觉得有价值的是《springboot 集成 springdoc-openapi-ui》


下面我简单整理几个常用的注解:

@Tag

可用在类或方法前(一般是类前),用于给多个接口归类(某些接口在同一组内),相当于 Swagger2 的 @Api

属性 作用 数据类型 备注 是否必填 默认值
name 名称 String 如果多个 @Tag 使用相同的 name,那么将归到同一个组中
description 描述 String 如果多个 @Tagname 相同归到同一个组,仅能使用一个 description,否则会发生覆盖 × “”
externalDocs 额外说明文档 ExternalDocumentation(注解) 一般用不到,可以附文字、网址进行补充说明,内容详见下文 @ExternalDocumentation 注解 × @ExternalDocumentation()
Extension 没用

使用示例:

1
2
3
4
5
@Tag(name = "本Controller的名字", description = "这里是一些描述",
externalDocs = @ExternalDocumentation(description = "外部文档", url = "https://www.baidu.com"))
public class MyController {
// ...
}

@Operation

可用在方法前,主要用于描述接口,相当于 Swagger 的 @ApiOperation

属性 作用 数据类型 备注 是否必填 默认值
summary 摘要(实际代表着名字的作用) String 在 Swagger 中,展示在接口 URL 旁边,建议必写 × “”
description 描述 String 在 Swagger 中,点击接口详情可看到 × “”
requestBody 请求体 RequestBody(注解) 使用比较繁冗,建议不使用 × @RequestBody()
responses 返回体 ApiResponse[](注解数组) 使用比较繁冗,建议不使用 × {}
externalDocs 一般用不上,用法同 @Tag 内的使用方法
tags 建议不用,当类没有 @Tag 注解时,将使用此处的 tags 属性分成指定的多组,使用上容易出错
method 用不上
operationId 用不上
deprecated 用不上,完全可以用 Java 自己的 @Deprecated 注解代替
ignoreJsonView 用不上
extensions 用不上
parameters 用不上
security 用不上
servers 用不上
hidden 用不上

使用示例:

1
2
3
4
5
6
// 如果采用自定义的VO类,来接收和返回值,那么代码将很紧凑
@GetMapping("example/{id}")
@Operation(summary = "本方法的摘要/简介", description = "本方法的具体描述")
Mono<ResponseBean<ResponseVo>> get(@PathVariable Long id) {
return Mono.just(new ResponseBean<>(HttpStatus.SC_OK, Boolean.TRUE, "success", new ResponseVo()));
}

@Schema

主要用于描述实体类和变量(也可应用在其他注解之中,但代码冗余,不建议)

属性 作用 数据类型 备注 是否必填 默认值
name 替换原名 String 不要使用,它会替换掉原字段名称,例如变量名 userIdname = "用户ID",变量名在前端的展示将不再是 userId,而是用户ID × “”
description 描述 String 最常使用,请在每处都使用它 × “”
example 样例 String 示例,建议使用 × “”
required 是否必填 boolean × false
其他(好多) 建议不使用,可自行斟酌

使用示例:

1
2
3
4
5
6
7
8
9
10
@Data
@Schema(description = "用户类")
public class User {

@Schema(description = "用户ID", required = true)
private String userId;

@Schema(description = "用户名", required = true, example = "张三")
private String username;
}

写一个简单的小 demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* —————————————————————— 定义两个类,用于接收和返回 —————————————————————— */

@Data
@Schema(name = "请求对象", description = "用于接收请求的内容")
public class RequestDto {

@Schema(description = "ID", example = "20200711001", required = true)
private Long id;

@Schema(description = "名字", example = "张三")
private String name;
}

@Data
@Schema(name = "返回对象", description = "用于封装返回的内容")
public class ResponseDto {

@Schema(description = "ID", example = "20200711002", required = true)
private Long id;

@Schema(description = "名字", example = "李四")
private String name;
}



/* —————————————————————— 下面是Controller层的使用 —————————————————————— */

@RestController
@RequestMapping("swagger")
@Tag(name = "swagger api", description = "swagger样例代码", externalDocs = @ExternalDocumentation(
description = "swagger-core 参考网站",
url = "https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations"
))
public class controller {

/* ———————————————————————— 以下是推荐用法 ———————————————————————— */

@GetMapping("example1/{id}")
@Operation(summary = "GET 请求示例1", description = "推荐用法(RESTful风格)")
Mono<ResponseDto> get1(@PathVariable Long id) {
return Mono.just(new ResponseDto());
}

@GetMapping("example2")
@Operation(summary = "GET 请求示例2", description = "推荐用法(用params接收参数)")
Mono<ResponseDto> get2(@RequestParam Long userId, @RequestParam String userName) {
return Mono.just(new ResponseDto());
}

/* —————————————————— 以下是不推荐用法(勉强实现效果) —————————————————— */

@PostMapping("example3")
@Operation(
summary = "POST 请求示例1", description = "不推荐用法(手动指定类型)",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
content = @Content(schema = @Schema(implementation = RequestDto.class))
),
responses = {@ApiResponse(
content = @Content(schema = @Schema(implementation = ResponseDto.class))
)}
)
Mono<JSONObject> save2(@RequestBody JSONObject request) {
return Mono.just(new JSONObject());
}
}

Swagger 的配置文件(Config.java)是可有可无的,因为大多数的配置,都可以使用上文提到的 application.properties 中配置,这跟在 Spring Boot 中使用其他依赖工具的方式是一样的。

如果你还是想用一个 Java 文件单独配置的话,可以参考下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@OpenAPIDefinition(info = @Info(
title = "pz的Swagger网站", version = "1.0", description = "后端管理系统接口文档"),
externalDocs = @ExternalDocumentation(description = "官方使用文档", url = "https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations"))
public class SwaggerConfig {

/**
* 以下是在配置鉴权 authorization
* 在使用时,在想鉴权的类上配置注解 @SecurityRequirement(name = "authorization")
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("authorization",
new SecurityScheme()
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.type(SecurityScheme.Type.APIKEY)
.scheme("bearer")));
}
}

它实现了两个配置:

  1. Swagger 可视化网站的名称等配置。
  2. 配置了鉴权(请求参数中携带 Authorization 作为 token)

值得一提的是,上面这两个配置,一个使用注解,一个使用方法,其实是可以互换的,也就是说每一种配置都是可以使用注解或方法来配置的,看个人喜好。

最终的效果如下图所示:

Swagger


研究 Swagger 配置鉴权花了不少时间,主要也是因为自己没有做过鉴权相关的东西。

总体的思路是,在配置文件中事先配置好,并给这个部分起一个名字(因为可以有多个鉴权)。在想使用的 Controller 层或者方法层上,加上一行注解,指定想要使用的鉴权的名字,就可以使用啦。

比如针对上面的配置文件(名字叫做 authorization),就可以在 Controller 上面加上这么一行注解,这样在 Swagger 中调用方法时,这个 Controller 里面的所有方法都会在请求参数里带上 Authorization

1
2
3
4
@SecurityRequirement(name = "authorization")
public class controller {
...
}

就整理这么多吧。