SpringBoot应用生成RESTful API文档 - Swagger 2.0、OAS 3.0、Springfox、Springdoc、Smart-doc

SpringBoot应用生成RESTful API文档 - Swagger 2.0、OAS 3.0、Springfox、Springdoc、Smart-doc,第1张

SpringBoot应用生成RESTful API文档 - Swagger 2.0、OAS 3.0、Springfox、Springdoc、Smart-doc

目录

1. Swagger及OpenAPI2. Springfox & Springdoc & Smart-doc

2.1 Springfox2.2 Springdoc2.3 smart-doc2.4 总结 3. Springfox2集成Swagger24. Springfox3集成OAS 3.05. Springdoc集成OAS 3.06. Smart-doc + Springdoc集成OAS 3.0

6.1 openapi.json集成Knife4j 7. springdoc-openapi-javadoc对比smart-doc


通常开发初期架构师需要给出DB设计、API接口设计,
而API接口设计或者以文档(word文档、markdown等)、或者以在线API文档(swagger-ui、yapi、showdoc等)形式给出,
以设计先行模式(本人也比较推崇此模式)为例,架构师通常通过如下两种方式输出API文档:

方式一:纯API文档编写(无代码输出)

编写Word文档(公司级接口文档模板) - 不好迁移,不是业界通用标准通过在线文档设计工具编写(yapi、showdoc等)- 方便统一管理,兼容通用标准如Swagger2.0、OAS3.0等 方式二:通过代码集成Swagger注解生成文档(侵入代码) - 同时输出:API接口文档、程序的接口框架(理想情况下文档和代码同步)

直接以swagger-ui进行文档展示将swagger.json文件导入到其他在线文档平台进行统一管理

记得N年之前用的是公司统一规范的word文档进行API文档编写,
后续迁移到了在线文档平台YAPI(兼容Swagger 2.0),
迁移过程中写了工具把word文档转换成swagger.json,然后再导入YAPI中(在此基础上再手动修改),
后续用的比较多的就是直接在YAPI编写文档。
之前一直不用Swagger是觉得这个东西太重了,需要在代码中添加好多和业务无关的注解,
但是Swagger这个文档规范还是很通用的(业界标准),
在编写Swagger注解的同时也是在定义程序接口框架(输出代码,提高后续开发效率),
所以就萌生了不通过Swagger注解 而是结合Java代码注释就可以生成Swagger统一规范文档(后续可导入其他在线API平台)的想法,
注: 代码及注释可以借助DB定义通过代码生成工具进行生成,而后再进一步修改
本文就是在实现此种想法过程中的一些记录,
这个过程的探索也借助了业内比较通用的工具:

swaggerspringfox2、springfox3springdocsmart-doc

接下来依次对各工具进行介绍与集成示例讲解。


1. Swagger及OpenAPI

Swagger提供了一整套API设计、文档编写及展示的工具,提供开源版、企业版、cloud版,
开源版主要包括:

OpenAPI规范(简称OAS) - RESTful API的接口定义规范,目前支持OAS 2.0和OAS 3.0(前身也叫Swagger规范,在2015后捐赠给Linux Foundation后更名为OpenAPI)Swagger Core - 基于Java注解生成OAS文档(*.json或.yaml格式)Swagger UI - 根据OAS文档生成可视化文档展示Swagger Editor - API在线编辑OAS及文档生成Swagger Codegen - 根据OAS文档生成Server端、client端接口代码

OAS规范的发展历史见下表(目前最新版本为OAS 3.0.3):

版本发布日期说明3.0.32020-02-20Patch release of the OpenAPI Specification 3.0.33.0.22018-10-08Patch release of the OpenAPI Specification 3.0.23.0.12017-12-06Patch release of the OpenAPI Specification 3.0.13.0.02017-07-26Release of the OpenAPI Specification 3.0.03.0.0-rc22017-06-16rc2 of the 3.0 specification3.0.0-rc12017-04-27rc1 of the 3.0 specification3.0.0-rc02017-02-28Implementer’s Draft of the 3.0 specification2.02015-12-31Donation of Swagger 2.0 to the OpenAPI Initiative2.02014-09-08Release of Swagger 2.01.22014-03-14Initial release of the formal document.1.12012-08-22Release of Swagger 1.11.02011-08-10First release of the Swagger Specification

Swagger 2.0和OAS 3.0常用注解对比:

Swagger 2.0
包名:io.swagger.annotationsOAS 3.0
包名:io.swagger.v3.oas.annotations注解常用位置@Api@Tag(name = “接口类描述”)Constroller类上@ApiOperation(value = “foo”, notes = “bar”)@Operation(summary = “foo”, description = “bar”)Controller方法上@ApiImplicitParams@ParametersController方法上@ApiImplicitParam@Parameter(name = “id”, description = “用户ID”, example = “1”, in = ParameterIn.QUERY)Controller方法上、参数前、@Parameters里@ApiParam@ParameterController方法参数前 或者 @Operation.parameters里@ApiResponse(code = 404, message = “foo”)@ApiResponse(responseCode = “404”, description = “foo”)Controller类上、方法上、@Operation.responses里@ApiIgnore@Parameter(hidden = true)
@Operation(hidden = true)
@HiddenController类上、方法上、Model对象上、属性上@ApiModel@Schema(description = “对象描述”)Model对象类上@ApiModelProperty(hidden = true)@Schema(accessMode = READ_ONLY)Model对象属性上@ApiModelProperty@Schema(description = “用户性别(1:男,2:女)”, required = true, example = “1”)Mode对象属性上

2. Springfox & Springdoc & Smart-doc

