0%

SpringBoot项目整合Swagger自动生成接口文档之最佳实践指南

最近为项目引入Swagger来支持自动生成文档功能,发现很多文章仅仅介绍了如何接入以及如何使用的问题。但是对于实际工程实践,并没有给出相应的最佳实践方案。因此,我重新梳理相关内容以及文档,整理出一套最佳实践指南

Swagger和Springfox

在正式介绍之前,我们首先要了解SwaggerSpringfox之间的关系。相信在Spring项目使用过Swagger的同学都知道,在Spring项目中是通过Springfox来整合Swagger功能的。

Swagger是什么

根据官网的介绍,Swagger是一系列用于Restful API开发的工具,开源的部分包括:

  • OpenAPI Specification:API规范,规定了如何描述一个系统的API
  • Swagger Codegen:用于通过API规范生成服务端和客户端代码
  • Swagger Editor:用来编写API规范
  • Swagger UI:用于展示API规范

简单来说,我们可以认为swagger主要制定并实现了一个API规范,用于描述系统的API接口

Springfox是什么

简单来说,Springfox其实是一个通过扫描并提取代码中的信息,来生成API文档的工具,支持swaggerRAMLjsonapi等多种API文档的格式。因为我们这里着重讨论swagger,因此我们可以简单理解为:Springfox就是整合SpringMVCswagger的中间层,以支持自动扫描Controller层的接口来生成符合swaggerAPI规范的描述数据(JSON格式)

通过Springfox,已经可以将swaggerSpringBoot项目整合起来了,网上有一大堆整合的文章,这里就不再赘述了,具体可以参考SpringBoot整合Swagger实战。并且官方最近也出了3.0版本,具体可以参考:还在手动整合Swagger?Swagger官方Starter是真的香!

knife4j简介

虽然直接通过Springfox已经可以实现SpringBoot接入Swagger生成接口文档。但是这种方式有两个弊端:

  1. 原生接口文档页面展示不够友好。
  2. 使用起来可能比较繁琐。

因此,在这里强烈推荐使用Knife4jKnife4jSwagger接口文档服务的通用性解决方案,底层基于Springfox实现。最主要的两个特性如下:

  1. 重写了前端UI界面,更符合国人使用习惯。
  2. 支持快速接入,并提供了很多实用功能增强。
  3. 文档友好,直接参考Knife4j官网文档就能完成接入。

能让用户以最低成本接入的方式就是最好的方式,而Knife4j确实实现的非常好。更多详细信息可以参考官方文档https://xiaoym.gitee.io/knife4j/documentation/description.html

最佳实践指南

实际上,SpringBootController层方法已经包含描述该接口详细信息了。当然,因为我们要补充中文说明。因此,最为理想的状况是,接口文档直接能直接根据Controller层的方法自动生成,我们顶多为每个参数编写一个中文说明,仅此而已

我觉得完全自描述的接口才是最合理的,同时也是最简便的。但是,理想很好,现实情况是Springfox对此支持的并不算太好。因此,我们需要在实践的时候,找到一种方式来解决这个问题,下面会介绍一些常见问题的解决思路。

2.x和3.x版本如何选择

Knife4j2.x版本对应Springfox2.x版本,其采用的是Swagger2规范,相应的3.x版本使用的OpenAPI3规范。关于这两个规范,我们不需要了解太多细节,只需要知道OpenAPI3规范能够更准确描述接口就行。而对于Springfox而言,3.x版本还有一个优势是:对于Bean Validation支持更好,能够从接口自身解析出更详细的接口信息。因此,如果条件允许,强烈建议3.x版本!我甚至觉得,如果是2.x版本,接入的价值都不大。我始终觉得,在一个本身已经自描述的接口上,再去专门为文档去写各种注释是一件本末倒置的事情。既然已经自描述,那么最佳的解决方案永远都是框架去解析,而不是人工再写一遍!!!

以下是一个2.x3.x版本,对同一接口生成的接口文档,相信大家看完会有一个自己的判断!

2.x版本:
swagger2.x

3.x版本:
swagger3.x

3.x版本能解析出更多的接口信息,描述更为准确

对请求体入参支持不够好

对于Query查询参数,Springfox3.x版本已经支持的非常好了,我们按之前的方式写接口即可。但是对于请求体参数,现有框架支持的还是不够好。其中,最为典型的是不支持分组校验。这样的导致的结果就是,文档上会将整个DTO类的字段以及约束全部展示到任何一个使用了该DTO接收参数的接口上。很明显,这并不合理,因为分组校验做的就是让该接口指定分组下的约束只对这个接口生效。

