文章目录
前言
Swagger是什么?
Swagger API 项目最初由 Wordnik 的技术联合创始人 Tony Tam 于 2011 年创建,主要针对在 Wordnik 的产品开发过程中 API 文档自动化和客户端 SDK 生成的需求。设计师/开发人员 Zeke Sikelianos 创建了 Swagger 这个名字,Swagger API 项目于 2011 年 9 月开源。
2015 年 11 月,维护 Swagger 的公司 SmartBear Software 宣布在 Linux 基金会的赞助下,创建了一个名为 OpenAPI Initiative 的新组织,包括谷歌、IBM 和微软在内的各种公司都是创始成员。
2016 年 1 月 1 日,Swagger 规范更名为 OpenAPI 规范,并移至 GitHub 上。
Swagger 官网上将 Swagger 分为三大块:
其中,Swagger 开源工具可以分成以下三部分:
- Swagger Editor: 编辑器,可以在浏览器中以 YAML 格式编辑 OpenAPI API 定义并实时预览文档。
- Swagger Codegen:代码生成器,可以基于根据 OpenAPI (以前称为 Swagger ) 定义的 RESTful API 可以自动生成服务端和客户端代码.。
- Swagger UI:UI部分,根据 OpenAPI(以前称为Swagger )规范自动生成的 HTML 界面,可视化 API 资源,能够与之交互。
SpringBoot中使用Swagger
在 SpringBoot 项目中一般使用 Swagger 用的是 Swagger UI 部分,用于接口的文档在线自动生成和功能测试。 常见的库是使用 springfox 提供的 springfox,不过最新更新在2020年,已经两年没更新,最新的替代品是 springdoc-openapi 。
下面会介绍 springfox swagger 的使用,虽然已经不再维护,但文档齐全,使用的人多,有什么问题可以十分方便找到答案。
一、pom依赖
不同 swagger 版本选择pom依赖也不同
swagger2
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
swagger3(openapi3) 提供了boot-starter版本,但自此之后再无更新
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
swagger2 ui 访问路径:http://localhost:8080/swagger-ui.html
swagger3 ui 访问路径:http://localhost:8080/swagger-ui/index.html
下面会使用 swagger3 为例,探索 swagger 使用
二、常用注解
swagger 以下注解都位于 io.swagger.annotations 包内
注解名 | 标记范围 | 含义 |
---|---|---|
@Api | TYPE | 用于标注一个 Controller,表明是 swagger 资源 。在默认情况下,Swagger-Core 只会扫描解析具有 @Api 注解的类 |
@ApiImplicitParam | METHOD | 用于标注 @ApiOperation 操作中的单个参数 |
@ApiImplicitParams | METHOD | 用于标注 @ApiOperation 操作中的多个参数 |
@ApiModel | TYPE | 用于标注一个类,表明此类是 swagger 的 Model 类 |
@ApiModelProperty | METHOD,FIELD | 用于描述被 @ApiModel 标记类的属性 |
@ApiOperation | METHOD,FIELD | 表明是一个 http 请求的操作 |
@ApiParam | PARAMETER,METHOD,FIELD | 和 @ApiImplicitParam 类似, 但只能和 JAX-RS 1.x/2.x 注解结合使用 |
@ApiResponse | METHOD | 用于描述 @ApiOperation 单个响应 |
@ApiResponses | METHOD | 用于描述 @ApiOperation 多个响应 |
@Authorization | METHOD | 定义要在资源或操作上使用的授权方案。应该在 @Api 或 @ApiOperation 中使用 |
@AuthorizationScope | METHOD | 描述 OAuth2 授权范围,应该在 @Authorization 中使用 |
@Example | ANNOTATION_TYPE | 用于描述一个示例 |
@ExampleProperty | ANNOTATION_TYPE | 用于描述一个示例属性 |
@ResponseHeader | METHOD | 定义 @ApiResponse head部分 |
下面注解不在 io.swagger.annotations 包下面
注解名 | 标记范围 | 含义 |
---|---|---|
@EnableSwagger | TYPE | 启用 swagger 1.2 规范 |
@EnableSwagger2 | TYPE | 启用 swagger 2.0 规范 |
@EnableOpenApi | TYPE | 启用 open api 3.0.3 规范 |
@ApiIgnore | METHOD, TYPE, PARAMETER | 忽略所标记的方法、类或参数,不会在UI界面显示 |
三、配置
一个 Swagger3 文档例子 UI 如下
可以通过 java 代码对界面进行配置
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.any()) // 可以通过PathSelectors.ant("/a/**")来分隔 进行分组
.build();
}
//生成接口信息,包括标题、联系人、版本等
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3 demo接口文档")
.description("如有疑问,请联系开发工程师。")
.contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
.version("1.0")
.build();
}
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.supportedSubmitMethods(DEFAULT_SUBMIT_METHODS) // 支持的http方法
.build();
}
}
Docket 是 主要的配置类,如果包含分组可以配置多个,上面使用 directModelSubstitute 是为了在前台用 String 接收时间类型
UiConfiguration 中可以配置一些UI界面选项
docExpansion 接口文档展开
- none 不展开
- list 展开
- full 全部展开,包括接口的详细信息
operationsSorter http操作排序
- alpha 字母排序
- method 根据 http 方法排序 (http方法根据字母顺序排序)
defaultModelRendering Model渲染
-
EXAMPLE 接口 response 默认显示 example
-
MODEL 接口 response 默认显示 schema
四、示例
下面以一个选课系统来演示 swagger 使用
4.1 代码
课程类
@ApiModel
@Data
public class Course {
@ApiModelProperty(value = "课程ID")
private Long id;
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "类别")
private String category;
@ApiModelProperty(value = "学分")
private String credit;
@ApiModelProperty(value = "学生数量")
private Integer number;
@ApiModelProperty(value = "老师名字")
private List<String> teachers;
@ApiModelProperty(value = "上课时间-星期")
private DayOfWeek timeWeek;
@ApiModelProperty(value = "开始时间")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime;
@ApiModelProperty(value = "结束时间")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime endTime;
@ApiModelProperty(value = "地点")
private String place;
}
学生类
@ApiModel
@Data
public class Student {
@ApiModelProperty(value = "学号")
private String id;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "专业")
private String major;
@ApiModelProperty(value = "年级")
private CollegeGradeEnum grade;
@ApiModelProperty(value = "性别")
private String gender;
}
学生年级枚举
public enum CollegeGradeEnum {
FRESHMAN,
SOPHOMOREER,
JUNIOR,
SENIOR
}
公共VO
@ApiModel
@Data
@Builder
public class CommonVo<T> {
@ApiModelProperty("状态码")
private String code;
@ApiModelProperty("状态信息")
private String message;
@ApiModelProperty("数据")
private T data;
}
课程Controller
@Api(value = "courseAPI",tags = "课程API")
@RestController
@RequestMapping
public class CourseController {
private static final Logger logger = LoggerFactory.getLogger(CourseController.class);
@ApiOperation("创建一门课程")
@ApiImplicitParam(name = "course", value = "课程信息", dataTypeClass = Course.class, paramType = "body")
@RequestMapping(value = "/createCourse",method = RequestMethod.POST)
public ResponseEntity<CommonVo> createCourse(@RequestBody Course course) {
return ResponseEntity.ok(CommonVo.builder().code("0").message("success").build());
}
@ApiOperation("修改课程信息")
@ApiImplicitParams({
@ApiImplicitParam( name = "id", value = "课程id", paramType = "query", dataTypeClass = Long.class, required = true),
@ApiImplicitParam( name = "category", value = "课程类别", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "name", value = "课程名称", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "timeWeek", value = "星期", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "startTime", value = "上课开始时间", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "endTime", value = "上课结束时间", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "credit", value = "学分", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "teachers", value = "老师", paramType = "query", dataTypeClass = String.class, allowMultiple = true),
@ApiImplicitParam( name = "number", value = "上课人数", paramType = "query", dataTypeClass = Integer.class),
@ApiImplicitParam( name = "place", value = "上课地点", paramType = "query", dataTypeClass = String.class),
})
@RequestMapping(value = "/updateCourse",method = RequestMethod.PUT)
public ResponseEntity<CommonVo<Course>> updateCourse(Course course){
logger.info("{}}", course);
return ResponseEntity.ok(CommonVo.<Course>builder().code("200").data(course).build());
}
@ApiOperation("操作课程")
@RequestMapping(value = "/testCourse", method = RequestMethod.POST)
public ResponseEntity<CommonVo<Course>> test(@ApiParam(name = "course") @RequestBody Course course) {
return null;
}
@ApiOperation("查看课程信息")
@RequestMapping(value = "/courseList",method = RequestMethod.GET)
public ResponseEntity<CommonVo<List<Course>>> courseList(@ApiParam(name = "studentId", value = "学号") String studentId) {
return null;
}
@ApiOperation("查看单个课程信息")
@RequestMapping(value = "/course",method = RequestMethod.GET)
public ResponseEntity<CommonVo<Course>> course(@ApiParam(name = "courseId", value = "课程Id") Long courseId) {
return null;
}
@ApiOperation("删除课程")
@RequestMapping(value = "/deleteCourse",method = RequestMethod.DELETE)
public ResponseEntity<CommonVo> deleteCourse(Long courseId) {
return null;
}
}
学生Controller
@Api(value = "student API", tags = "学生API")
@RestController
@RequestMapping("/home")
public class StudentController {
@ApiOperation("新增学生")
@ApiImplicitParam(name = "student", value = "学生信息", dataTypeClass = Student.class, paramType = "body")
@RequestMapping(value = "/createCourse",method = RequestMethod.POST)
public Object createCourse(@RequestBody Student student) {
return null;
}
}
4.2 后台接收前端字段
一般通过 @ApiImplicitParam 或 @ApiParam 来定义接收的字段,@ApiImplicitParam 用处比较广,可以接收表单和 json 等各种字段,常用属性如下所示
-
name
参数名字
-
value
参数描述
-
paramType
参数类型,可以为以下部分
-
header:@RequestHeader
-
query:@RequestParam
-
path:@PathVariable
-
body:@RequestBody
-
form:表单
-
-
dataTypeClass
参数类型,String类型用的比较多,默认 Void.class
-
required
参数是否必填,默认 false
-
allowMultiple
是否数组,,默认 false
@ApiImplicitParam
@ApiParam 在官方文档中注明需要和 JAX-RS 1.x/2.x 注解结合使用,实际使用发现可以和 SpringBoot 中注解一起使用,使用效果和 @ApiImplicitParam 类似
字段含义可以通过Scchema 查看
4.3 后台返回前端字段
后台返回前端字段,不需要特别指出,只需要使用 @ApiModel @ApiModelProperty将返回数据类定义好,swagger 会自动将 controller 返回值解析为 json 数据.。以上面示例为例,ResponseEntity<CommonVo<Course>>
会自动解析为下面字段
每个字段的含义,可以通过schema标签查看
五、拓展使用
5.1 隐藏前端的 schema
在UiConfiguration uiConfig配置defaultModelsExpandDepth(-1)
5.2 分组
当Controller很多时,页面查找不方便,可以根据路径将各个Controller进行分组
@Bean
public Docket studentApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("学生")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.ant("/student/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
.build();
}
@Bean
public Docket courseApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("课程")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.ant("/course/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
.build();
}
@Bean
public Docket defaultApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("default")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.any())
.build();
}
//生成接口信息,包括标题、联系人等
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3 demo接口文档")
.description("如有疑问,请联系开发工程师。")
.contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
.version("1.0")
.build();
}
5.3 生成离线文档
swagger 通过 swagger2markup 这个项目将文档转成静态文档。注意:暂时只能生成 swagger2 文档,通过查找源码发现,解析类Swagger20Parser 解析json数据前会判断是否包含 swagger 这个节点,而 swagger3 的 swagger 节点名字已经改成 openapi 了,这个问题暂时未修复,相关问题已经有人提ISSUE Swagger v3 cannot use swagger2markup(1.3.3)
所以需要生成文档,临时的解决方案是将 new Docket(DocumentationType.OAS_30)
修改为 new Docket(DocumentationType.SWAGGER_2)
-
生成 asciidoc 文档
mvn swagger2markup:convertSwagger2markup
<properties> <swagger2markup.version>1.3.3</swagger2markup.version> <asciidoctor.input.directory>${project.basedir}/src/docs/asciidoc</asciidoctor.input.directory> <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory> <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory> </properties> <plugin> <groupId>io.github.swagger2markup</groupId> <artifactId>swagger2markup-maven-plugin</artifactId> <version>1.3.3</version> <configuration> <swaggerInput>http://localhost:8080/v2/api-docs</swaggerInput> <outputDir>${asciidoctor.input.directory}</outputDir> <config> <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage> </config> </configuration> </plugin>
-
生成 HTML 和 PDF 文档
mvn test
<plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.5</version> <configuration> <!--上一步生成的asciidoc文件路径--> <sourceDirectory>${asciidoctor.input.directory}</sourceDirectory> <headerFooter>true</headerFooter> <doctype>book</doctype> <sourceHighlighter>coderay</sourceHighlighter> <attributes> <!--菜单栏在左边--> <toc>left</toc> <!--多标题排列--> <toclevels>3</toclevels> <!--自动打数字序号--> <sectnums>true</sectnums> </attributes> </configuration> <dependencies> <dependency> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctorj-pdf</artifactId> <version>1.5.0-alpha.16</version> </dependency> <dependency> <groupId>org.jruby</groupId> <artifactId>jruby-complete</artifactId> <version>1.7.21</version> </dependency> </dependencies> <executions> <execution> <id>output-html</id> <phase>test</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html5</backend> <outputDirectory>${asciidoctor.html.output.directory}</outputDirectory> </configuration> </execution> <execution> <id>output-pdf</id> <phase>test</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>pdf</backend> <outputDirectory>${asciidoctor.pdf.output.directory}</outputDirectory> </configuration> </execution> </executions> </plugin>
最终在 target 目录下生成
PDF样式如下,可以发现中文有丢失情况,解决方法:swagger+asciidoctor 导出PDF中文缺失乱码问题解决
5.4 第三方主题
推荐一个第三方主题,访问路径为/doc.html
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-ui</artifactId>
<version>3.0.3</version>
</dependency>
六、顺序问题(坑)
3.0版本的各种顺序默认都变成了字母顺序,而且不好更改,是个大坑。介意这个问题的可以考虑换成 springdoc-openapi
6.1 Controller tag 顺序
默认字母顺序,理论上讲可以通过 tagsSorter 这个方法修改,但是 TagsSorter 这个枚举也只有 ALPHA(“alpha”) 一个值,所以说还是不能修改
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.tagsSorter(TagsSorter.ALPHA)
.build();
}
6.2 operation 顺序
@ApiOperation 中 position 也已经被废弃,但是可以通过 operationsSorter 来进行更改, OperationsSorter 有两种配置,一种是 ALPHA (字母顺序),另一种是 METHOD (根据HTTP方法排序)。这两种排序也还行,但是还是希望可以根据类中方法顺序进行排序。
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.tagsSorter(TagsSorter.ALPHA)
.build();
}
6.3 参数顺序
自 V2.9 后,参数顺序就变为字母顺序,不能更改,具体 ISSUE: 2.9.0 change the order of parameters in operations。这非常影响查看,比如参数 startTime 和 endTime 就分隔开来了,希望改成类中的字段顺序。
6.4 model property 顺序
@ApiModelProperty position 失效,具体 ISSUE : Model properties are alphabetically sorted (without order attribute),维护者说会在v3.0.1 版本修复这个 bug ,但是已经两年没更新,最新版本是 v3.0.0.