在前后端分离的项目中,维护接口文档是必不可少的工作。一个容易想到的流程是做完设计后把接口文档发给前端和后端,按所有人照既定的规则各自开发,开发完对接上线。但这是非常理想的状态,实际开发中很少会这么简单而顺利。接口总是在不断变化之中,有变化就要去维护,手工维护接口文档的工作量非常巨大。

幸好有一些工具可以减轻维护接口的工作量,Swagger 就是其中的典型。本文主要讨论如何在Spring Boot中整合Swagger 管理接口文档,并且使用 Knife4j 的增强功能和管理界面。

42.1 概述

42.1.1 关于Springfox Swagger

Springfox Swagger 是一个规范和完整的文档框架,用于生成、描述、调用和可视化 RESTful 风格的接口文档。后端开发人员只需要根据 OpenAPI 官方定义的注解就可以把接口文档非常丰富的呈现给前端接口对接人员。并且接口文档是随着代码的变动实时更新,同时提供了在线 HTML 文档辅助开发人员可以进行接口联调测试,这大大省去了技术人员写文档的烦恼,也提升了企业开发的效率,减少沟通成本。

42.1.2 关于Knife4j

除了核心框架功能,Swagger 还提供了一个用户界面项目实现可视化的接口文档管理,但是不怎么好用,界面过于简陋,而国内的工程师开发的 Knife4j 项目是配合 Swagger 更理想的选择。它完全遵循Springfox Wwagger和OpenAPI规范,不仅提供了更加友好的UI管理界面,也增加了更多个性化的特性增强功能。

它有两大核心功能:文档说明和在线调试:

  • 文档说明:根据 Swagger 的规范详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息。
  • 在线调试:提供在线接口联调的功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、Curl请求命令实例、响应时间、响应状态码等信息,帮助开发者在线调试。

在满足以上功能的同时,还提供了文档的增强功能,这些功能是官方 UI 所没有的。每一个增强的功能都是贴合实际,考虑到开发者的实际开发需要,是比不可少的功能,主要包括:

  • 个性化配置:通过个性化UI配置项,可自定义UI的相关显示信息。
  • 离线文档:根据标准规范,生成的在线 markdown 离线文档,开发者可以进行拷贝生成 markdown 接口文档,通过其他第三方 markdown 转换工具转换成 html 或 pdf。
  • 接口排序:方便对控制器和接口进行更舒适的管理。

    42.2 OpenAPI 的规范

    目前 OpenAPI 规范主要分两个版本,分别是 2.0 以及 3.0,如下图(常规属性节点):
    42. 生成和管理接口文档 - 图2
    在 OpenAPI2.0 规范中,主要涉及的元素:

  • info:该接口的一些元数据信息,包含标题、联系人、版本、描述等信息

  • host:该接口的服务地址
  • basePath:基础路径
  • schemes:当前接口的传输协议,一般是 HTTP 或者 HTTPS
  • security:接口可使用的安全方案,基础验证、OAuth2 等
  • securityDefinitions:接口的安全类型参数定义,例如我们常见的 OAuth2
  • consumes:定义接口的请求数据类型集合
  • produces:定义接口的响应数据类型集合
  • paths:所有接口节点的详细描述信息
  • tags:分组 tag
  • definitions:所有的类 Model 结果集

OpenAPI 3.0 相比较而言,简单了很多:

  • info:该接口的一些元数据信息,包含标题、联系人、版本、描述等信息
  • servers:该接口的服务地址
  • security:接口可使用的安全方案,基础验证、OAuth2 等
  • paths:所有接口节点的详细描述信息
  • tags:分组 tag
  • components:所有的类 Model 结果集

    42.3 Swagger基本配置

    42.3.1 使用Swagger UI的基本配置

    本节我们先看一下 Springfox 及其 UI 的常规配置方法。

首先添加依赖

  1. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-boot-starter</artifactId>
  4. <version>3.0.0</version>
  5. </dependency>

请注意这里依赖的是写作本章教程时 Swagger 最新的 3.0.0版。这个版本较之前的版本有非常大的变化,以至于在网络上搜索得到的各种中英文范例基本上都过时无效。即便是 Swagger 官方手册也因为没有及时更新全部而存在前后不一致。所以请仔细阅读本章后续的内容,不要随便被网络上其他的说明而误导。

然后定义一个空的配置类,

package com.longser.union.cloud.config;

import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@Profile({"dev"})
public class SwaggerConfig {

}

在 SecurityConfig 中增加3个开发用的放行的地址

    private final String[] developmentApi = {
            "/api/createUser",
+           "/v3/api-docs/**",
+           "/swagger-resources/**",
+           "/swagger-ui/**",
    };

本来完成上面工作以后 swagger-ui 就可以工作。但由于我们把所有 API 的返回做了强制封装,swagger-ui 的API 的返回内容也都被封装了起来,为了让它能够正常工作,还需要修改 RestfulResponseAdvice 类中的 supports 方法:

    @Value("${spring.profiles.active}")
    private String activeProfile;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {

        if(returnType.getExecutable().getDeclaringClass().getName().contains("org.springframework.boot.actuate")) {
            return false;
        }

        boolean isProduction = ApplicationState.PRODUCTION.is(activeProfile);

        if(!isProduction && returnType.getExecutable().getDeclaringClass().getName().contains("springfox")) {
            return false;
        }

        return !returnType.hasMethodAnnotation(IgnoreRestful.class);
    }

重启应用后访问当前服务器的 /swagger-ui/即可看到默认的 Swagger UI 内容
image.png
这个界面确实不怎么好看。