针对这个问题,有什么完美的解决方案吗?答案是没有,这里只能做一个取舍。如果十分看重接口文档准确性的话,只能不使用分组校验,这样的话,意味着每个接口的请求体都要用不同的DTO类来接收,以防止互相干扰。如果不是特别在意,那还是按分组校验来写,实现更简单。目前,我们项目准备选第一种方式。

如果不了解参数校验,可以戳这里:Spring Validation最佳实践及其实现原理,参数校验没那么简单!。对于参数校验,强烈建议这种注解式的方式。因为只有注解式,才能真正做到接口自描述。

接入和使用示例

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>

编写接口

只需要额外再为每个字段加中文说明即可

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@RestController
@RequestMapping("/api/person")
@Validated
@Api(tags = "Person管理")
public class PersonController {


@ApiOperation("保存用户")
@PostMapping("savePerson")
public Result<PersonVO> savePerson(@RequestBody @Valid SavePersonDTO person) {
PersonVO personVO = new PersonVO().setAge(10).setEmail("xxxxx").setId(1L).setName("哈哈");
return Result.ok(personVO);
}

@ApiOperation("更新用户")
@PostMapping("updatePerson")
public Result<PersonVO> updatePerson(@RequestBody @Valid UpdatePersonDTO person) {
PersonVO personVO = new PersonVO().setAge(10).setEmail("xxxxx").setId(1L).setName("哈哈");
return Result.ok(personVO);
}


@ApiOperation("查询person")
@GetMapping("queryPerson")
public Result<List<PersonVO>> queryPerson(
@ApiParam("用户id") @Min(1000) @Max(10000000) Long id,
@ApiParam("姓名") @NotNull @Size(min = 2, max = 10) String name,
@ApiParam("年龄") @NotNull @Max(200) Integer age,
@ApiParam("邮箱") @Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$") String email) {
List<PersonVO> list = new ArrayList<>();
list.add(new PersonVO().setAge(10).setEmail("xxxxx").setId(1L).setName("哈哈"));
return Result.ok(list);
}

}


@Data
@Accessors(chain = true)
@ApiModel("保存用户DTO类")
public class SavePersonDTO {

@ApiModelProperty("姓名")
@NotNull
@Size(min = 2, max = 10)
private String name;

@ApiModelProperty("年龄")
@NotNull
@Max(200)
private Integer age;

@ApiModelProperty("邮箱")
@Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
private String email;
}

@Data
@Accessors(chain = true)
@ApiModel("更新用户DTO类")
public class UpdatePersonDTO {


@ApiModelProperty("用户id")
@NotNull
private Long id;

@ApiModelProperty("姓名")
@Size(min = 2, max = 10)
private String name;

@ApiModelProperty("年龄")
@Max(200)
private Integer age;

@ApiModelProperty("邮箱")
@Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
private String email;
}

@Data
@Accessors(chain = true)
@ApiModel("用户VO类")
public class PersonVO {

@ApiModelProperty("用户id")
private Long id;

@ApiModelProperty("姓名")
private String name;

@ApiModelProperty("年龄")
private Integer age;

@ApiModelProperty("邮箱")
private String email;
}

一般来说,生产环境并不需要开启接口文档功能,只需要在生产环境加如下配置即可:

1
2
3
springfox:
documentation:
enabled: false

常用文档注解

使用文档注解的目的只是为了添加中文说明,仅此而已

注解 说明 示例
@Api 标注在Controller上,用于给控制器添加中文说明 @Api(tags = “Person管理”)
@ApiOperation 标注在方法上,用于给请求添加中文说明 @ApiOperation(“保存用户”)
@ApiParam 标注在方法参数上,用于给请求参数添加中文说明 @ApiParam(“用户id”)
@ApiModel 标注在DTO实体类上,用于给请求体添加中文说明 @ApiModel(“保存用户DTO类”)
@ApiModelProperty 标注在DTO实体类的字段上,用于给请求体字段添加中文说明 @ApiModelProperty(“姓名”)

示例截图

swagger-demo

源码

源码地址:https://github.com/chentianming11/spring-validation

启动项目,访问http://localhost:8080/doc.html可查看接口文档。

原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~

欢迎关注我的开源项目:一款适用于SpringBoot的轻量级HTTP调用框架