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.***/docs/quick-start
Springdoc 官方文档: https://springdoc.org
2 核心 Maven 依赖
demo-knife4j-openapi3/pom.xml
<!-- knife4j openapi3 接口文档 -->
<dependency>
<groupId>***.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j-openapi3-spring.version}</version>
</dependency>
版本信息
<knife4j-openapi3-spring.version>4.3.0</knife4j-openapi3-spring.version>
注意事项:
本示例中使用的是 Springboot 2.7 版本,如果是 sprigboot 3.0 + 版本,则对应的 maven 依赖为:
<dependency>
<groupId>***.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
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/***/ljq/demo/springboot/knife4j/***mon/config/OpenApiConfig.java
package ***.ljq.demo.springboot.knife4j.***mon.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.***/Flying9001")))
.externalDocs(new ExternalDocumentation()
// 额外补充说明
.description("Github example code")
// 额外补充链接
.url("https://github.***/Flying9001/springBootDemo/demo-knife4j-openapi3"));
}
}
3.3 POJO 类使用示例
3.3.1 实体类
demo-knife4j-openapi3/src/main/java/***/ljq/demo/springboot/knife4j/model/entity/UserPushTypeEntity.java
package ***.ljq.demo.springboot.knife4j.model.entity;
import ***.baomidou.mybatisplus.annotation.TableName;
import ***.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/***/ljq/demo/springboot/knife4j/model/param/userpushtype/UserPushTypeSaveParam.java
package ***.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/***/ljq/demo/springboot/knife4j/***mon/api/ApiResult.java
package ***.ljq.demo.springboot.knife4j.***mon.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<T> {
/**
* 默认成功结果 key
*/
public static final String DEFAULT_SU***ESS_KEY = "api.response.su***ess";
/**
* 默认成功返回提示信息
*/
public static final String DEFAULT_SU***ESS_MESSAGE = "SU***ESS";
/**
* 默认错误结果 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<String, Object> 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<String, Object> extraDataMap) {
this.key = key;
this.message = message;
this.data = data;
this.extraDataMap = extraDataMap;
}
/**
* 成功
*
* @return
*/
public static <T> ApiResult<T> su***ess() {
return su***ess(null,null);
}
/**
* 成功
*
* @param data 返回数据
* @return
*/
public static <T> ApiResult<T> su***ess(T data) {
return su***ess(data, null);
}
/**
* 成功
*
* @param data 返回数据
* @param extraDataMap 额外返回数据
* @return
*/
public static <T> ApiResult<T> su***ess(T data, Map<String, Object> extraDataMap) {
return new ApiResult<>(DEFAULT_SU***ESS_KEY, DEFAULT_SU***ESS_MESSAGE, data, extraDataMap);
}
/**
* 失败
*
* @return
*/
public static <T> ApiResult<T> fail() {
return fail(DEFAULT_ERROR_KEY, DEFAULT_ERROR_MESSAGE, null, null);
}
/**
* 失败
*
* @param responseKey 返回结果 key
* @param message 返回提示信息
* @return
*/
public static <T> ApiResult<T> fail(String responseKey, String message) {
return fail(responseKey, message, null, null);
}
/**
* 失败
*
* @param responseKey 返回结果 Key
* @param message 返回提示信息
* @param data 返回数据
* @param extraDataMap 额外返回数据
* @return
*/
public static <T> ApiResult<T> fail(String responseKey, String message, T data, Map<String, Object> 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/***/ljq/demo/springboot/knife4j/controller/UserPushTypeController.java
package ***.ljq.demo.springboot.knife4j.controller;
import ***.baomidou.mybatisplus.core.metadata.IPage;
import ***.ljq.demo.springboot.knife4j.***mon.api.ApiResult;
import ***.ljq.demo.springboot.knife4j.model.entity.UserPushTypeEntity;
import ***.ljq.demo.springboot.knife4j.model.param.userpushtype.*;
import ***.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<ApiResult<UserPushTypeEntity>> save(@RequestBody @Validated UserPushTypeSaveParam saveParam) {
return ResponseEntity.ok(userPushTypeService.save(saveParam));
}
@GetMapping(value = "/query/info", produces = {MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "用户消息推送方式查询详情(单条)")
public ResponseEntity<ApiResult<UserPushTypeEntity>> info(@Validated @ParameterObject UserPushTypeInfoParam infoParam) {
return ResponseEntity.ok(userPushTypeService.info(infoParam));
}
@GetMapping(value = "/query/page", produces = {MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "用户消息推送方式查询列表")
public ResponseEntity<ApiResult<IPage<UserPushTypeEntity>>> list(@Validated @ParameterObject UserPushTypeListParam listParam) {
return ResponseEntity.ok(userPushTypeService.list(listParam));
}
@PutMapping(value = "/update", produces = {MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "用户消息推送方式修改(单条)")
public ResponseEntity<ApiResult> update(@RequestBody @Validated UserPushTypeUpdateParam updateParam) {
return ResponseEntity.ok(userPushTypeService.update(updateParam));
}
@DeleteMapping(value = "/delete", produces = {MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "用户消息推送方式删除(单条)")
public ResponseEntity<ApiResult> 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 isio.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(a***essMode = 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 withGroupedOpenApi
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 导出接口文档
5.3 Knife4j UI 界面
地址:
http://127.0.0.1:9050/doc.html
效果图:
5.4 Swagger UI 界面
地址:
http://127.0.0.1:9050/swagger-ui/index.html
效果图:
6 推荐参考资料
Knife4j 官方文档: https://doc.xiaominfo.***/docs/quick-start
Springdoc 官方文档: https://springdoc.org
【超详细】springboot + springdoc-openapi + knife4j 集成案例
swagger(三):统一返回结果不显示字段说明
Knife4j v4.0版本针对参数解析ParameterObject的问题说明
7 Github 源码
Gtihub 源码地址 : https://github.***/Flying9001/springBootDemo/tree/master/demo-knife4j-openapi3
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.