42.3.2 使用 Knife4j 的基本配置

Knife4j 底层是依赖 SpringFox 项目的,他的本身已经引入了Springfox Swagger,在使用时不用再单独引入Springfox Swagger的具体版本,否额会导致版本冲突。

删除 springfox-boot-starter 依赖项,增加下面的内容

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

然后在类 WebMvcConfig 中重载 addResourceHandlers 方法

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("doc.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }

修改放行的定义

    private final String[] developmentApi = {
            "/api/createUser",
            "/v3/api-docs/**",
            "/swagger-resources/**",
-           "/swagger-ui/**",
+           "/doc.html",
+           "/webjars/**",
    };

重启后访问当前服务器的 /doc.html,可以看到结构清晰且更加美观的文档界面
image.png

42.4 常见配置及文档信息

尽管不做任何配置,Swagger 和 Knife4j 就可以工作,此时自动生成的文档已经包括了对应用系统所有 API 的描述。但自动化默认的结果并不能完全让我们满意,所以需要使用 Swagger 提供的注解和配置进一步优化生成的文档。具体的配置方法非常简单——只需要提供一个类型为 Docket 的 Bean 来完成 Swagger 的配置。

下面是一个基本的配置代码

package com.longser.union.cloud.config;

import org.springframework.boot.SpringBootVersion;
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.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@Profile({"dev"})
public class SwaggerConfig {
    @Bean
    public Docket swaggerDocketConfig() {
        Docket docket=new Docket(DocumentationType.OAS_30)
                .enable(true)
                .groupName("Spring Boot 教程")
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(getApiInfo());
        return docket;
    }

    private ApiInfo getApiInfo() {
        return new ApiInfoBuilder()
                .title("朗思技术学院 Spring Boot 手把手开发教程")
                .description("这是朗思技术学院《Spring Boot手把手开发教程》的 API 文档")
                .version("应用程序版本: " + "1.0.0"
                        + ", Spring Boot  版本: "
                        + SpringBootVersion.getVersion())
                .contact(new Contact("David Jia",
                        "www.longser.com","david@longser.com"))
                .build();
    }
}

关于上面的代码有几个重要的说明:

  • 返回 Docket 对象的方法名称没有任何具体的要求(但不能是 swaggerConfig,因为已经被它自己用了)
  • 定义具体的配置方法后就不需要在任何地方放置 @EnableSwagger2 注解。这个在 Swagger 的官方文档已经做了说明。但在这个官方文档的代码里面还能够看到很多使用这个注解的代码段,他们自己也没有把文档更新好。另外,官方在 Github 上的例子也不用去看。那些的版本都太老了了,他们应该是很长时间没有更新这些例子。

下面是构建 Docket 对象时需要的几个主要属性。

  • apiInfo

该属性主要是构建接口文档的开发者信息,可以理解为项目的基本信息以及作者信息,主要包含的属性:项目标题、描述、作者(联系人)信息、版本、协议地址等信息。

  • groupName

配置该接口文档的分组名称,开发者自定义,例如在微服务的架构下,我们的多个微服务可以在这里进行定义,或者是在单体架构中,我们也可以根据包名来区分具体的模块,如:用户服务、订单服务、公共服务等等。

  • apis

该属性是告诉 SpringFox 在初始化时,将我们的需要服务规则的接口进行解析,这在分包的情况下显得尤为重要,如果不配置该属性,默认会全局扫描,主要有以下几种方式:

  • 基于包路径扫描,通过调用 RequestHandlerSelectors.basePackage(“com.package”)初始化即可,需要注意的是这里的包名不支持通配符 * 等,所以开发者需要写上包名的完整路径,如果开发者存在根据模块进行分包的结构,这里写上模块的顶级包名即可。
  • 基于注解扫描,主要有两种,一种是 class 类级别、一种是 method 方法级别:
    • RequestHandlerSelectors.withClassAnnotation(Controller.class),扫描所有的类上带有 Controller 注解的接口进行初始化。
    • RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class),扫描所有方法上带有 ApiOperation 注解的接口进行初始化。

重启后再次访问,可以看到配置好的信息
image.png

42.5 编写各种说明信息

Swagger 直接生成的接口文档中可以看到各种路径、类名、方法名和参数变量名。尽管从规范命名的代码中可以看出基本的含义,但这些命名毕竟信息量不够,生成的文档也无法满足项目交付的要求。我们可以在给源代码添加注释的同时使用 Swagger 的注解添加各种用于生成接口文档的文字说明。

42.5.1 编写控制器说明

使用@Api注解可以编写控制器的说明

