Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0
作者:mmseoamin日期:2023-12-13

文章目录

    • 1 摘要
    • 2 核心 Maven 依赖
    • 3 核心代码
      • 3.1 application 配置
      • 3.2 openApi 配置类
      • 3.3 POJO 类使用示例
        • 3.3.1 实体类
        • 3.3.2 请求参数
        • 3.3.3 公共返回参数(包含泛型)
        • 3.4 Controller 控制层示例
        • 4 升级注意事项(踩坑指南)
          • 4.1 Swagger 2 注解替换
          • 4.2 公共返回参数不能使用 `@Schema` 注解,否则就会只生成一个 `Swagger Model`
          • 4.3 Get 请求参数前需要添加 `@ParamterObject` 注解
          • 5 使用效果
            • 5.1 导出到 Postman 等测试工具的接口数据地址
            • 5.2 Knife4j 导出接口文档
            • 5.3 Knife4j UI 界面
            • 5.4 Swagger UI 界面
            • 6 推荐参考资料
            • 7 Github 源码

              1 摘要

              Swagger 作为一款服务端接口文档自动生成框架,早已深入人心,并且在市场上得到了广泛的应用。然而,Swagger 3.0 也就是 OpenApi 3.0 规范发布之后便停止了更新维护,出道就是巅峰。Knife4j 作为 Swagger 的增强版,是对 Swagger UI 做了优化,同时还有很多增强的功能。伴随着 Swagger 3.0 的停止更新,如今 Knife4j 从4.0开始已经逐渐使用 Springdoc 作为 swagger 的替代。Springdoc 针对 OpenApi 3.0 的适配做了较大的调整,其中注解与 Swagger 2 的基本不通用。作为新项目而言,使用社区维护活跃的开源框架显得非常重要。本文将介绍基于 Springboot 2.7 集成 Swagger 的增强框架 Knife4j 4.3 + Springdoc OpenApi 3.0 。

              Knife4j 官方文档: https://doc.xiaominfo.com/docs/quick-start

              Springdoc 官方文档: https://springdoc.org

              2 核心 Maven 依赖

              demo-knife4j-openapi3/pom.xml
              
                      
                      
                          com.github.xiaoymin
                          knife4j-openapi3-spring-boot-starter
                          ${knife4j-openapi3-spring.version}
                      
              

              版本信息

              4.3.0
              

              注意事项:

              本示例中使用的是 Springboot 2.7 版本,如果是 sprigboot 3.0 + 版本,则对应的 maven 依赖为:

              
                  com.github.xiaoymin
                  knife4j-openapi3-jakarta-spring-boot-starter
                  4.3.0
              
              

              3 核心代码

              3.1 application 配置

              demo-knife4j-openapi3/src/main/resources/application.yml
              

              springdoc 相关配置:

              # spring-doc 接口文档
              springdoc:
                api-docs:
                  enabled: true # 是否启用接口文档
              knife4j:
                enable: true # 是否启用 knife4j 增强,如果只是使用 knife4j 的 UI,则可以关闭
              

              3.2 openApi 配置类

              demo-knife4j-openapi3/src/main/java/com/ljq/demo/springboot/knife4j/common/config/OpenApiConfig.java
              
              package com.ljq.demo.springboot.knife4j.common.config;
              import io.swagger.v3.oas.models.ExternalDocumentation;
              import io.swagger.v3.oas.models.OpenAPI;
              import io.swagger.v3.oas.models.info.Contact;
              import io.swagger.v3.oas.models.info.Info;
              import org.springframework.context.annotation.Bean;
              import org.springframework.context.annotation.Configuration;
              /**
               * @Description: openapi 界面配置
               * @Author: junqiang.lu
               * @Date: 2023/8/15
               */
              @Configuration
              public class OpenApiConfig {
                  @Bean
                  public OpenAPI springShopOpenAPI() {
                      return new OpenAPI()
                              // 接口文档标题
                              .info(new Info().title("Knife4j OpenApi 3")
                                      // 接口文档描述
                                      .description("Knife4j OpenApi 3 example application")
                                      // 接口文档版本
                                      .version("v1.0")
                                      // 开发者联系方式
                                      .contact(new Contact().name("Flying9001").url("https://github.com/Flying9001")))
                              .externalDocs(new ExternalDocumentation()
                                      // 额外补充说明
                                      .description("Github example code")
                                      // 额外补充链接
                                      .url("https://github.com/Flying9001/springBootDemo/demo-knife4j-openapi3"));
                  }
                  
              }
              

              3.3 POJO 类使用示例

              3.3.1 实体类
              demo-knife4j-openapi3/src/main/java/com/ljq/demo/springboot/knife4j/model/entity/UserPushTypeEntity.java
              
              package com.ljq.demo.springboot.knife4j.model.entity;
              import com.baomidou.mybatisplus.annotation.TableName;
              import com.ljq.demo.springboot.knife4j.model.BaseEntity;
              import io.swagger.v3.oas.annotations.media.Schema;
              import lombok.Data;
              import lombok.ToString;
              /**
               * 用户消息推送方式实体类
               *
               * @author junqiang.lu
               * @date 2023-08-15 14:26:38
               */
              @Data
              @ToString(callSuper = true)
              @Schema(description = "用户消息推送方式")
              @TableName(value = "user_push_type")
              public class UserPushTypeEntity extends BaseEntity {
              	private static final long serialVersionUID = 1L;
              	@Schema(description = "用户id")
              	private Long userId;
              	@Schema(description = "推送方式,1-短信;2-邮件;3-app;4-wechat")
              	private Integer pushType;
              	@Schema(description = "通知推送接收地址")
              	private String receiveAddress;
              	@Schema(description = "是否启用,0-未启用,1-启用")
              	private Integer enable;
              }
              

              原来 Swagger 2 的 @Apimodel 以及 @ApiModelPropertity 注解全部使用 @Schema 注解代替

              3.3.2 请求参数
              demo-knife4j-openapi3/src/main/java/com/ljq/demo/springboot/knife4j/model/param/userpushtype/UserPushTypeSaveParam.java
              
              package com.ljq.demo.springboot.knife4j.model.param.userpushtype;
              import io.swagger.v3.oas.annotations.media.Schema;
              import lombok.Data;
              import javax.validation.constraints.NotBlank;
              import javax.validation.constraints.NotNull;
              import java.io.Serializable;
              /**
               * 用户消息推送方式参数接收类
               *
               * @author junqiang.lu
               * @date 2023-08-14 17:17:19
               */
              @Data
              @Schema(description = "用户消息推送方式保存(单条)")
              public class UserPushTypeSaveParam implements Serializable {
                  private static final long serialVersionUID = 1L;
                  @NotNull(message = "用户id 不能为空")
                  @Schema(description = "用户id", requiredMode = Schema.RequiredMode.REQUIRED)
                  private Long userId;
                  @NotNull(message = "推送方式不能为空")
                  @Schema(description = "推送方式,1-短信;2-邮件;3-app;4-wechat", requiredMode = Schema.RequiredMode.REQUIRED)
                  private Integer pushType;
                  @NotBlank(message = "通知推送接收地址 不能为空")
                  @Schema(description = "通知推送接收地址", requiredMode = Schema.RequiredMode.REQUIRED)
                  private String receiveAddress;
                  @NotNull(message = "是否启用不能为空")
                  @Schema(description = "是否启用,0-未启用,1-启用", requiredMode = Schema.RequiredMode.REQUIRED)
                  private Integer enable;
              }
              

              @Schema 注解中参数是否必填使用了一个枚举类 io.swagger.v3.oas.annotations.media.Schema.RequiredMode 这是 @Schema 注解的一个内部类,支持 3 种模式, @Schema 注解中多种属性也采用了枚举类的方式,这是与原来的 @ApiModelPropertity 注解区别比较大的地方。

              3.3.3 公共返回参数(包含泛型)
              demo-knife4j-openapi3/src/main/java/com/ljq/demo/springboot/knife4j/common/api/ApiResult.java
              
              package com.ljq.demo.springboot.knife4j.common.api;
              import io.swagger.v3.oas.annotations.media.Schema;
              import lombok.Data;
              import org.slf4j.MDC;
              import java.util.Map;
              /**
               * @Description: 接口返回结果
               * @Author: junqiang.lu
               * @Date: 2020/9/3
               */
              @Data
              public class ApiResult {
                  /**
                   * 默认成功结果 key
                   */
                  public static final String DEFAULT_SUCCESS_KEY = "api.response.success";
                  /**
                   * 默认成功返回提示信息
                   */
                  public static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
                  /**
                   * 默认错误结果 key
                   */
                  public static final String DEFAULT_ERROR_KEY = "api.response.error";
                  /**
                   * 默认错误返回提示信息
                   */
                  public static final String DEFAULT_ERROR_MESSAGE = "ERROR";
                  /**
                   * 请求 id key
                   */
                  public static final String REQUEST_ID_KEY = "REQUEST_ID";
                  /**
                   * 返回结果 key
                   */
                  @Schema(description = "返回结果 key")
                  private String key;
                  /**
                   * 返回提示信息
                   */
                  @Schema(description = "返回提示信息")
                  private String message;
                  /**
                   * 返回数据
                   */
                  private T data;
                  /**
                   * 额外返回数据
                   */
                  @Schema(description = "额外返回数据")
                  private Map extraDataMap;
                  /**
                   * 系统当前时间(精确到毫秒)
                   */
                  @Schema(description = "系统当前时间(精确到毫秒)")
                  private Long timestamp = System.currentTimeMillis();
                  /**
                   * 请求 id
                   */
                  @Schema(description = "请求 id")
                  private String requestId = MDC.get(REQUEST_ID_KEY);
                  private ApiResult(){
                  }
                  /**
                   * 构造方法
                   *
                   * @param key
                   * @param message
                   * @param data
                   * @param extraDataMap
                   */
                  protected ApiResult(String key, String message, T data, Map extraDataMap) {
                      this.key = key;
                      this.message = message;
                      this.data = data;
                      this.extraDataMap = extraDataMap;
                  }
                  /**
                   * 成功
                   *
                   * @return
                   */
                  public static  ApiResult success() {
                      return success(null,null);
                  }
                  /**
                   * 成功
                   *
                   * @param data 返回数据
                   * @return
                   */
                  public static  ApiResult success(T data) {
                      return success(data, null);
                  }
                  /**
                   * 成功
                   *
                   * @param data 返回数据
                   * @param extraDataMap 额外返回数据
                   * @return
                   */
                  public static  ApiResult success(T data, Map extraDataMap) {
                      return new ApiResult<>(DEFAULT_SUCCESS_KEY, DEFAULT_SUCCESS_MESSAGE, data, extraDataMap);
                  }
                  /**
                   * 失败
                   *
                   * @return
                   */
                  public static  ApiResult fail() {
                      return fail(DEFAULT_ERROR_KEY, DEFAULT_ERROR_MESSAGE, null, null);
                  }
                  /**
                   * 失败
                   *
                   * @param responseKey 返回结果 key
                   * @param message 返回提示信息
                   * @return
                   */
                  public static  ApiResult fail(String responseKey, String message) {
                      return fail(responseKey, message, null, null);
                  }
                  /**
                   * 失败
                   *
                   * @param responseKey 返回结果 Key
                   * @param message 返回提示信息
                   * @param data 返回数据
                   * @param extraDataMap 额外返回数据
                   * @return
                   */
                  public static  ApiResult fail(String responseKey, String message, T data, Map extraDataMap) {
                      return new ApiResult<>(responseKey, message, data, extraDataMap);
                  }
              }
              

              这里需要注意的是公共返回结果类上不能使用 @Schema 注解,尤其是在指定 name 的情况下,这会使生成的接口文档没有对应泛型类的属性,所有的返回结果都是一样的,看不到返回结果具体对象的属性,增加了前后端对接成本,这不符合我们对于接口文档的要求。

              于此同时,对于泛型类 data 参数,也不要使用 @Schema 注解,对于没有使用 lombok 插件,而手动书写 getter/setter 的方式,data 的 getter 方法返回结果也必须是 T 而不能是 Object 类型,否则也无法生成对应泛型类的属性文档。

              3.4 Controller 控制层示例

              demo-knife4j-openapi3/src/main/java/com/ljq/demo/springboot/knife4j/controller/UserPushTypeController.java
              
              package com.ljq.demo.springboot.knife4j.controller;
              import com.baomidou.mybatisplus.core.metadata.IPage;
              import com.ljq.demo.springboot.knife4j.common.api.ApiResult;
              import com.ljq.demo.springboot.knife4j.model.entity.UserPushTypeEntity;
              import com.ljq.demo.springboot.knife4j.model.param.userpushtype.*;
              import com.ljq.demo.springboot.knife4j.service.UserPushTypeService;
              import io.swagger.v3.oas.annotations.Operation;
              import io.swagger.v3.oas.annotations.tags.Tag;
              import org.springdoc.api.annotations.ParameterObject;
              import org.springframework.http.MediaType;
              import org.springframework.http.ResponseEntity;
              import org.springframework.validation.annotation.Validated;
              import org.springframework.web.bind.annotation.*;
              import javax.annotation.Resource;
              /**
               * 用户消息推送方式
               * 
               * @author junqiang.lu
               * @date 2023-08-15 10:41:29
               */
              @RestController
              @RequestMapping(value = "/knife4j/user/pushtype")
              @Tag(name = "用户消息推送方式控制层")
              public class UserPushTypeController {
              	@Resource
              	private UserPushTypeService userPushTypeService;
                  @PostMapping(value = "/add", produces = {MediaType.APPLICATION_JSON_VALUE})
                  @Operation(summary = "用户消息推送方式保存(单条)")
                  public ResponseEntity> save(@RequestBody @Validated UserPushTypeSaveParam saveParam) {
                      return ResponseEntity.ok(userPushTypeService.save(saveParam));
                  }
                  @GetMapping(value = "/query/info", produces = {MediaType.APPLICATION_JSON_VALUE})
                  @Operation(summary = "用户消息推送方式查询详情(单条)")
                  public ResponseEntity> info(@Validated @ParameterObject UserPushTypeInfoParam infoParam) {
                      return ResponseEntity.ok(userPushTypeService.info(infoParam));
                  }
                  @GetMapping(value = "/query/page", produces = {MediaType.APPLICATION_JSON_VALUE})
                  @Operation(summary = "用户消息推送方式查询列表")
                  public ResponseEntity>> list(@Validated @ParameterObject UserPushTypeListParam listParam) {
                      return ResponseEntity.ok(userPushTypeService.list(listParam));
                  }
                  @PutMapping(value = "/update", produces = {MediaType.APPLICATION_JSON_VALUE})
                  @Operation(summary = "用户消息推送方式修改(单条)")
                  public ResponseEntity update(@RequestBody @Validated UserPushTypeUpdateParam updateParam) {
                      return ResponseEntity.ok(userPushTypeService.update(updateParam));
                  }
                  @DeleteMapping(value = "/delete", produces = {MediaType.APPLICATION_JSON_VALUE})
                  @Operation(summary = "用户消息推送方式删除(单条)")
                  public ResponseEntity delete(@RequestBody @Validated UserPushTypeDeleteParam deleteParam) {
                      return ResponseEntity.ok(userPushTypeService.delete(deleteParam));
                  }
              }
              

              在 Controller 类中,原来 Swagger 2 的 @Api 注解替换为 @Tag, 原来的 @ApiOperation 注解替换为 @Operation。

              在请求头中的参数,例如 GET 请求,如果是一个对象,则需要使用 @ParameterObject 注解,如果不添加,则无法在 Swagger 接口文档界面进行正常请求。

              4 升级注意事项(踩坑指南)

              4.1 Swagger 2 注解替换

              • Replace swagger 2 annotations with swagger 3 annotations (it is already included with springdoc-openapi-starter-webmvc-ui dependency). Package for swagger 3 annotations is io.swagger.v3.oas.annotations.
                • @Api → @Tag
                • @ApiIgnore → @Parameter(hidden = true) or @Operation(hidden = true) or @Hidden
                • @ApiImplicitParam → @Parameter
                • @ApiImplicitParams → @Parameters
                • @ApiModel → @Schema
                • @ApiModelProperty(hidden = true) → @Schema(accessMode = READ_ONLY)
                • @ApiModelProperty → @Schema
                • @ApiOperation(value = "foo", notes = "bar") → @Operation(summary = "foo", description = "bar")
                • @ApiParam → @Parameter
                • @ApiResponse(code = 404, message = "foo") → @ApiResponse(responseCode = "404", description = "foo")
                • This step is optional: Only if you have multiple Docket beans replace them with GroupedOpenApi beans.

                  Migrating from SpringFox

                  4.2 公共返回参数不能使用 @Schema 注解,否则就会只生成一个 Swagger Model

                  swagger(三):统一返回结果不显示字段说明

                  4.3 Get 请求参数前需要添加 @ParamterObject 注解

                  springdoc-openapi 在 1.6.11 版本中添加了一个配置,可以一键解决不使用 @ParamterObject 注解 Swagger 接口测试不可用的问题,配置如下:

                  springdoc:
                    # 默认是false,需要设置为true
                    default-flat-param-object: true
                  

                  然而这么做之后,所有通过 body 传的参数也会变成请求头传递,这只是把问题转移了,并没有从根本上解决问题。

                  针对这一点,对于想要尝鲜的用户还是要注意书写规范,否则改动量就非常大了。

                  Knife4j v4.0版本针对参数解析ParameterObject的问题说明

                  5 使用效果

                  5.1 导出到 Postman 等测试工具的接口数据地址

                  http://127.0.0.1:9050/v3/api-docs
                  

                  5.2 Knife4j 导出接口文档

                  Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0,knife4j-openapi3-export-1,第1张

                  5.3 Knife4j UI 界面

                  地址:

                  http://127.0.0.1:9050/doc.html
                  

                  效果图:

                  Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0,knife4j-openapi3-doc-1,第2张

                  5.4 Swagger UI 界面

                  地址:

                  http://127.0.0.1:9050/swagger-ui/index.html
                  

                  效果图:

                  Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0,knife4j-openapi3-swagger-1,第3张

                  6 推荐参考资料

                  Knife4j 官方文档: https://doc.xiaominfo.com/docs/quick-start

                  Springdoc 官方文档: https://springdoc.org

                  【超详细】springboot + springdoc-openapi + knife4j 集成案例

                  swagger(三):统一返回结果不显示字段说明

                  Knife4j v4.0版本针对参数解析ParameterObject的问题说明

                  7 Github 源码

                  Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo/tree/master/demo-knife4j-openapi3

                  个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.

                  Springboot 2.7 集成 Swagger 增强版接口框架 Knife4j 4.3 + springdoc OpenApi 3.0,404Code,第4张