Springfox 和 Springdoc均是spring社区(非官方)开发的,
支持在Spring生态中根据 SpringMvc代码、Swagger注解、JSR303注解(@NotNull, @Min, @Max, @Size) 自动生成接口文档的工具,并且支持集成Swagger UI。
而Smart-doc则是国内开源的根据SpringMvc代码、Java源码注释、JSR303注解、泛型推导等自动生成接口文档的工具。

2.1 Springfox

Springfox支持Swagger 2.0 和 OAS 3.0规范,
对Swagger 2.0的支持较为成熟,比较流行,
但对OAS 3.0的支持并不完善(可参见springfox 3.0整合OAS 3.0及其问题),
目前社区也不是很活跃(目前最新版本3.0.0发布在2020-07-14,已经1年多没有更新了)。

SpringFox 3.0.0 的新特性:

支持Spring5、Webflux、Spring Integration支持Springboot零配置启动 springfox-boot-starter支持OAS 3.0.3且兼容Swagger 2.0(注:对OSA 3.0支持不是很完善,且github上已好久没有更新)精简依赖(仅依赖spring-plugin, swagger-core) 2.2 Springdoc

而Springdoc仅支持OAS 3.0规范,但对OAS 3.0的支持比较完善,
社区较为活跃(目前最新版本1.6.4发布在2022-01-06),
Springdoc核心特性如下:

支持OpenAPI 3(不支持Swagger 2.0)支持Spring-boot (v1 and v2)支持JSR-303验证注解(@NotNull, @Min, @Max, @Size)支持集成Swagger-ui支持OAuth 2支持GraalVM native images

从 SpringFox 迁移到 SpringDoc
可参见官网文档:Migrating from SpringFox

2.3 smart-doc

Smart-doc为国内开源的根据Java源码注释、JSR303注解、泛型推导等自动生成接口文档的工具。你只需要按照java-doc标准编写注释,smart-doc就能帮你生成一个简易明了的Markdown、HTML5、Postman Collection2.0+、OpenAPI 3.0+的文档。
smart-doc最吸引我的特性就是基于注释,而不是基于注解,

我们在写Java代码的时候,都要求书写规范的文档注释,
而使用Swagger生态则需要侵入代码再书写大量swagger-core注解,
如@Api, @ApiOperation,…,@Tag,@Operation,…,增加了一定学习成本,
作为开发人员会觉得很痛苦,同样的说明功能被做了2次(注释、注解),后续维护也要修改2次,
而且代码中需要添大量和业务无关的Swagger-core注解,
而使用smart-doc可以直接提取我们文档中的注释来生成API文档,不需要再添加额外的注解。

同时smart-doc支持生成多种文档格式:

如可以直接展示的(支持group):HTML5、Markdown、Adoc用于Postman调试的:Postman Collection2.0+OAS 3.0生态:OpenAPI 3.0+

smart-doc的相关特性如下:

零注解、零学习成本、只需要写标准JAVA注释。基于源代码接口定义自动推导,强大的返回结构推导。支持Spring MVC、Spring Boot、Spring Boot Web Flux(controller书写方式)、Feign。支持JavaBean上的JSR303参数校验规范,包括分组验证。支持导出错误码和定义在代码中的各种字典码到接口文档。支持Maven、Gradle插件式轻松集成。支持Apache Dubbo RPC接口文档生成。支持生成多种格式文档:Markdown、HTML5、Asciidoctor、Postman Collection、OpenAPI 3.0。debug接口调试html5页面完全支持文件上传,下载(@download tag标记下载方法)测试。开放文档数据,可自由实现接入文档管理系统。支持Callable、Future、CompletableFuture等异步接口返回的推导。对JSON请求参数的接口能够自动生成模拟JSON参数。对一些常用字段定义能够生成有效的模拟值。支持生成JSON返回值示例。支持从项目外部加载源代码来生成字段注释(包括标准规范发布的jar包)。

smart-doc关于注释也支持一些特殊格式:

@apiNote来说明详细描述(长注释)@tag表示分类、分组@mock指定基本类型mock值@required、@ignored、@ignoreParams@download、@page参数对象替换

@param pageable com.power.doc.model.PageRequestDto@param pageable 你的注释|com.power.doc.model.PageRequestDto@param pageable com.power.doc.model.PageRequestDto 设置参数mock值

@param author 作者|村上春树 …

实际使用smart-doc中遇到的问题:

@RequestMapping等注解value属性若指定为数组,则会解析path为: “[/api/v1/xxx]”,指定value为字符串时会被正确解析为:"/api/v1/xxx"分组配置groups不支持openapi、postman模式分组配置groups.apis只支持包名格式(不支持到特定类名),如:com.luo.controller.biz1.*关于OpenApi支持有限,如server定义过于简单,不支持多server定义关于源码包的解析也有要求,可提供源码包、手动exclude解析报错的包 2.4 总结 框架Swagger 2.0OAS 3.0依赖Swagger注解
(侵入代码)依赖Java注释
(不侵入代码)社区活跃度官网及源码仓库springfox✔️springfox2
成熟✔️springfox3
OAS 3.0支持不完善,兼容Swagger 2.0✔️❌不活跃
最新版本3.0.0
发布时间2020-07-14http://springfox.github.io/springfox/
https://github.com/springfox/springfoxspringdoc❌✔️springdoc
仅支持OAS 3.0✔️❌活跃
最新版本1.6.4
发布时间2022-01-06https://springdoc.org/
https://github.com/springdoc/springdoc-openapismart-doc❌✔️smart-doc
支持根据注释生成OAS 3.0文档❌✔️活跃
最新版本2.3.6
发布时间2022-01-02https://smart-doc-group.github.io/#/zh-cn/?id=smart-doc
https://github.com/smart-doc-group/smart-doc
https://gitee.com/smart-doc-team/smart-doc

