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
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
demo-knife4j-openapi3/src/main/resources/application.yml
springdoc 相关配置:
# spring-doc 接口文档 springdoc: api-docs: enabled: true # 是否启用接口文档 knife4j: enable: true # 是否启用 knife4j 增强,如果只是使用 knife4j 的 UI,则可以关闭
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")); } }
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 注解代替
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 注解区别比较大的地方。
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 类型,否则也无法生成对应泛型类的属性文档。
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 接口文档界面进行正常请求。
Migrating from SpringFox
swagger(三):统一返回结果不显示字段说明
springdoc-openapi 在 1.6.11 版本中添加了一个配置,可以一键解决不使用 @ParamterObject 注解 Swagger 接口测试不可用的问题,配置如下:
springdoc: # 默认是false,需要设置为true default-flat-param-object: true
然而这么做之后,所有通过 body 传的参数也会变成请求头传递,这只是把问题转移了,并没有从根本上解决问题。
针对这一点,对于想要尝鲜的用户还是要注意书写规范,否则改动量就非常大了。
Knife4j v4.0版本针对参数解析ParameterObject的问题说明
http://127.0.0.1:9050/v3/api-docs
地址:
http://127.0.0.1:9050/doc.html
效果图:
地址:
http://127.0.0.1:9050/swagger-ui/index.html
效果图:
Knife4j 官方文档: https://doc.xiaominfo.com/docs/quick-start
Springdoc 官方文档: https://springdoc.org
【超详细】springboot + springdoc-openapi + knife4j 集成案例
swagger(三):统一返回结果不显示字段说明
Knife4j v4.0版本针对参数解析ParameterObject的问题说明
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo/tree/master/demo-knife4j-openapi3
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.