# 使用Swagger和SpringFox文档化Spring Boot REST API
> 如果您是第一次了解Swagger,建议您先前往https://swagger.io/官网学习,推荐学习文档《Swagger从入门到实战》,而本文档并不适用于入门。
>
> 如果您仅仅是想了解如何Spring系列项目生成API文档,可先前往[SpringFox](https://springfox.github.io/springfox/)学习。
## Swagger介绍
Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。
做后端开发 ,自然离不开有接口文档。接口文档不仅仅方便后端人员之间查看,也方便前端人员查看,也提供给第三方调用接口时查看。但是,编写接口文档太费时间,如果文档格式没有统一,那么每个人写得接口文档可能各不相同。这样就十分混乱,严重影响团队开发进程。因此,有必要寻找到一个方便统一接口文档的工具,swagger就是为此诞生。
如果我们的SpringBoot项目集成了`springfox-swagger-ui`,在**接口**、**入参**、**出参**、**实体类**添加`Swagger注解`,那么,就可以通过访问`http://yourIp:yourport/swgger-ui.html`来访问在线接口,在此页面可以直接进行接口测试。For Example,`http://localhost:8080/swagger-ui.html`
## SpringFox介绍

自动JSON API文档,用于使用Spring构建的API。

## 问题:
**如何使用Swagger和SpringFox文档化Spring Boot REST API?**
REST API非常重要。它是一个公共接口,其他模块、应用程序或开发人员需要使用它。拥有适当文档的界面以避免混淆并使其始终保持最新是至关重要的。
  最受欢迎的API文档规范之一是OpenApi,以前称为Swagger。它允许您使用JSON或YAML元数据描述API的属性。它还提供了一个Web UI,它可以将元数据转换为一个很好的HTML文档。此外,通过该UI,您不仅可以浏览有关API端点的信息,还可以将UI用作REST客户端 - 您可以调用任何端点,指定要发送的数据并检查响应。它非常方便。
  手动编写此类文档并在代码更改时保持更新是不现实的。这就是SpringFox发挥作用的地方。它是Spring Framework的Swagger集成。它可以自动检查您的类,检测控制器,它们的方法,它们使用的模型类以及它们映射到的URL。没有任何手写文档,只需检查应用程序中的类,它就可以生成大量有关API的信息。多么酷啊?最重要的是,每当您进行更改时,它们都会反映在文档中。如下所示。

## Swagger的优点与缺点
在此,我们先提出Swagger的优点与缺点,在后续会介绍缘由。
🔶优点:
- `自动生成文档`。只需要在接口中使用注解进行标注,就能生成对应的接口文档。
- `自动更新文档`。由于Swagger是动态生成的,修改接口,文档也会自动对应修改(如果你也更新了注解的话)。这样就不会发生我修改了接口,却忘记更新接口文档的情况。
- `支持在线调试`。swagger提供了在线调用接口的功能。
🔶缺点:
- `不能创建测试用例`。Swagger暂时不能帮助我们处理完所有的事情。他只能提供一个简单的在线调试,如果你想存储你的测试用例,可以使用Postman或者YAPI这样支持创建测试用户的功能。
- `要遵循一些规范`,它不是任意规范的。比如说,你可能会返回一个json数据,而这个数据可能是一个Map格式的,那么我们此时不能标注这个Map格式的返回数据的每个字段的说明,而如果它是一个实体类的话,我们可以通过标注类的属性来给返回字段加说明。也比如说,对于swagger,不推荐在使用GET方式提交数据的时候还使用Body,仅推荐使用query参数、header参数或者路径参数,当然了这个限制只适用于在线调试。
- `没有接口文档更新管理`,虽然一个接口更新之后,可能不会关心旧版的接口信息,但你“可能”想看看旧版的接口信息,例如有些灰度更新发布的时候可能还会关心旧版的接口。那么此时只能由后端去看看有没有注释留下了,所以可以考虑接口文档大更新的时候注释旧版的,然后写下新版的。【当然这个问题可以通过导出接口文档来对比。】
- 虽然现在Java的实体类中有不少模型,po,dto,vo等,模型的区分是为了屏蔽一些多余参数,比如一个用户登录的时候只需要username,password,但查权限的时候需要连接上权限表的信息,而如果上述两个操作都是使用了User这个实体的话,在文档中就会自动生成了多余的信息,这就要求了你基于模型来创建多个实体类,比如登录的时候一个LoginForm,需要用户-权限等信息的时候才使用User类。(当然了,这个问题等你会swagger之后你就大概就会怎么规避这个问题了。)
> 不明白?无妨,我们继续看
## 建立Swagger项目demo
利用`Spring Initializer`构建SpringBoot整合Swagger项目。
- 项目文件结构图
```
swagger-demo
 ├── mvnw
 ├── mvnw.cmd
 ├── pom.xml
 ├── README.md
 └── src
     ├── main
     │   ├── java
     │   │   └── com
     │   │       └── example
     │   │           ├── config
     │   │           │   └── SwaggerConfig.java
     │   │           ├── controller
     │   │           │   ├── RoleController.java
     │   │           │   └── UserController.java
     │   │           ├── domain
     │   │           │   └── LoginForm.java
     │   │           └── SwaggerDemoApplication.java
     │   └── resources
     │       └── application.yml
     └── test
         └── java
             └── com
                 └── example
                     └── SwaggerDemoApplicationTests.java
```
- pom.xml  (只展示Swagger依赖配置部分)
```xml
       <!--Swagger依赖-->
        <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>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.8.7</version>
        </dependency>
```
- application.yaml
```yaml
spring:
  profiles:
#    active: release
    active: dev
```
要使用swagger,须对Swagger进行配置。因此,需要创建一个Swagger配置类,一般命名为SwaggerConfig.java
- SwaggerConfig.java
  ```java
  package com.zhoujk.swagger.config;
  
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import springfox.documentation.builders.ApiInfoBuilder;
  import springfox.documentation.builders.PathSelectors;
  import springfox.documentation.builders.RequestHandlerSelectors;
  import springfox.documentation.service.ApiInfo;
  import springfox.documentation.spi.DocumentationType;
  import springfox.documentation.spring.web.plugins.Docket;
  import springfox.documentation.swagger2.annotations.EnableSwagger2;
  
  @Configuration  //表明是配置类
  @EnableSwagger2  //开启swagger功能
  public class SwaggerConfig {
  
      @Bean
      public Docket createRestApi() {
          return new Docket(DocumentationType.SWAGGER_2)
                  //调用apiInfo方法,创建一个ApiInfo实例,展示文档页面信息内容
                  .apiInfo(apiInfo())
                  // select()函数返回一个ApiSelectorBuilder实例,用来控制接口被swagger做成文档
                  .select()
                  // 用于指定扫描哪个包下的的接口
                  .apis(RequestHandlerSelectors.basePackage("com.zhoujk.swagger"))
                  // 选择所有的API,如果你只想为部分API生成文档,可以在这里配置
                  .paths(PathSelectors.any())
                  .build();
      }
  
      //构建api文档的详细信息函数
      private ApiInfo apiInfo() {
  
          return new ApiInfoBuilder()
                  //页面标题
                  .title("Swagger演示项目API")
                  //项目描述
                  .description("Swagger演示项目API接口界面")
                  //服务条款网址
                  .termsOfServiceUrl("https://zhoujk.top")
                  //作者信息
                  .contact(new Contact("千叶", "zhoujk.top", "zhou431613@163.com"))
                  //许可证
                  .license("Apache 3.0")
                  //版本
                  .version("1.0")
                  .build();
      }
  
  }
  
  ```
其中==@EnableSwagger2==表示开启Swagger2的功能。在Config配置类中需要注入一个Docket的Bean,该Bean包含了ApiInfo,即基本的API文件的描述信息,以及包扫描的基本包名等信息。其中`select`、`apis`、`paths`和`build`这四个是一起的,它们组合最后才能返回一个Docket对象,当然apis、paths是可选的,非必选,apis表示可以扫描哪个路径下的实例,path表示过滤的方式。
- 实体类LoginForm 
```java
package com.zhoujk.swagger.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "用户登录表单对象",description = "用户登录表单对象")
public class LoginForm {
    @ApiModelProperty(value = "用户名", required = true, example = "admin")
    private String username;
    @ApiModelProperty(value = "密码", required = true, example = "123456")
    private String password;
    private String getUsername() {
        return username;
    }
    private String getPassword() {
        return password;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
```
在这里,实体类LoginForm也作为请求参数。因此,对于实体类也要进行相关的注解。
此时我们需要使用`@ApiModel`来标注实体类,然后在接口中定义入参为实体类即可:
- @ApiModel:用来标注类
  - 常用配置项:
    - value:实体类简称
    - description:实体类说明
- @ApiModelProperty:用来描述类的字段的意义。
  - 常用配置项:
    - value:字段说明
    - example:设置请求示例(Example Value)的默认值,如果不配置,当字段为string的时候,此时请求示例中默认值为"".
    - name:用新的字段名来替代旧的字段名。
    - allowableValues:限制值得范围,例如`{1,2,3}`代表只能取这三个值;`[1,5]`代表取1到5的值;`(1,5)`代表1到5的值,不包括1和5;还可以使用infinity或-infinity来无限值,比如`[1, infinity]`代表最小值为1,最大值无穷大。
    - required:标记字段是否必填,默认是false,
    - hidden:用来隐藏字段,默认是false,如果要隐藏需要使用true,因为字段默认都会显示,就算没有`@ApiModelProperty`。
接着下来,我来编写Controller类。
- userController类
```java
package com.zhoujk.swagger.controller;
import com.zhoujk.swagger.domain.LoginForm;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
 * @author : zhoujiankang
 * @Desc:
 * @since : 2022/4/4 16:34
 */
@Api(tags = "用户管理")
@RestController
public class UserController {
    @GetMapping("/info")
    public String info(String id) {
        return "1811421199";
    }
    @ApiOperation(value = "用户测试", notes = "用户测试notes", tags = "角色管理")
    @GetMapping("/test")
    public String test(String id) {
        return "test";
    }
    //获取实体类对象参数
    @ApiOperation(value = "登录接口", notes = "登录接口的说明")
    @PostMapping("/login")
    public LoginForm login(@RequestBody LoginForm loginForm) {
        return loginForm;
    }
    //使用URL  query参数
    @ApiOperation(value = "登录接口2", notes = "登录接口说明2")
    @ApiImplicitParams({@ApiImplicitParam(name = "username",
            value = "用户",
            required = true,
            paramType = "query",
            defaultValue = "root"),
            @ApiImplicitParam(name = "password",
                    value = "密码",
                    required = true,
                    paramType = "query")})
    @PostMapping(value = "/login2")
    public LoginForm login2(String username, String password) {
        System.out.println(username + ":" + password);
        LoginForm loginForm = new LoginForm();
        loginForm.setUsername(username);
        loginForm.setPassword(password);
        return loginForm;
    }
    //使用路劲参数
    @PostMapping("/login3/{id1}/{id2}")
    @ApiOperation(value = "登录接口3", notes = "登录接口3的说明")
    @ApiImplicitParams({@ApiImplicitParam(name = "id1",
    value = "用户名",
    required = true,
    paramType = "path"),
    @ApiImplicitParam(name = "id2",
    value="密码",
    required = true,
    paramType = "path")})
    public String login3(@PathVariable Integer id1, @PathVariable Integer id2) {
        return id1 + ":" + id2;
    }
    // 使用header传递参数
    @PostMapping("/login4")
    @ApiOperation(value = "登录接口4", notes = "登录接口4的说明")
    @ApiImplicitParams({@ApiImplicitParam(name = "username",
    value="用户名",
    required = true,
    paramType = "header"),
    @ApiImplicitParam(name = "password",
    value="密码",
    required=true,
    paramType="header")})
    public String longin4(@RequestHeader String username, @RequestHeader String password) {
        return username + ":" + password;
    }
    //对于这个接口的在线测试,是基于弃用了swagger-bootstrap-ui之后。
    // 默认情况下,sagger无法选择发送数据的编码方式
    @PostMapping("login5")
    @ApiOperation(value = "等录接口5", notes = "登录接口5的说明")
    @ApiImplicitParams({@ApiImplicitParam(name = "username",
    value = "用户名",
    required = true,
    paramType = "form"),
            @ApiImplicitParam(name = "password",
            value = "密码",
            required=true,
            paramType = "form")}
    )
    public String login5(String username, String password) {
        return username + ":" + password;
    }
    // 有文件上传时要用@ApiParam,用法基本与@ApiImplicitParam一样,不过@ApiParam用在参数上
    // 或者你也可以不注解,swagger会自动生成说明
    @ApiOperation(value = "上传文件", notes = "上传文件")
    @PostMapping(value = "/upload")
    public String upload(@ApiParam(value = "图片文件", required = true) MultipartFile uploadFile) {
        return uploadFile.getOriginalFilename();
    }
    // 多个文件上传时,**swagger只能测试单文件上传**
    @ApiOperation(value = "上传多个文件", notes = "上传多个文件")
    @PostMapping(value = "upload2", consumes = "multipart/*", headers = "content-type=multipart/form-data")
    public String upload2(@ApiParam(value = "图片文件", required = true, allowMultiple = true) MultipartFile[] uploadFile) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < uploadFile.length; i++) {
            System.out.println(uploadFile[i].getOriginalFilename());
            stringBuilder.append(uploadFile[i].getOriginalFilename());
            stringBuilder.append(",");
        }
        return stringBuilder.toString();
    }
    // 既有文件,又有参数
    @ApiOperation(value = "既有文件,又有参数",notes = "既有文件,又有参数")
    @PostMapping(value = "/upload3")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name",
                    value = "图片新名字",
                    required = true
            )
    })
    public String upload3(@ApiParam(value = "图片文件", required = true)MultipartFile uploadFile,
                          String name){
        String originalFilename = uploadFile.getOriginalFilename();
        return originalFilename+":"+name;
    }
    // 如果需要额外的参数,非本方法用到,但过滤器要用,类似于权限token
    @PostMapping("/login6")
    @ApiOperation(value = "带token的接口",notes = "带token的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "authorization",//参数名字
                    value = "授权token",//参数的描述
                    required = true,//是否必须传入
                    paramType = "header"
            )
            ,
            @ApiImplicitParam(name = "username",//参数名字
                    value = "用户名",//参数的描述
                    required = true,//是否必须传入
                    paramType = "query"
            )
    })
    public String login6(String username){
        return username;
    }
}
```
接口有时候应该是分组的,而且大部分都是在一个controller中的,比如用户管理相关的接口应该都在UserController中,那么不同的业务的时候,应该定义/划分不同的接口组。接口组可以使用`@Api`来划分。
比如:
```java
@Api(tags = "用户管理")  //  tags:你可以当作是这个组的名字。
@RestController
public class UserController {
```
和
```java
@Api(tags = "用户管理") //  tags:你可以当作是这个组的名字。
@RestController
public class UserController {
}
```
- RoleController类
```java
package com.zhoujk.swagger.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPObject;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.sun.org.apache.xerces.internal.xs.StringList;
import com.zhoujk.swagger.domain.LoginForm;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import netscape.javascript.JSObject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.spring.web.json.Json;
import java.util.ArrayList;
/**
 * @author : zhoujiankang
 * @Desc:
 * @since : 2022/4/4 16:34
 */
@Api(tags = "角色管理")
@RestController
public class RoleController {
    // 返回被@ApiModel标注的类的对象
    @ApiOperation(value = "实体类响应", notes = "返回数据为实体类的接口")
    @PostMapping("/role1")
    public LoginForm role1(@RequestBody LoginForm loginForm) {
        return loginForm;
    }
    @ApiOperation(value = "非实体类", notes = "非实体类")
    @ApiResponses({
            @ApiResponse(code = 200, message = "调用成功"),
            @ApiResponse(code = 401, message = "无权限")
    })
    @PostMapping("/role2")
    public String role2() {
        JSONArray  jsonArray  = new JSONArray();
        JSONObject jsonObject  = new JSONObject();
        jsonObject.put("provinces","浙江");
        jsonObject.put("city", new String[] {"杭州","温州","绍兴","宁波","台州","嘉兴","金华","衢州"});
        jsonArray.add(jsonObject);
        return jsonArray.toJSONString();
    }
}
```
我们再来看看接口,使用`@ApiOperation`来描述接口,比如:
```java
    @ApiOperation(value = "非实体类", notes = "非实体类")
    @ApiResponses({
            @ApiResponse(code = 200, message = "调用成功"),
            @ApiResponse(code = 401, message = "无权限")
    })
    @PostMapping("/role2")
    public String role2() {
```

关于Swagger的注解,统计一下,一共使用多少个?又都用在什么地方呢?
|        参数        |                         说明                         |                         使用场景案例                         |
| :----------------: | :--------------------------------------------------: | :----------------------------------------------------------: |
|        @Api        | 用于类,标识这个类是Swagger的资源,通常为Controller  |                   @Api(tags = "角色管理")                    |
|   @ApiOperation    |       用于方法,描述Controller类中的method接口       | // 返回被@ApiModel标注的类的对象 @ApiOperation(value = "实体类响应", notes = "返回数据为实体类的接口") |
|   @ApiResponses    |            用于方法,包含多个@ApiResponse            | @ApiResponses({@ApiResponse(code = 200, message = "调用成功"),         @ApiResponse(code = 401, message = "无权限") }) |
|    @ApiResponse    |              用于方法,描述单个响应信息              |                              -                               |
|  @EnableSwagger2   |                  开启Swagger2的功能                  | @Configuration  //表明是配置类 @EnableSwagger2  //开启swagger功能 public class SwaggerConfig {......} |
| @ApiImplicitParams |         用于方法,包含多个@ApiImplicitParam          | @ApiImplicitParams({@ApiImplicitParam(name = "username", value="用户名", required = true, paramType = "header"), @ApiImplicitParam(name = "password", value="密码", required=true, paramType="header")}) |
| @ApiImplicitParam  |             用于方法,表示单独的请求参数             |                              -                               |
|     @ApiModel      | 用于Model类,标识Swagger model,提供对象额外信息描述 | @ApiModel(value = "用户登录表单对象",description = "用户登录表单对象") |
| @ApiModelProperty  |        用于Model属性,描述属性,设置是否必须         | @ApiModelProperty(value = "用户名", required = true, example = "admin") |
想一想,Swagger的注解都全部使用到了吗?如果没有,又是那些注解没有使用呢?
| 参数       | 说明                                     |
| ---------- | ---------------------------------------- |
| @ApiIgnore | 用于类,忽略该Controller,即不扫描当前类 |
| @ApiError  | 用于方法,接口错误所返回的信             |
再想一想,这些注解需要写吗?不写又怎样?

Added support for resolving properties in property sources to replace expression in certain annnotations。这句话什么意思?添加了对解析属性源中的属性以替换某些注解中的表达式的支持。可以简单的理解为,我们可以对类和方法添加描述。例如:为get请求 /test方法操作添加描述。

这里是Springfox-demos的一些案例:https://gitee.com/zhou431615/springfox-demos.git,可以参考。网上的一些例子,大都相同,可以去看看springfox创始人写的demo。For  example,
```java
package springfoxdemo.boot.swagger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.annotations.ApiIgnore;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.AuthorizationScopeBuilder;
import springfox.documentation.builders.ImplicitGrantBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.BasicAuth;
import springfox.documentation.service.Contact;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.LoginEndpoint;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger1.annotations.EnableSwagger;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.petstore.controller.PetController;
import springfox.petstore.controller.PetStoreResource;
import springfox.petstore.controller.UserController;
import springfoxdemo.boot.swagger.web.HomeController;
import javax.print.Doc;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import static springfox.documentation.builders.PathSelectors.*;
@SpringBootApplication
@EnableSwagger //Enable swagger 1.2 spec
@EnableSwagger2 //Enable swagger 2.0 spec
@EnableOpenApi //Enable open api 3.0.3 spec
public class Application {
  public static void main(String[] args) {
    ApplicationContext ctx = SpringApplication.run(Application.class, args);
  }
  @Bean
  public PetController petController() {
    return new PetController();
  }
  @Bean
  public PetStoreResource petStoreController() {
    return new PetStoreResource();
  }
  @Bean
  public UserController userController() {
    return new UserController();
  }
  @Bean
  public Docket petApi() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("full-petstore-api")
        .apiInfo(apiInfo())
        .select()
        .paths(petstorePaths())
        .build()
        .securitySchemes(Collections.singletonList(oauth()))
        .securityContexts(Collections.singletonList(securityContext()));
  }
  @Bean
  public Docket categoryApi() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("category-api")
        .apiInfo(apiInfo())
        .select()
        .paths(categoryPaths())
        .build()
        .ignoredParameterTypes(ApiIgnore.class)
        .enableUrlTemplating(true);
  }
  @Bean
  public Docket multipartApi() {
    return new Docket(DocumentationType.SWAGGER_2)
        .groupName("multipart-api")
        .apiInfo(apiInfo())
        .select()
        .paths(multipartPaths())
        .build();
  }
  private Predicate<String> categoryPaths() {
    return regex(".*/category.*")
        .or(regex(".*/category")
            .or(regex(".*/categories")));
  }
  private Predicate<String> multipartPaths() {
    return regex(".*/upload.*");
  }
  @Bean
  public Docket userApi() {
    AuthorizationScope[] authScopes = new AuthorizationScope[1];
    authScopes[0] = new AuthorizationScopeBuilder()
        .scope("read")
        .description("read access")
        .build();
    SecurityReference securityReference = SecurityReference.builder()
        .reference("test")
        .scopes(authScopes)
        .build();
    List<SecurityContext> securityContexts =
        Collections.singletonList(
            SecurityContext.builder()
                .securityReferences(Collections.singletonList(securityReference))
                .build());
    return new Docket(DocumentationType.SWAGGER_2)
        .securitySchemes(Collections.singletonList(new BasicAuth("test")))
        .securityContexts(securityContexts)
        .groupName("user-api")
        .apiInfo(apiInfo())
        .select()
        .paths(input -> input.contains("user"))
        .build();
  }
  @Bean
  public Docket openApiPetStore() {
    return new Docket(DocumentationType.OAS_30)
        .groupName("open-api-pet-store")
        .select()
        .paths(petstorePaths())
        .build();
  }
  private Predicate<String> petstorePaths() {
    return regex(".*/api/pet.*")
        .or(regex(".*/api/user.*")
            .or(regex(".*/api/store.*")));
  }
  private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
        .title("Springfox petstore API")
        .description("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum " +
            "has been the industry's standard dummy text ever since the 1500s, when an unknown printer "
            + "took a " +
            "galley of type and scrambled it to make a type specimen book. It has survived not only five " +
            "centuries, but also the leap into electronic typesetting, remaining essentially unchanged. " +
            "It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum " +
            "passages, and more recently with desktop publishing software like Aldus PageMaker including " +
            "versions of Lorem Ipsum.")
        .termsOfServiceUrl("http://springfox.io")
        .contact(new Contact("springfox", "", ""))
        .license("Apache License Version 2.0")
        .licenseUrl("https://github.com/springfox/springfox/blob/master/LICENSE")
        .version("2.0")
        .build();
  }
  @Bean
  SecurityContext securityContext() {
    AuthorizationScope readScope = new AuthorizationScope("read:pets", "read your pets");
    AuthorizationScope[] scopes = new AuthorizationScope[1];
    scopes[0] = readScope;
    SecurityReference securityReference = SecurityReference.builder()
        .reference("petstore_auth")
        .scopes(scopes)
        .build();
    return SecurityContext.builder()
        .securityReferences(Collections.singletonList(securityReference))
        .forPaths(ant(".*/api/pet.*"))
        .build();
  }
  @Bean
  SecurityScheme oauth() {
    return new OAuthBuilder()
        .name("petstore_auth")
        .grantTypes(grantTypes())
        .scopes(scopes())
        .build();
  }
  @Bean
  SecurityScheme apiKey() {
    return new ApiKey("api_key", "api_key", "header");
  }
  List<AuthorizationScope> scopes() {
    return Arrays.asList(
        new AuthorizationScope("write:pets", "modify pets in your account"),
        new AuthorizationScope("read:pets", "read your pets"));
  }
  List<GrantType> grantTypes() {
    GrantType grantType = new ImplicitGrantBuilder()
        .loginEndpoint(new LoginEndpoint("http://petstore.swagger.io/api/oauth/dialog"))
        .build();
    return Collections.singletonList(grantType);
  }
  @Bean
  public SecurityConfiguration securityInfo() {
    return SecurityConfigurationBuilder.builder()
        .clientId("abc")
        .clientSecret("123")
        .realm("pets")
        .appName("petstore")
        .scopeSeparator(",")
        .build();
  }
}
```
仔细看一下这一段代码,数一数,一共创建了多少个`Docket`?


### ApiPost介绍
在这里简单介绍一下,ApiPost,使用步骤与Postman并无多大区别,对于新手友好上手。下载地址:https://www.apipost.cn/
APlPost = Postman + Swagger + Mock后端、前端、测试同时在线编辑,内容实时同步。

> 国内包括我在内的开发者,之前进行API调试时一直在使用Postman。它的接口调试功能虽然强大,但是很多诸如生成接口文档等研发常用功能并不符合国人习惯;虽然支持协同功能,但几乎是天价,同时由于服务器在国外,经常出现延迟甚至卡顿的情况,十分不稳定。做Apipost的初心是简单易用,上手要快,支持研发协同、快速导出接口文档;并针对个人开发者,完全支持离线使用。Apipost已迭代到6.0版本,我们希望通过这款产品,让更多的中国开发者从无尽的消耗中解放出来,更希望以此证明中国软件的价值和中国力量的崛起     ----ApiPost创始人穆红伟评语
我很喜欢使用ApiPost的分享文档的功能,也可以导出文档。

## 参考文档
1.SpringFox[官方参考文档](https://springfox.github.io/springfox/)
2.SpringFox[官方文档中文翻译版](https://uhfun.gitee.io/uhfun-jekyll/tech/2020/05/13/Springfox%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3-Springfox-Reference-Documentation%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91.html#1-%E4%BB%8B%E7%BB%8D)
3.Swagger [OpenAPIGuide](https://swagger.io/docs/specification/2-0/basic-structure/)
### 名词解释
REST API
REST 是一组架构规范,并非协议或标准。API 开发人员可以采用各种方式实施 REST。当客户端通过 RESTful API 提出请求时,它会将资源状态表述传递给请求者或终端。该信息或表述通过 HTTP 以下列某种格式传输:JSON(Javascript 对象表示法)、HTML、XLT、Python、PHP 或纯文本。JSON 是最常用的编程语言,尽管它的名字英文原意为“JavaScript 对象表示法”,但它适用于各种语言,并且人和机器都能读。 
还有一些需要注意的地方:头和参数在 RESTful API HTTP 请求的 HTTP 方法中也很重要,因为其中包含了请求的元数据、授权、统一资源标识符(URI)、缓存、cookie 等重要标识信息。有请求头和响应头,每个头都有自己的 HTTP 连接信息和状态码。
       
          Springfox-Swagger-ui学习笔记
 
               
            