综上,

【不推荐】继续使用Swagger 2.0则可继续使用Springfox 2.x用过的稳定版本【推荐】现阶段推荐接入OAS 3.0,则Springdoc + Swagger Core 3.0注解【⭐️最推荐⭐️】推荐smart-doc openapi模式(集成Springdoc Swagger-ui展示 或者 导入到其他文档平台统一管理)

即代码仅需标准Java注释、JSR303然后通过smart-doc openapi模式生成OAS 3.0文档 - openapi.json亦可根据需要按照OAS 3.0规范手动修改openapi.json【可选】最后通过Springdoc接入openapi.json进行Swagger UI渲染展示【可选】【⭐️推荐⭐️】亦可将openapi.json导入其他已有的文档平台进行展示与管理注:此种方法以OAS 3.0规范形成统一格式文档,便于后续维护修改、迁移到其他文档管理平台


3. Springfox2集成Swagger2

maven依赖:

 
 
 	 
     org.springframework.boot
     spring-boot-starter-parent
     2.6.2
      
 
 

	2.9.2
	1.6.4



 	
    
         org.springframework.boot
         spring-boot-starter-web
    
    
	
    
        io.springfox
        springfox-swagger2
        ${springfox2.version}
    
    
    
        io.springfox
        springfox-swagger-ui
        ${springfox2.version}
    
    
    
        io.springfox
        springfox-bean-validators
        ${springfox2.version}
    

    
    
        io.swagger
        swagger-annotations
        ${swagger.version}
    
    
        io.swagger
        swagger-models
        ${swagger.version}
    

应用配置application.yaml:

spring:
  mvc:
    pathmatch:
      # 设置path匹配策略,解决高版本springboot启动springfox报空指针异常问题,
      # 具体参见:https://blog.csdn.net/Faint35799/article/details/122344731
      matching-strategy: ant_path_matcher

代码配置:

import com.luo.demo.sc.base.enums.RespCodeEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.documentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Configuration
@EnableSwagger2
public class SpringfoxConfig {

    @Bean
    public Docket createRestApi() {
        List respMsgList = this.convertRespMsgList();
        return new Docket(documentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //设置全局响应信息
                .globalResponseMessage(RequestMethod.GET, respMsgList)
                .globalResponseMessage(RequestMethod.POST, respMsgList)
                .globalResponseMessage(RequestMethod.PUT, respMsgList)
                .globalResponseMessage(RequestMethod.DELETE, respMsgList)
                .select()
                //为当前包路径
                .apis(RequestHandlerSelectors.basePackage("com.luo.demo.api.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    @Bean
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // 页面标题
                .title("Springfox Swagger2 - RESTful API")
                // 创建人信息
                .contact(new Contact("luohq", "https://blog.csdn.net/luo15242208310", "[email protected]"))
                // 版本号
                .version("1.0")
                // 描述
                .description("Springfox Swagger2构建RESTful API")
                .build();
    }

    
    private List convertRespMsgList() {
        return Stream.of(RespCodeEnum.values())
                .map(respCodeEnum -> {
                    return new ResponseMessageBuilder()
                            .code(respCodeEnum.getCode())
                            .message(respCodeEnum.getMessage())
                            .build();
                }).collect(Collectors.toList());
    }
}

如上配置完成后,则可以直接访问swagger-ui界面:http://localhost:8080/swagger-ui.html
在不添加任何Swagger 2.0注解的情况下,Springfox也可根据SpringMvc相关结构生成文档如下图:


可以发现在英文环境下,规范的Controller方法名、参数名称、变量名称皆可起到说明的作用,
但是在中文环境这种纯英文的描述还不够,我们还需要中文的描述,
如此便可通过Swagger 2.0注解(或者后续的OAS 3.0注解、中文注释)的进行详细的中文说明。

添加Swagger 2.0注解的示例代码如下:

//=======================================================================================
//================================ Controller层代码 ======================================
//=======================================================================================
@Api(description = "用户信息管理")
@Slf4j
@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    @ApiOperation(value = "查询用户信息", notes = "根据用户ID查询用户信息")
    @ApiImplicitParam(name = "id", value = "用户ID", paramType = "path", required = true)
    @GetMapping("/{id}")
    public RespResult getUser(@NotNull @PathVariable Long id) {
        log.info("get user, param: id={}", id);
        return RespResult.successData(this.buildUser(id));
    }

    @ApiOperation(value = "查询用户信息列表", notes = "查询用户信息列表")
    @GetMapping
    public RespResult getUsers(@Validated UserQueryDto userQueryDto) {
        log.info("get users, param: {}", userQueryDto);
        return RespResult.successRows(TOTAL_DEFAULT, this.buildUsers(Optional.ofNullable(userQueryDto.getId()).orElse(ID_DEFAULT), TOTAL_DEFAULT));
    }

    @ApiOperation(value = "新增用户及设备绑定信息")
    @PostMapping
    public RespResult addUser(@Validated @RequestBody UserAddDto userAddDto) {
        log.info("add user, param: {}", userAddDto);
        return RespResult.successData(1);
    }

    @ApiOperation(value = "修改用户及设备绑定信息")
    @PutMapping
    public RespResult updateUser(@Validated @RequestBody UserEditDto userEditDto) {
        log.info("update user, param: {}", userEditDto);
        return RespResult.successData(1);
    }

   @ApiOperation(value = "删除用户信息", notes = "根据用户ID列表删除用户信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "ids", value = "用户ID列表(逗号分隔)", paramType = "path", required = true)
    })
    @DeleteMapping("/{ids}")
    public RespResult deleteUsers(@NotEmpty @PathVariable List ids) {
        log.info("delete users, param: ids={}", ids);
        return RespResult.successData(ids.size());
    }
	//省略...
}

//=======================================================================================
//================================= Model对象层代码 ======================================
//=======================================================================================

@Data
@Builder
@ApiModel("新增用户参数")
public class UserAddDto {
    