@Api(tags = "和用户用户登录及用户管理有关的接口")
public class LoginController {

这个说明会显示在接口文档左侧的列表中
image.png

42.5.2 编写接口说明

默认的情况下,左侧列表中显示的是各个接口的方法名,右侧接口参数的说明部分就是参数名称:
image.png
网址则按照规则自动生成
image.png
使用 @ApiOperation 注解可以给定义接受的名称、说明等参信息

@ApiOperation(
    value = "用户登录",
    notes = "这是一个响应登录操作的接口,接受用户和密码,认证成功后返回加了签名的 JWT(JWS)",
    nickname = "login"
)
@PostMapping("/login")
public String login(

在上面的代码中使用了 @ApiOperation 的三个常见属性

  • value 显示在左侧列表中的菜单项和右侧的标题
  • notes 显示在右边对接口的详细说明
  • nickname 用户定义的访问地址

下面是注解生效后的的效果
image.png

42.5.3 编写接口参数说明

使用 @ApiImplicitParams 和 @ApiImplicitParam 注解可以给方法的参数加上说明

@ApiImplicitParams({
    @ApiImplicitParam(name = "username", value = "用户名", required = true, dataTypeClass = String.class),
    @ApiImplicitParam(name = "password", value = "用户密码", required = true, dataTypeClass = String.class)
})
@PostMapping("/login")

注意:这里的 required 只是用来在文档上做标记,不影响实际的接口定义。

下面是注解生效后的结果
image.png

尽管使用 @ApiParam 也能定义方法的参数说明,但对于 Servlets或者非 JAX-RS的环境,只能使用 @ApiImplicitParam。而且在使用上,@ApiImplicitParam 比 @ApiParam 具有更少的代码侵入性。所以应该尽量使用 @ApiImplicitParam 注解。

42.5.4 上传文件接口的参数说明

下面是 /upload 接口的代码

@PostMapping("/upload")
public RestfulResult<String> upload(@RequestParam("file") MultipartFile file){
    return super.saveFile(file);
}

查看 /upload 接口的说明,并没有看到参数 file 的信息:
image.png
如果用文档的调试功能,也无法上传文件

image.png
那么如何让我们的MultipartFile file 能够被自动识别呢?档案很简单,我们把 @RequestParam 注解换成 @RequestPart 即可

-public RestfulResult<String> upload(@RequestParam("file") MultipartFile file){
+public RestfulResult<String> upload(@RequestPart("file") MultipartFile file){

现在可以在文档中看到接口参数(此时请求数据类型也跟着变化了)
image.png
测试的时候也能上传文件
image.png
当然我们可以把信息定义的更加完整详细

@PostMapping("/upload")
@ApiOperation(value = "单个文件上传", notes = "这是一个响应单个文件上传的接口", nickname = "upload" )
@ApiImplicitParams({
    @ApiImplicitParam(name = "file", value = "MultipartFile文件流对象",
        required = true,dataTypeClass = MultipartFile.class)
})
public RestfulResult<String> upload(@RequestPart("file") MultipartFile file){
    return super.saveFile(file);
}

image.png

42.5.5 实体类及成员变量说明

Swagger 会根据接口的定义情况自动分析需要在文档中显示的实体类定义。点击左侧菜单中的 Swagger Models,可以在右侧看到各实体类及其成员变量的说明.
image.png
对可以用 @ApiModel 和 @ApiModelProperty 注解给实体类和类的城边变量添加说明:

@ApiModel(value = "请求结果", description = "全局通用的前后端交互请求结果")
public class RestfulResult<T>{
    @ApiModelProperty("执行结果是否成功")
    Boolean success;
    @ApiModelProperty("执行失败时的错误代码")
    Integer errorCode;
    @ApiModelProperty("执行失败时的错误信息")
    String errorMessage;
    @ApiModelProperty("执行成功时返回的对象(最终以字符串形式提交给前端)")
    T data;

下面是添加了说明的效果
image.png

注意:使用 @ApiModel 注解时,需要保证该描述是全局唯一的,就和类名一样,不能出现重复,否则就可能会出现属性错乱的问题。即使是包名不同但是类名相同,也会出现异常,因为从 SpringFox Swagger 的解析规则来看,类名是只有名称,不包含包名的。

42.6 在文档中忽略一些内容

42.6.1 在文档中忽略特定接口

42.6.1.1 不同的忽略策略

在文档中忽略特定接口有多种方法:

  • 用 @ApiIgnore 注解直接标注忽略的类或方法
  • 在配置代码中指定只处理特定代码包的类
  • 在配置代码中排除特定请求路径的所有接口
  • 在配置代码中指定特定请求路径的所有接口
  • 在配置代码中指定只处理被特定注解标注的类
  • 使用 @ApiOperation 的 hidden 属性

    42.6.1.2 用注解标注忽略的类或方法

    可以直接在类上标注 @ApiIgnore 注解忽略该类

    @ApiIgnore
    public class DemoAnnotationAuthorizeController {
    
    @ApiIgnore
    public class ExceptionGenerator {
    

    这样这两个控制器类就不会出现在左侧的控制器列表中。

    @ApiIgnore 注解也可以作用在方法上 ```diff public class DruidStatController {

  • @ApiIgnore @GetMapping(“/test/druid/stat”) public Object druidStat(){ ```

    42.6.1.3 只处理特定代码包下面的类

    在左侧的控制器列表中,我们可以看到 basic-error-controller 这种Spring Boot框架自动生成的控制器。
    image.png

    由于这些控制器不是我们定义的,所以不能使用添加 @ApiIgnore 注解的方式忽略他们。为此我们在配置中指定只处理我们代码包下面的控制器类: ```diff Docket docket=new Docket(DocumentationType.OAS_30) .enable(true) .groupName(“Spring Boot 教程”) .pathMapping(“/“) .select() .apis(RequestHandlerSelectors.any())
  • .apis(RequestHandlerSelectors.basePackage(“com.longser.union.cloud”)) .paths(PathSelectors.any()) .build() .apiInfo(getApiInfo()); ```

    42.6.1.4 排除特定请求路径的所有接口

    我们可以在配置中使用路径的正则表达式定义哪些请求路径的控制器类不在文档中出现。 ```diff Docket docket=new Docket(DocumentationType.OAS_30) .enable(true) .groupName(“Spring Boot 教程”) .pathMapping(“/“) .select() .apis(RequestHandlerSelectors.any()) .apis(RequestHandlerSelectors.basePackage(“com.longser.union.cloud”)) .paths(PathSelectors.any())
  • .paths(PathSelectors.regex(“(?!/test).+”)) .build() .apiInfo(getApiInfo()); `` 上面的代码告诉 Swagger 选择不是以/test` 开头的请求路径的接口(即忽略以它们开头的那些)。

作为非必要的讨论,考虑到上面的正则表达式不够直观,还可以把排除路径的逻辑用下面的更直观的代码来实现

  .paths(PathSelectors.any())
  .paths(PathSelectors.regex("/test/.*").negate())
  .build()

排除路径的方式可以有效地控制在接口文档中出现的内容——比如你可以给所有用于测试的接口的访问路径都加上 /test 前缀,给正在开发调试的接口的访问路径加上 /developing 前缀。这种方式的好处是对代码不具有侵入性,但坏处是你必须牢记定义的前缀规则。

42.6.1.5 只处理特定请求路径的所有接口

既然是正则表达式,当然也可以反过来定义只有符合路径要求的接口才能出现在文档中,如下面的代码定义要求所有以 /api 为路径前缀的接口才能出现在文档中

  .paths(PathSelectors.any())
  .paths(PathSelectors.regex("/api/.*"))
  .build()

使用这种方式的逻辑就是约定一个或多个确定的前缀,所有正式对外发布的接口都冠以约定的前缀,那些临时测试或者开发中的接口都不用这些前缀即可。这种方式的好处是对代码不具有侵入性,但坏处是你必须牢记定义的前缀规则。

42.6.1.6 只处理被特定注解标注的类

除了指定特定代码包以外,还可以使用 RequestHandlerSelectors.withClassAnnotation() 方法指定只处理那些被特定注解标注的类。

下面的定义里面是设置只处理被 @RestController 注解标注的类。当然这只是为了举例,这么做的用途并不大,毕竟这个注解的使用范围太大,没有明显的区分度。想要采用这种方式的话,最好自己定义一个专门的注解。

.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))

这种方式是最不应该使用的方法,讨论它只是为了列举一种可能性。使用标准注解没有很好的区分度,而使用自定义注解的的侵入性太大,也提高了对开发的要求。预期用自定义注解来标注,不如约定特定的接口路径前缀。

42.5.1.7 使用 @ApiOperation 注解的 hidden 属性

@ApiOperation 注解有一个 hidden 属性,它的默认值为 false,如果把它设置为 true,则被标注的接口不会出现在文档中

    @ApiOperation(
            value = "创建用户",
            notes = "一个创建用户的接口,一次性运行不该暴露出去",
            nickname = "createUser",
            hidden = true
    )
    @RequestMapping("/createUser")
    public String createUser() {

因为这种方法有非常强的侵入性,并且依赖与 @ApiOperation 注解,所以它不应该作为主要的方法来使用。如果想要临时隐藏某个已经发布的接口(已经使用了 @ApiOperation 注解), 可以使用这种方法。

42.6.2 忽略方法的特定参数

有时候我们还会需要在接口文档中忽略一些特别的接口参数。实现这个目标主要有两种方法:

  • 在方法的参数前添加 @ApiIgnore 注解
  • 在配置代码中指定忽略参数中特定的类对象

    42.6.2.1 注解标记忽略接口参数

    我们在 /aip/user/web-info 接口使用了 Srping Security 自动注入的 Authentication 对象
    @GetMapping("/user/web-info")
    public void getUserWebInfo(Authentication authentication) {
    
    在文档中可以看到它的内容
    image.png
    因为这个参数是自动注入的,它不应该出现在接口文档中,可以直接在参数定义处使用 @ApiIgnore 注解告诉 Swagger 忽略它
    @GetMapping("/user/web-info")
    public void getUserWebInfo(@ApiIgnore Authentication authentication) {
    

    注意:这里的顺序很重要,如果在参数那里有多个注解,@ApiIgnore 必须是最右边的,否则它不会生效。

下面这个接口文档现在的样子
image.png

类似的,所有使用 HttpSession 参数的接口的文档都会默认显示这个参数的相关信息。同样可以用 @ApiIgnore 注解去隐藏它(HttpServletRequest 和 HttpServletResponse 会被自动忽略,无须专门处理)。

42.6.2.2 忽略参数中特定类对象

HttpSession 这样的参数在开发中可能会被大量使用,而在所有的 HttpSession 参数都不应该出现在接口文档中。给每一个这样的参数添加注解的工作过于繁琐,也很难避免错漏。针对这种情况,SpringFox 在我们创建 Docket 对象时,提供统一指定忽略特定类对象参数的方法 ignoredParameterTypes ,这样就不需要在每个接口都加注解。

Docket docket=new Docket(DocumentationType.OAS_30)
  .enable(true)
  .groupName(groupName)
  .pathMapping("/")
  .select()
  .apis(RequestHandlerSelectors.any())
  .apis(RequestHandlerSelectors.basePackage("com.longser.union.cloud"))
  .paths(PathSelectors.any())
  .paths(PathSelectors.regex("/api/.*"))
  .build()
+ .ignoredParameterTypes(HttpSession.class)
  .apiInfo(getApiInfo());

前文提到 HttpServletRequest 和 HttpServletResponse 类型的参数会被自动忽略。此外,ServletRequest|、HttpHeaders、BindingResult、ServletContext、UriComponentsBuilder 也都会被自动忽略。

42.7 定义全局的消息响应状态

在接口文档中会列出可能出现的响应状态、状态说明和返回的类型,一般默认情况下我们在文档上看见的效果图如下:
image.png
因为我们已经定义了全局的异常管理,可以通过项目设计文档来说明各种返回状态的含义以及返回的类型。但只能阅读接口文档的人无法了解这些信息,而且这样的接口文档也不完全满足项目交付的要求。为此我们针对接口文档设置全局的消息响应状态说明。

首先 在类 SwaggerConfig 中定义一个方法,返回包含 Swagger 响应配置的列表

    private List<Response> getGlobalResponses() {
        List<Response> responseList = new ArrayList<>(3);

        Consumer<RepresentationBuilder> consumer = representationBuilder ->
                representationBuilder.model(modelSpecificationBuilder ->
                        modelSpecificationBuilder.referenceModel(referenceModelSpecificationBuilder ->
                                referenceModelSpecificationBuilder.key(modelKeyBuilder ->
                                        modelKeyBuilder.qualifiedModelName(qualifiedModelNameBuilder ->
                                                qualifiedModelNameBuilder.namespace(RestfulResult.class.getPackage().getName())
                                                        .name(RestfulResult.class.getSimpleName())))));

        responseList.add(new ResponseBuilder()
                .code("401")
                .description("需要登录")
                .representation(MediaType.APPLICATION_JSON)
                .apply(consumer)
                .build());

        responseList.add(new ResponseBuilder()
                .code("403")
                .description("无权访问")
                .representation(MediaType.APPLICATION_JSON)
                .apply(consumer)
                .build());

        responseList.add(new ResponseBuilder()
                .code("404")
                .description("没有该资源")
                .representation(MediaType.APPLICATION_JSON)
                .apply(consumer)
                .build());

        return responseList;
    }

这段代码中 consumer 的写法非常晦涩复杂,不过你不用仔细了解它的原理,只要留意它最后传入类名称的方法,以及知道它是用于 apply() 方法的参数。

然后把定义好的列表数据放到 Swagger 的配置中

Docket docket=new Docket(DocumentationType.OAS_30)
  .enable(true)
  .groupName(groupName)
  .pathMapping("/")
  .select()
  .apis(RequestHandlerSelectors.any())
  .apis(RequestHandlerSelectors.basePackage("com.longser.union.cloud"))
  .paths(PathSelectors.any())
  .paths(PathSelectors.regex("/api/.*"))
  .build()
  .ignoredParameterTypes(HttpSession.class)
+ .additionalModels((new TypeResolver()).resolve(RestfulResult.class))
+ .globalResponses(HttpMethod.POST, getGlobalResponses())
+ .globalResponses(HttpMethod.GET,  getGlobalResponses())
  .apiInfo(getApiInfo());

提示:globalResponseMessage()方法已经被废弃,所以即便不指定响应的数据类型也不不要使用这个方法

完成上面的配置后,除了默认的 200 状态不会被覆盖,其它默认给定的状态码都会使用自定义的内容。

42.8 使用Knife4j增强特性优化接口文档

前几节讨论的各种注解和配置方法都是 SpringFox Swagger 提供的功能。本节我们讨论如何使用 Knife4j 提供的增强特性进一步地优化接口文档。

42.8.1 开启Knife4j增强特性

Knife4j 自2.0.6版本开始,将在原来在界UI面中一些个性化配置剥离,我们可以在后端进行配置。它的增强功能需要通过配置yml配置文件开启

knife4j:
  enable: true

除了这个设置,还需要在创建 Docket 对象时调用 Knife4j 提供的扩展进行赋值。

首先在 SwaggerConfig 类中定义新的成员变量和方法

    /*引入Knife4j提供的扩展类*/
    private final OpenApiExtensionResolver openApiExtensionResolver;

    @Autowired
    public SwaggerConfig(OpenApiExtensionResolver openApiExtensionResolver) {
        this.openApiExtensionResolver = openApiExtensionResolver;
    }

然后在 swaggerDocketConfig 定义一个变量

String groupName = "Spring Boot 教程";

生成 Docket 对象时添加这个扩展

- .apiInfo(getApiInfo());
+ .apiInfo(getApiInfo()) 
+ .extensions(openApiExtensionResolver.buildExtensions(groupName));

buildExtensions 方法需要传入分组名称。该分组名称主要是为了区分开发者在构建自定义文档时,在不同的Docket逻辑分组下进行区别显示。

42.8.2 给接口说明添加作者信息

当有较多人参与后端开发时,我们会希望在接口文档中写明作者信息。这样前端或者后续其他人维护时可以知道找谁来讨论问题。

可以在接口上使用 knife4j 提供的增强注解 @ApiOperationSupport 添加作信息

    @PostMapping("/login")
    @ApiOperationSupport(author = "David")
    public String login(HttpServletRequest request,

注解生效后,可以在接口文档中看到设置的内容
image.png

你也可以在控制器类上使用 knife4j 提供的增强注解 @ApiSupport 为类中所有的接口添加作者信息。

如果同时使用 @ApiSupport 和 @ApiOperationSupport 注解,则作用在接口上的 @ApiOperationSupport 优先级更高。

下面给类 LoginController 添加作者信息

@Validated
@RequestMapping("/api")
@Api(tags = "和用户用户登录及用户管理有关的接口")
@ApiSupport(author = "David Jia")
public class LoginController {

在接口文档中可以看到除了 /api/login 接口以外,其他接口的作者信息都被设置成 David Jia
image.png

42.8.3 对控制器和接口排序

SpringFox Swagger 在控制器列表和接口列表中会分别按照控制器和接口的名字排序。有时我们会希望按照某种和业务相关的逻辑排列他们的顺序。使用 Knife4j 的增强特性可以满足这一要求。

42.8.3.1 指定接口的排序

@ApiOperationSupport 注解还有一个 order 参数用来给一个控制器内的接口排序。order 参数值是一个1到999的整数,默认值为0,排序的时候按照降序排列,order 值高的排在接口列表的前面。

    @ApiOperationSupport(author = "David",order = 99)
    public String login(HttpServletRequest request,

做了这样的配置后,这个登录接口就排在 LoginController 控制器的接口列表的最前面了
image.png

42.8.3.2 指定控制器的排序

@ApiSupport 注解也有一个 order 参数用来给决定控制器类在列表中的排序。order 参数值是一个1到999的整数,默认值为0,排序的时候按照降序排列,order 值高的排在控制器列表的前面。

@RestController
@Validated
@RequestMapping("/api")
@Api(tags = "和用户用户登录及用户管理有关的接口")
@ApiSupport(author = "David Jia", order = 99)
public class LoginController

做了这样的设置以后,这个控制器就在排在控制器列表的最前面了
image.png

42.8.4 过滤对象参数的属性

通常我们在开发接口时会直接把数据实体类对象做为参数,但对象的有些属性在某种场景下是用不到的。最典型的一个实例就是对于一个新增数据的接口来说,实体类对象中的主键id是用不到的,此时在新增接口时显示主键id不仅会显得很多余,还可能会造成无解解。

42.8.4.1 过滤对象参数属性的规则

Knife4j 定义的增强注解 @ApiOperationSupport 有一个 ignoreParameters 属性可以强制忽略要显示的参数。忽略的规则如下:

  • 如果参数层级只是一级,并且参数是实体类的情况下,不需要设置参数名称,直接给定属性值名称即,例如:
    ignoreParameters={“id”}

  • 如果存在多个层次的参数过滤,则使用 名称.属性 的方式。如在实体参数类对象中有一个名为 uptModel 的属性,它本身也是一个类对象,则使用下面的定义方式:
    ignoreParameters={“uptModel.id”,”uptModel.uptPo.id”}

  • 如果要忽略的实体类属性是类似 List 这种数组形式,那么在属性后面需要追加一个下标[0]
    ignoreParameters={“uptModel.uptPo[0].id”}

在接口过滤时,主要有表单参数和 JSON 参数两种情况。

42.8.4.2 过滤表单参数

我们在使用实体类直接作为参数时,在的UI界面中是不会显示参数名称的,此时可以直接使用实体的属性名称进行参数过滤:

    @PostMapping("newuser")
    @ApiOperation(value = "添加新用户的接口")
    @ApiOperationSupport(order = 999, ignoreParameters = {"id"})
    public UserEntry addNewUser(UserEntry user) {
        return user;
    }

生效后在请求参数中不再显示 id 参数
image.png
如果请求参数是使用JSON的方式,过滤方法相同

    @PostMapping("newuser")
    @ApiOperation(value = "添加新用户的接口")
    @ApiOperationSupport(order = 999, ignoreParameters = {"id"})
    public UserEntry addNewUser(@RequestBody UserEntry user) {
        return user;
    }

42.8.4.3 包含请求参数

如果需要忽略的参数太多,写很多忽略参数属性也是一个复杂且不易维护的事情。此时,一个与忽略参数对立取反的特性就显得很有帮助了。使用 Knife4j 增强注解 @ApiOperationSupport中的 includeParameters 参数可以强制只包含要显示的参数,去除多余的参数显示。

42.8.5 清除文档缓存

Knife4j 的缓存全部存储在浏览器中的 IndexedDB 中,所以通过浏览器的强制刷新等操作是无法起到清理缓存的作用。在文档升级、或者产生一些莫名其妙的问题时,可以点击文档右上角的清除缓存操作

42. 生成和管理接口文档 - 图27

当然你也可以手工删除该缓存值,然后再刷新 Knife4j 的文档

42.8.6 搜索API接口文档

在文档的右上角,Knife4j 提供了文档检索的功能
42. 生成和管理接口文档 - 图28
目前 Knife4j 的检索范围主要包括接口的地址、接口名称、接口描述,仅对当前分组下的已经加载的接口有效。对于分组中的接口,没有加载的时候是搜索不到的。换句话说,该检索功能并非是全局检索,只对你已经看到的所有接口列表进行检索。

如果你想要禁用这个搜索功能,需要通过增强属性进行配置

knife4j:
  enable: true
  setting:
    enableSearch: false

42.8.7 个性化设置

42.8.7.1 个性化设置对话框

点击右上角菜单中的个性化设置或者左侧文档管理中的个性化设置,可以打开个性文化设置对话框
image.png image.png
下面是个性化设置的界面
image.png

42.8.7.2 启停请求参数缓存

在默认情况下,Knife4j 对接口调试时用过的请求参数都会缓存起来。该配置通过个性化设置开启或停止。
image.png
缓存的情况只会在后端没有设置属性example的情况下产生。如果后端在写Swagger的注解的时候给每个字段赋予了example的值,那么Knife4j不会使用调试时缓存的内容,而是会一直使用后端的example值。例如后端Java实体类如下情况:

public class SwaggerRequestBody{
    @ApiModelProperty(value="姓名",example="张飞")
    private String name;

    //more...
}

对于上面的代码示例,Knife4j在每一次打开该接口的请求参数值默认都是“张飞”

你也可以在 Knife4j 的配置参数中启停这个功能

knife4j:
  enable: true
  setting:
    # 对于调试中的请求参数是否缓存进行开启配置,该参数默认为true
    enableRequestCache: true

42.8.7.3 启停动态请求参数功能

在某些特定的情况下,但是如果后端定义的是一种Map结构,或者是参数并没有做明确的声明,若希望能够通过动态添加参数来调试接口,可以使用这个动态请求参数的功能。

在Knife4j 的个性化设置功能里可以开启对参数的动态调试(该选项默认是关闭状态)。开启该功能后,在原本已存在的参数栏下会出现一栏空的参数栏,开发者可以输入参数名称、参数值对参数进行添加。不管是参数名称的变化还是参数值的变化,变化后会自动追加一行新的调试栏参数。此时前端的体验有点类似于Postman。

42.8.7.4 自定义Swagger Models名称

Knife4j 在界面左侧的菜单中设置了 Open API 规范中的模型结构Swagger Models,对于接口文档展示的效果来说,可能很多人并不知道其具体所代表的含义,因此你可以自定义该菜单项的显示内容。

首先需要在application.yml配置文件中配置自定义名称

knife4j:
  enable: true
  setting:
    enableSwaggerModels: true
    swaggerModelName: 数据模型定义

重启后端后刷新管理界面,可以看到变化

image.png

42.8.7.5 自定义主页内容

Knife4j 允许你提供一个Markdown文件来自定义显示Home主页的显示内容,通过配置yml来进行开启,配置文件如下

knife4j:
  enable: true
  setting:
    enableHomeCustom: true
    homeCustomLocation: classpath:config/markdown/home.md

除非对文档首页有特别的要求,否则没必要用这个功能修改现在的首页内容.

42.8.7.6 自定义页面底部内容

Knife4jj 允许你自定义页面底部Footer的内容,可以更改为与本公司或者当前系统的相关说明,通过配置yml来进行开启,配置文件如下

knife4j:
  enable: true
  setting:
    enableFooter: false
    enableFooterCustom: true
    footerCustomContent: 【**北京中科朗思信息技术有限公司 版权所有**】

属性说明:

  • enableFooter: 禁用默认的Footer显示,如果要自定义的话该属性必须设置为false,否则不会生效
  • enableFooterCustom:该属性为Boolean值,默认false,如果开发者要自定义Footer内容,该选项设置为true
  • footerCustomContent: 最终在Ui界面底部显示的Footer内容,支持Markdown格式

    42.8.7.7 增加自定义文档

    为了满足文档的更多的个性化配置,Knife4j 设计了添加了自定义文档功能。你能够通过提交自己灵活撰写的内容对接口文档进行更加更清晰的描述。

你可以在当前项目中添加多个文件夹,文件夹中存放.md扩展名格式的markdown格式的文件。每个代文件表一份自定义文档说明

注意:自定义文档说明必须使用 .md 扩展名,其他文扩展名的文件会被忽略。

在每个.md文件中,允许一级(h1)、二级(h2)、三级(h3)标题作为最终的文档标题,如果没有按照一级(h1)、二级(h2)、三级(h3)来设置标题,默认标题会是文件名称。

现在我们在src/main/resources/markdown/目录下张志一个explain.md文档:

# 自定义文档说明

## 效果说明

`knife4j`为了满足文档的个性化配置,添加了自定义文档功能

开发者可自定义`md`文件扩展补充整个系统的文档说明

开发者可以在当前项目中添加一个文件夹,文件夹中存放`.md`格式的markdown文件,每个`.md`文档代表一份自定义文档说明

**注意**:自定义文档说明必须以`.md`结尾的文件,其他格式文件会被忽略

在application.yml 配置文件中做如下的配置

knife4j:
  documents:
    - group: Spring Boot 教程
      name: Spring Boot 教程文档
      # 某一个文件夹下所有的.md文件
      locations: classpath:markdown/*
    - group: Spring Boot 教程
      name: Spring Boot 教程文档 0.9
      locations: classpath:markdown/second.md

下面时生效后的自定义文档的效果
image.png

42.9 在接口文档中调试

在 SpringFox Swagger UI 中就提供了在接口文档中调试的功能,Knife4 同样设计了类似的功能,并且界面更加友好。

42.9.1 调试接口的方法

接口文档的调试功能和 Postman 非常相似,例如在设置了JWT方式登录的时候进行调试
image.png
image.png

image.png

42.9.2 设置全局参数

Knife4j 提供基于临时设置全局参数的功能。使用此功能,你可以做类似设置 token 参数等方面的事情。这个功能主要是方便你进行调试。目前全局参数功能主要提供两种参数类型:query(表单)、header(请求头)。
image.png

42.9.3 导出Postman数据

Knife4j 在每个接口的详情界面除中,除了文档调试两个Tab选项卡功能外,还设计了 Open选项卡。该选项卡主要是展示当前接口的 OpenAPI 规范结构。可你以直接点击界面中的复制按钮,将该接口复制导入到Postman工具中进行调试

42.9.4 禁用调试功能

如果你不希望有人通过接口文档执行调试功能,那么可以在 application.yml 配置文件中关闭它:

knife4j:
  enable: true
  setting:
    enableDebug: false

42.9.5 禁用OpenApi结构显示

开发者如果想要禁用接口文档界面中的 OpenAPI 功能,那么可以在 application.yml 配置文件中关闭它(如果你已经禁用调试,那么OpenAPI 默认也不会显示):

knife4j:
  enable: true
  setting:
    enableOpenApi: false

42.9.6 重要的说明

这里需要特别地强调一下:绝对不能完全依赖接口文档这个调试功能来执行测试和调试。在实际的项目开发中,一定要为你开发的接口编写自动化测试代码。即便是做手工测试的时候,也应该更多地使用类似 Postman 这样的工具 。

42.10 导出离线文档

显然,我们不仅需要可以在线浏览的接口文档,在项目管理归档和向客户交付项目时,还需要可以独立保存和打印的文档文件。为此 Knife4j 离提供了在线文档的导出功能——可以导出Markdown、HTML和Word格式的离线文档。

在前端界面中的文档管理 => 离线文档 菜单栏
image.png
直接点击下载Markdow,Knife4j就会为我们生成一份精细的MD文档,效果图如下:
image.png
Knife4j 没有提供导出 PDF 文件的功能,你可以自己把导出的 HTML 或者 Word 文件另存为 PDF 文件。也可以在 Chrome中安装 Markdown Preview Plus 插件在预览 Markdown 文件之后另存为 PDF 文件。

  1. 在 Chrome中安装 Markdown Preview Plus 插件

https://chrome.google.com/webstore/detail/markdown-preview-plus/febilkbfcbhebfnokafefeacimjdckgl

image.png

  1. 在管理扩展程序中允许插件访问文件网址

image.pngimage.png
image.png

  1. 在插件选项中选择Github 的 CSS 方案(或其他你喜欢的)

image.pngimage.png

  1. 打开Markdown 文件并打印另存为 PDF

image.png

42.11 设置日志级别

如果你因调试等目的设置了较为详细的全局的日志级别,会在启动的时候看到类似下面的内容
image.png
这是 SpringFox Swagger 在 Spring 容器初始化完成后解析接口信息,将的接口信息最终转换为 SpringFox 的缓存对象 Documentation,而这些信息其实时你无须关心的。可以通过下面的配置单独设置日志的级别:

# FATAL > ERROR > WARN > INFO > DEBUG > TRACE
logging:
  level:
    root: debug
    springfox.documentation: warn

42.11 在生产环境屏蔽文档

一般来说,我们不希望(也不应该)在正式的生成环境中开放接口文档的功能。

根据我们现在的逻辑,当把 spring.profiles.active 设置为 prod 时,下面这个数组的地址不会被放行

    private final String[] developmentApi = {
            "/api/createUser",
            "/v3/api-docs/**",
            "/swagger-resources/**",
            "/doc.html",
            "/webjars/**",
    };

但如果仅仅依靠这个设置,任意用户登录以后还是可以访问接口问题。

那我们在给配置类 SwaggerConfig 增加的那个注解 @Profile({"dev"}) 是否管用呢?

@Configuration
@Profile({"dev"})
public class SwaggerConfig {

答案是仍旧不行。因为在没有执行配置的情况下, Swagger 会以默认的方式运行,所以要彻底关闭它,必须进行如下的设置

knife4j:
  # 开启增强配置 
  enable: true
 # 开启生产环境屏蔽
  production: true

配置此属性后所有相关资源的输出都会被屏蔽。

42.12 总结与提示

通过本章的讨论,可以看到 SpringFox Swagger 和 Knife4j 确实是功能强大又方便快捷的接口文档管理工具,使用它可以非常大的提高开发协同的效率,并且它生成的结果也可以直接作为项目文档保存及交付。

在使用 Swagger 的过程中,有如下的要点需要注意:

  • 要在项目建设的初始设计阶段就明确接口路径的规则,然后选择路径白名单或者黑名单的方式决定接口文档的处理规则。
  • 不要用接口文档的测试功能代替自动化测试代码以及手工测试工具。
  • Swagger 所有的配置和注解都是用来处理文档生成逻辑的,不会对代码逻辑有影响和侵入,所以不要把这些和实际代码开发的逻辑混淆。
  • 公共接的口文档的服务应该部署在内部的开发测试服务器而不是软件工程师的个人开发机上。
  • 就我们自己的项目特点来看,在开发环境中不必对 Swagger 进行访问权限限制。但可以在设置能够放问他的源 IP 地址。
  • 在项目发布、部署在正式的环境时,应该关闭 Swagger 功能,绝对不要在正式上线的应用系统中开放 Swagger 访问。
  • 使用 Swagger 这种版本功能差异较大的开源软件的时候,不能盲目相信网络上搜索到的信息,应该尽可能第一时间去查询官方文档。甚至于阅读官方文档也要多加小心。
  • 尽管 Knife4j 提供了对动态请求参数和动态响应参数的支持,但实际上你开发的代码中就不应该使用这种设计方式。

虽然很多人对 Swagger 这样的的自动化接口文档工具不满意,说他不好用,代码入侵强,等等,但它毕竟建立起了一套不错的接口规范,供了自动生成接口文档的解决方案,其价值就远超它本身的缺点。

有人会刻意地追求“无侵入”式的自动生成接口文档。但其实这个除了能够满足一部分片面的癖好以外,并没有额外的好处。后端工程师还是需要有自主影响文档结果的手段,比如你可能不想把某个正在开发调试中的接口展现在文档中,这样的需求通过 Java DOC 是无法表达出来的。

42.13 附录

42.13.1 重要的参考信息

42.13.2 其他接口管理工具

YApi 是一个高效、易用、功能强大的 API 管理平台(非自动化生成平台),旨在为开发、产品、测试人员提供更优雅的接口管理服务。尤其可用于 API 的设计阶段,可以帮助开发者轻松创建、发布、维护 API。YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。

下面是它的代码地址和官方网站

YApi具有如下特性

  • 基于 Json5 和 Mockjs 定义接口返回数据的结构和文档,效率提升多倍
  • 扁平化权限设计,即保证了大型企业级项目的管理,又保证了易用性
  • 类似 postman 的接口调试
  • 自动化测试, 支持对 Response 断言
  • MockServer 除支持普通的随机 mock 外,还增加了 Mock 期望功能,根据设置的请求过滤规则,返回期望数据
  • 支持 postman, har, swagger 数据导入
  • 免费开源,内网部署

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。