    @ApiModelProperty(value = "用户名称")
    @NotBlank
    @Size(min = 1, max = 30)
    private String name;
    
    @ApiModelProperty(value = "用户性别(1:男,2:女)")
    @NotNull
    @Range(min = 1, max = 2)
    private Integer sex;

    
    @ApiModelProperty(value = "设备列表")
    @NotEmpty
    private List deviceInfoList;
}

添加Swagger 2.0相关注解完成后重启应用,Swagger-ui效果如下图:


4. Springfox3集成OAS 3.0

maven依赖:



org.springframework.boot
spring-boot-starter-parent
2.6.2
 



   3.0.0
   1.6.4



	
	
	    org.springframework.boot
	    spring-boot-starter-web
	
	
   
   
       io.springfox
       springfox-boot-starter
       ${springfox3.version}
   

   
   
       io.swagger
       swagger-annotations
       ${swagger.version}
   
   
       io.swagger
       swagger-models
       ${swagger.version}
   

应用配置application.yaml:

spring:
  mvc:
    pathmatch:
      # 设置path匹配策略,解决springfox启动空指针异常问题,
      # 具体参见:https://blog.csdn.net/Faint35799/article/details/122344731
      matching-strategy: ant_path_matcher

代码配置:

import com.luo.demo.sc.base.enums.RespCodeEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.*;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Response;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.documentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Configuration
public class SpringfoxConfig {

    @Bean
    public Docket createRestApi() {
        List respMsgList = this.convertRespMsgList();
        return new Docket(documentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .globalResponses(HttpMethod.GET, respMsgList)
                .globalResponses(HttpMethod.POST, respMsgList)
                .globalResponses(HttpMethod.PUT, respMsgList)
                .globalResponses(HttpMethod.DELETe, respMsgList)
                .select()
                // 为当前包路径
                .apis(RequestHandlerSelectors.basePackage("com.luo.demo.api.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    @Bean
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // 页面标题
                .title("Springfox OAS3.0 - RESTful API")
                // 创建人信息
                .contact(new Contact("luohq", "https://blog.csdn.net/luo15242208310", "[email protected]"))
                // 版本号
                .version("1.0")
                // 描述
                .description("Springfox OAS3.0 构建RESTful API" + this.convertRespMsgHtmlTable())
                .build();
    }

    
    private List convertRespMsgList() {
        return Stream.of(RespCodeEnum.values())
                .map(respCodeEnum -> {
                    return new ResponseBuilder()
                            .code(respCodeEnum.getCode().toString())
                            .description(respCodeEnum.getMessage())
                            .build();
                }).collect(Collectors.toList());
    }

    
    private String convertRespMsgHtmlTable() {
        StringBuilder sb = new StringBuilder("响应码提示信息");
        Stream.of(RespCodeEnum.values()).forEach(respCodeEnum -> {
            sb.append("")
                    .append(respCodeEnum.getCode())
                    .append("")
                    .append(respCodeEnum.getMessage())
                    .append("");
        });
        return sb.append("").toString();
    }
}

添加OAS 3.0注解的示例代码如下:

//=======================================================================================
//================================ Controller层代码 ======================================
//=======================================================================================
@Tag(name = "用户信息管理")
@Slf4j
@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private final Integer TOTAL_DEFAULT = 3;
    private final Long ID_DEFAULT = 1L;

    
    @Operation(summary = "查询用户信息", description = "根据用户ID查询用户信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @GetMapping("/{id}")
    public RespResult getUser(@Parameter(name = "id", description = "用户ID", example = "1", in = ParameterIn.PATH, required = true)
                                        @NotNull @PathVariable Long id) {
        log.info("get user, param: id={}", id);
        return RespResult.successData(this.buildUser(id));
    }

    
    @Operation(summary = "查询用户信息列表", description = "查询用户信息列表",
            parameters = {
                    @Parameter(name = "id", description = "用户ID", example = "1", in = ParameterIn.QUERY),
                    @Parameter(name = "name", description = "用户姓名", in = ParameterIn.QUERY),
                    @Parameter(name = "sex", description = "用户性别(1:男,2:女)", example = "1", in = ParameterIn.QUERY),
                    @Parameter(name = "createTimeStart", description = "起始创建日期", example = "2022-01-01 10:00:00", in = ParameterIn.QUERY),
                    @Parameter(name = "createTimeEnd", description = "结束创建日期", example = "2022-01-01 10:00:00", in = ParameterIn.QUERY),
            },
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            }
    )
    //@Operation(summary = "查询用户信息列表", description = "查询用户信息列表")
    @GetMapping
    public RespResult getUsers(@Parameter(hidden = true) @Validated UserQueryDto userQueryDto) {
        log.info("get users, param: {}", userQueryDto);
        return RespResult.successRows(TOTAL_DEFAULT, this.buildUsers(Optional.ofNullable(userQueryDto.getId()).orElse(ID_DEFAULT), TOTAL_DEFAULT));
    }

    
    @Operation(summary = "新增用户及设备绑定信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @PostMapping
    public RespResult addUser(@Validated @RequestBody UserAddDto userAddDto) {
        log.info("add user, param: {}", userAddDto);
        return RespResult.successData(1);
    }

    
    @Operation(summary = "修改用户及设备绑定信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @PutMapping
    public RespResult updateUser(@Validated @RequestBody UserEditDto userEditDto) {
        log.info("update user, param: {}", userEditDto);
        return RespResult.successData(1);
    }

    
    @Operation(summary = "删除用户信息", description = "根据用户ID列表删除用户信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @DeleteMapping("/{ids}")
    public RespResult deleteUsers(@Parameter(name = "ids", description = "用户ID列表(逗号分隔)", in = ParameterIn.PATH, required = true)
                                           @NotEmpty @PathVariable List ids) {
        log.info("delete users, param: ids={}", ids);
        return RespResult.successData(ids.size());
    }
    
	//省略...
}


//=======================================================================================
//================================ Model对象层代码 =======================================
//=======================================================================================

@Data
@Builder
@Schema(description = "新增用户参数")
public class UserAddDto {
    
    @Schema(description = "用户名称")
    @NotBlank
    @Size(min = 1, max = 30)
    private String name;
    
    @Schema(description = "用户性别(1:男,2:女)", example = "1")
    @NotNull
    @Range(min = 1, max = 2)
    private Integer sex;

    
    @Schema(description = "设备列表")
    @NotEmpty
    private List deviceInfoList;
}

启动项目,浏览器访问:http://localhost:8080/swagger-ui/

注: Springfox2和Springfox3版本的swagger-ui访问地址不同

Springfox2 Swagger 2.0版本swagger-ui访问地址: http://localhost:8080/swagger-ui.htmlSpringfox3 OAS 3.0版本swagger-ui访问地址: http://localhost:8080/swagger-ui/

启动后Swagger-ui效果如下图:


5. Springdoc集成OAS 3.0

maven依赖:


	
	org.springframework.boot
	spring-boot-starter-parent
	2.6.2
	 



   1.6.4



	
	
	    org.springframework.boot
	    spring-boot-starter-web
	
	
   
        
        
            org.springdoc
            springdoc-openapi-ui
            ${springdoc.version}
        
    

应用配置application.yaml:

# springdoc配置
springdoc:
  # 分组配置
  group-configs:
    - group: 用户管理
      packages-to-scan: com.luo.demo.api.controller
      paths-to-match: /users/**
    - group: 角色管理
      packages-to-scan: com.luo.demo.api.controller
      paths-to-match: /roles/**

代码配置:

import com.luo.demo.sc.base.enums.RespCodeEnum;
import io.swagger.v3.oas.models.Externaldocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.stream.Stream;


@Configuration
public class OpenApiConfig {
    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info().title("Springdoc OAS3.0 - RESTful API")
                        .description("Springdoc OAS3.0 构建RESTful API" + this.convertRespMsgHtmlTable())
                        .version("1.0")
                        .license(new License().name("Apache 2.0").url("http://springdoc.org")))
                .servers(Arrays.asList(
                        new Server().description("开发环境").url("http://localhost:8080")
                ))
                .externalDocs(new Externaldocumentation()
                        .description("SpringShop Wiki documentation")
                        .url("https://springshop.wiki.github.org/docs"));
    }

    //
    //@Bean
    //public OperationCustomizer customizeOperation() {
    //    return (operation, handlerMethod) -> {
    //        System.out.println("op: " + operation.getSummary());
    //        ApiResponses curResponses = operation.getResponses();
    //        Stream.of(RespCodeEnum.values()).forEach(respCodeEnum -> {
    //            curResponses.addApiResponse(
    //                    String.valueOf(respCodeEnum.getCode()),
    //                    new ApiResponse().description(respCodeEnum.getMessage()));
    //        });
    //        return operation.responses(curResponses);
    //    };
    //}

    //代码配置分组(亦可直接通过配置文件进行配置springdoc.group-configs[*])
    //@Bean
    //public GroupedOpenApi publicApi() {
    //    return GroupedOpenApi.builder()
    //            .group("用户管理")
    //            .pathsToMatch("/users
    private String convertRespMsgHtmlTable() {
        StringBuilder sb = new StringBuilder("响应码提示信息");
        Stream.of(RespCodeEnum.values()).forEach(respCodeEnum -> {
            sb.append("")
                    .append(respCodeEnum.getCode())
                    .append("")
                    .append(respCodeEnum.getMessage())
                    .append("");
        });
        return sb.append("").toString();
    }
}

添加OAS 3.0注解的示例代码如下(同之前Springfox3中使注解相同):

//=======================================================================================
//================================ Controller层代码 ======================================
//=======================================================================================
@Tag(name = "用户信息管理")
@Slf4j
@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private final Integer TOTAL_DEFAULT = 3;
    private final Long ID_DEFAULT = 1L;

    
    @Operation(summary = "查询用户信息", description = "根据用户ID查询用户信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @GetMapping("/{id}")
    public RespResult getUser(@Parameter(name = "id", description = "用户ID", example = "1", in = ParameterIn.PATH, required = true)
                                        @NotNull @PathVariable Long id) {
        log.info("get user, param: id={}", id);
        return RespResult.successData(this.buildUser(id));
    }

    
    @Operation(summary = "查询用户信息列表", description = "查询用户信息列表",
            parameters = {
                    @Parameter(name = "id", description = "用户ID", example = "1", in = ParameterIn.QUERY),
                    @Parameter(name = "name", description = "用户姓名", in = ParameterIn.QUERY),
                    @Parameter(name = "sex", description = "用户性别(1:男,2:女)", example = "1", in = ParameterIn.QUERY),
                    @Parameter(name = "createTimeStart", description = "起始创建日期", example = "2022-01-01 10:00:00", in = ParameterIn.QUERY),
                    @Parameter(name = "createTimeEnd", description = "结束创建日期", example = "2022-01-01 10:00:00", in = ParameterIn.QUERY),
            },
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            }
    )
    //@Operation(summary = "查询用户信息列表", description = "查询用户信息列表")
    @GetMapping
    public RespResult getUsers(@Parameter(hidden = true) @Validated UserQueryDto userQueryDto) {
        log.info("get users, param: {}", userQueryDto);
        return RespResult.successRows(TOTAL_DEFAULT, this.buildUsers(Optional.ofNullable(userQueryDto.getId()).orElse(ID_DEFAULT), TOTAL_DEFAULT));
    }

    
    @Operation(summary = "新增用户及设备绑定信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @PostMapping
    public RespResult addUser(@Validated @RequestBody UserAddDto userAddDto) {
        log.info("add user, param: {}", userAddDto);
        return RespResult.successData(1);
    }

    
    @Operation(summary = "修改用户及设备绑定信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @PutMapping
    public RespResult updateUser(@Validated @RequestBody UserEditDto userEditDto) {
        log.info("update user, param: {}", userEditDto);
        return RespResult.successData(1);
    }

    
    @Operation(summary = "删除用户信息", description = "根据用户ID列表删除用户信息",
            responses = {
                    @ApiResponse(responseCode = "1000", description = " *** 作成功", content = @Content),
                    @ApiResponse(responseCode = "1101", description = "参数无效", content = @Content),
                    @ApiResponse(responseCode = "2000", description = " *** 作失败", content = @Content),
            })
    @DeleteMapping("/{ids}")
    public RespResult deleteUsers(@Parameter(name = "ids", description = "用户ID列表(逗号分隔)", in = ParameterIn.PATH, required = true)
                                           @NotEmpty @PathVariable List ids) {
        log.info("delete users, param: ids={}", ids);
        return RespResult.successData(ids.size());
    }
    
	//省略...
}


//=======================================================================================
//================================ Model对象层代码 =======================================
//=======================================================================================

@Data
@Builder
@Schema(description = "新增用户参数")
public class UserAddDto {
    
    @Schema(description = "用户名称")
    @NotBlank
    @Size(min = 1, max = 30)
    private String name;
    
    @Schema(description = "用户性别(1:男,2:女)", example = "1")
    @NotNull
    @Range(min = 1, max = 2)
    private Integer sex;

    
    @Schema(description = "设备列表")
    @NotEmpty
    private List deviceInfoList;
}

启动项目,浏览器访问:http://localhost:8080/swagger-ui.html

注:

Springfox2 Swagger 2.0版本swagger-ui访问地址: http://localhost:8080/swagger-ui.htmlSpringfox3 OAS 3.0版本swagger-ui访问地址: http://localhost:8080/swagger-ui/Springdoc OAS 3.0版本swagger-ui访问地址: http://localhost:8080/swagger-ui.html

启动后Swagger-ui效果如下图:



6. Smart-doc + Springdoc集成OAS 3.0

smart-doc提供maven(或gradle)插件,通过集成插件运行mvn相关命令即可生成文档。

示例命令如下:

# 生成 Open Api 3.0+,Since smart-doc-maven-plugin 1.1.5
mvn -Dfile.encoding=UTF-8 smart-doc:openapi

# 生成html
mvn -Dfile.encoding=UTF-8 smart-doc:html
# 生成markdown
mvn -Dfile.encoding=UTF-8 smart-doc:markdown
# 生成adoc
mvn -Dfile.encoding=UTF-8 smart-doc:adoc

# 生成postman json数据
mvn -Dfile.encoding=UTF-8 smart-doc:postman

# 生成文档推送到Torna平台
mvn -Dfile.encoding=UTF-8 smart-doc:torna-rest

maven依赖:


	
	org.springframework.boot
	spring-boot-starter-parent
	2.6.2
	 



   1.6.4
   2.3.6



	
	
	    org.springframework.boot
	    spring-boot-starter-web
	

    
    
        org.springdoc
        springdoc-openapi-ui
        ${springdoc.openapi.version}
    



    
        
        
            com.github.shalousun
            smart-doc-maven-plugin
            ${smart.doc.version}
            
                
                ./src/main/resources/smart-doc.json
                
                
                    
                    
                    com.alibaba:.*
                    cn.hutool:hutool-core
                
            
            
                
                    
                    compile
                    
                        
                        openapi
                    
                
            
        
    

smart-doc.json配置:

注:

smart-doc.json文件位置需和之前maven插件smart-doc-maven-plugin中configuration.configFile指定位置一致

./src/main/resources/smart-doc.json即对应resources/smart-doc.json文件。 如下配置中的groups在openapi、postman模式下并不生效(仅适用于html、markdown、adoc)如下配置中的outPath即为smart-doc生成文件的存放位置

若使用openapi模式且需要通过Springdoc渲染此openapi.json,则outPath需输出到./src/main/resources/static目录下 关于smart-doc.json详细配置参见:https://smart-doc-group.github.io/#/zh-cn/diy/config

{
  "projectName": "Smartdoc + springdoc + OAS3.0 - RESTful API",
  "serverUrl": "http://localhost:8080",
  "pathPrefix": "/",
  "outPath": "./src/main/resources/static/doc",
  "allInOne": true,
  "showAuthor": true,
  "groups": [
    {
      "name": "用户管理",
      "apis": "com.luo.demo.api.controller.user.*"
    },
    {
      "name": "角色管理",
      "apis": "com.luo.demo.api.controller.role.*"
    }
  ],
  "revisionLogs": [
    {
      "version": "1.0",
      "revisionTime": "2022-01-17 16:30",
      "status": "create",
      "author": "luohq",
      "remarks": "Smartdoc OAS3集成Springdoc"
    }
  ]
}

应用配置application.yaml:

# custom path for swagger-ui
springdoc:
  swagger-ui:
    # 自定义openapi.json文件位置(需在/resources/static目录下)
    url: /doc/openapi.json

代码配置:

示例代码如下(仅添加标准的Java注释即可):

//=======================================================================================
//================================ Controller层代码 ======================================
//=======================================================================================

@Slf4j
@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private final Integer TOTAL_DEFAULT = 3;
    private final Long ID_DEFAULT = 1L;

    
    @GetMapping("/{id}")
    public RespResult getUser(@NotNull @PathVariable Long id) {
        log.info("get user, param: id={}", id);
        return RespResult.successData(this.buildUser(id));
    }

    
    @GetMapping
    public RespResult getUsers(@Validated UserQueryDto userQueryDto) {
        log.info("get users, param: {}", userQueryDto);
        return RespResult.successRows(TOTAL_DEFAULT, this.buildUsers(Optional.ofNullable(userQueryDto.getId()).orElse(ID_DEFAULT), TOTAL_DEFAULT));
    }

    
    @PostMapping
    public RespResult addUser(@Validated @RequestBody UserAddDto userAddDto) {
        log.info("add user, param: {}", userAddDto);
        return RespResult.successData(1);
    }

    
    @PutMapping
    public RespResult updateUser(@Validated @RequestBody UserEditDto userEditDto) {
        log.info("update user, param: {}", userEditDto);
        return RespResult.successData(1);
    }

    
    @DeleteMapping("/{ids}")
    public RespResult deleteUsers(@NotEmpty @PathVariable List ids) {
        log.info("delete users, param: ids={}", ids);
        return RespResult.successData(ids.size());
    }
    
	//省略...
}


//=======================================================================================
//================================ Model对象层代码 =======================================
//=======================================================================================

@Data
@Builder
public class UserAddDto {
    
    @NotBlank
    @Size(min = 1, max = 30)
    private String name;
    
    @NotNull
    @Range(min = 1, max = 2)
    private Integer sex;

    
    @NotEmpty
    private List deviceInfoList;
}

之前集成Springfox、Springdoc皆是在程序运行时扫描代码和Swagger相关注解后生成的Api文档,
而Smart-doc可以在编译期(mvn compile)或者程序启动前通过插件命令预先生成Api文档,
即可依次通过如下几步进行集成和启动:

首先通过maven插件smart-doc-maven-plugin的openapi模式生成openapi.json

mvn命令:mvn -Dfile.encoding=UTF-8 smart-doc:openapiopenapi.json文件位置:./src/main/resources/static/doc/openapi.json 然后通过应用配置springdoc.swagger-ui.url去引用此openapi.json

springdoc.swagger-ui.url=/doc/openapi.json 最后启动应用通过springdoc swagger-ui去渲染此openapi.json对应的应用文档

启动项目,浏览器访问:http://localhost:8080/swagger-ui.html (对应Springdoc OAS3 Swagger-ui界面),
启动后Swagger-ui效果如下图:


6.1 openapi.json集成Knife4j

除了使用swagger-ui渲染,还发现了国内开源的Knife4j,
Knife4j提供了一个swagger-ui增强界面(还提供其他增强功能,本文暂未涉及),
通过Disk本地模式聚合OpenAPI文档模式,
即通过Knife4j去渲染之前生成的openapi.json文件,
实际测试后发现其对OAS 3.0的解析及渲染还不是很完善,如对象嵌套的结构皆显示不出来,
开源不易,还是得感谢大神,希望Knife4j可以越来越好。

关于Knife4j聚合本地文件openapi.json的具体集成见下文。

maven配置:


    2.0.9



    
        com.github.xiaoymin
        knife4j-aggregation-spring-boot-starter
        ${knife4j.aggregation.version}
    

应用配置application.yaml:

# knife4j聚合配置
knife4j:
  enableAggregation: true
  disk:
    enable: true
    routes:
      - name: 用户
        location: classpath:static/doc/openapi.json

启动后即可通过http://localhost:8080/doc.html进行访问,
实际集成后发现其对OAS 3.0的解析及渲染还不是很完善,如对象嵌套的结构皆显示不出来,如下图:


7. springdoc-openapi-javadoc对比smart-doc

在springdoc生态中提供了一个Javadoc模块 - springdoc-openapi-javadoc,
该模块增强了springdoc对Java注释及Tag的处理能力,
但支持有限,目前仅支持:

方法上的注释 - 被解析为@Operation.description(即接口的描述信息,不支持@Operation.summary)@param注释 - 被解析为@Parameter.description(即参数的描述信息)@return注释 - 被解析为@Operation.response.description(即返回结果的描述信息)对象属性上的注释 - 被解析为@Schema.description

Smart-doc相较于Springdoc-openapi-javadoc的源码模式,Smart-doc额外支持:

smart-doc支持方法上的注释为@Operation.summary(此处缺失可参见下文图片效果)smart-doc支持方法上的@apiNote注释为@Operation.description支持通过注释设置mock值(@param desc|mockVal、@mock mockVal)smart-doc关于注释的更多使用参见:https://smart-doc-group.github.io/#/zh-cn/start/javadoc

Springdoc-openapi-javadoc除了支持在运行时获取代码注释信息,
也可配合springdoc生态提供的maven插件 - springdoc-openapi-maven-plugin使用,
该插件可在mvn verify阶段扫描源码来生成对应的openapi.json文件。

具体springdoc-openapi-javadoc和springdoc-openapi-maven-plugin的集成及使用见下文。

maven依赖:


	
	org.springframework.boot
	spring-boot-starter-parent
	2.6.2
	 



	1.6.4
	1.3



	
	
	    org.springframework.boot
	    spring-boot-starter-web
	

    
    
        org.springdoc
        springdoc-openapi-ui
        ${springdoc.version}
    

    
    
        org.springdoc
        springdoc-openapi-javadoc
        ${springdoc.version}
    



    

        
            org.apache.maven.plugins
            maven-compiler-plugin
            
            
                
                
                    
                        com.github.therapi
                        therapi-runtime-javadoc-scribe
                        0.13.0
                    
                
            
        

        
        
            org.springdoc
            springdoc-openapi-maven-plugin
            ${springdoc.plugin.version}
            
                
                    integration-test
                    
                        generate
                    
                
            
            
                
                http://localhost:8080/v3/api-docs
                
                openapi.json
                
                 ${project.build.directory}
                
                
                false
            
        
        
            org.springframework.boot
            spring-boot-maven-plugin
            
            
                -Dspring.application.admin.enabled=true
            
            
                
                    pre-integration-test
                    
                        start
                    
                
                
                    post-integration-test
                    
                        stop
                    
                
            
        

    

代码配置:

示例代码同smart-doc示例(仅添加标准的Java注释即可)

//=======================================================================================
//================================ Controller层代码 ======================================
//=======================================================================================

@Slf4j
@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private final Integer TOTAL_DEFAULT = 3;
    private final Long ID_DEFAULT = 1L;

    
    @GetMapping("/{id}")
    public RespResult getUser(@NotNull @PathVariable Long id) {
        log.info("get user, param: id={}", id);
        return RespResult.successData(this.buildUser(id));
    }

    
    @GetMapping
    public RespResult getUsers(@Validated UserQueryDto userQueryDto) {
        log.info("get users, param: {}", userQueryDto);
        return RespResult.successRows(TOTAL_DEFAULT, this.buildUsers(Optional.ofNullable(userQueryDto.getId()).orElse(ID_DEFAULT), TOTAL_DEFAULT));
    }

    
    @PostMapping
    public RespResult addUser(@Validated @RequestBody UserAddDto userAddDto) {
        log.info("add user, param: {}", userAddDto);
        return RespResult.successData(1);
    }

    
    @PutMapping
    public RespResult updateUser(@Validated @RequestBody UserEditDto userEditDto) {
        log.info("update user, param: {}", userEditDto);
        return RespResult.successData(1);
    }

    
    @DeleteMapping("/{ids}")
    public RespResult deleteUsers(@NotEmpty @PathVariable List ids) {
        log.info("delete users, param: ids={}", ids);
        return RespResult.successData(ids.size());
    }
    
	//省略...
}


//=======================================================================================
//================================ Model对象层代码 =======================================
//=======================================================================================

@Data
@Builder
public class UserAddDto {
    
    @NotBlank
    @Size(min = 1, max = 30)
    private String name;
    
    @NotNull
    @Range(min = 1, max = 2)
    private Integer sex;

    
    @NotEmpty
    private List deviceInfoList;
}

启动方式一:

可以直接启动项目,浏览器访问:http://localhost:8080/swagger-ui.html,

启动方式二:

执行mvn verify命令,生成openapi.json到target目录下,拷贝target/openapi.json到应用代码目录resources/static/doc/openapi.json添加application.yaml配置springdoc.swagger-ui.url=/doc/openapi.json(可参见前文smart-doc结合springdoc展示openapi.json)然后再启动项目,浏览器访问:http://localhost:8080/swagger-ui.html

以上两种启动方式的最终展示效果都是一样的,
具体Swagger-ui界面如下图(左边为smart-doc,右边为springdoc javadoc,可以对比着看):



综合对比springdoc-openapi-javadoc和smart-doc,现阶段我还是更倾向于smart-doc,主要原因如下:

smart-doc文档更完善(中文文档对国内开发者更友好)smart-doc对注释的支持更完善(如@apiNote及其他自定义注释语法)实际使用过程中javadoc模块和lombok框架冲突,集成javadoc后在Idea开发工具中提示Lombok相关代码编译失败


参考链接:

swagger:
https://swagger.io/

openapi:
OAS2.0 & 3.0 规范 - https://swagger.io/resources/open-api/
OAS3.0规范 - https://swagger.io/specification/

springfox:
http://springfox.github.io/springfox/
http://springfox.github.io/springfox/docs/current/
从Springfox2迁移到SpringDoc - https://springdoc.org/#migrating-from-springfox

springdoc:
https://springdoc.org/
https://github.com/springdoc/springdoc-openapi-maven-plugin
springdoc设置全局response:
https://github.com/springdoc/springdoc-openapi/issues/114
https://github.com/springdoc/springdoc-openapi/issues/381
https://stackoverflow.com/questions/60869480/default-response-class-in-springdoc
springdoc maven plugin 404报错:
https://stackoverflow.com/questions/59616165/what-is-the-function-of-springdoc-openapi-maven-plugin-configuration-apidocsurl

smart-doc:
https://smart-doc-group.github.io/

其他:
Spring Boot 中使用 SpringFox 整合 Swagger 3(OpenAPI 3)生成 API 文档
Spring Boot 中使用 SpringDoc 整合 Swagger 3(OpenAPI 3)生成 API 文档
https://www.baeldung.com/spring-rest-openapi-documentation

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5707002.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存