SpringBoot学习小结之Swagger

41 篇文章 2 订阅
25 篇文章 3 订阅

swagger

前言

Swagger是什么?

Swagger API 项目最初由 Wordnik 的技术联合创始人 Tony Tam 于 2011 年创建,主要针对在 Wordnik 的产品开发过程中 API 文档自动化客户端 SDK 生成的需求。设计师/开发人员 Zeke Sikelianos 创建了 Swagger 这个名字,Swagger API 项目于 2011 年 9 月开源。

2015 年 11 月,维护 Swagger 的公司 SmartBear Software 宣布在 Linux 基金会的赞助下,创建了一个名为 OpenAPI Initiative 的新组织,包括谷歌、IBM 和微软在内的各种公司都是创始成员。

2016 年 1 月 1 日,Swagger 规范更名为 OpenAPI 规范,并移至 GitHub 上。

Swagger 官网上将 Swagger 分为三大块:

  1. OpenAPI 规范
  2. Swagger 开源工具
  3. SwaggerHub

其中,Swagger 开源工具可以分成以下三部分:

  1. Swagger Editor: 编辑器,可以在浏览器中以 YAML 格式编辑 OpenAPI API 定义并实时预览文档。
  2. Swagger Codegen:代码生成器,可以基于根据 OpenAPI (以前称为 Swagger ) 定义的 RESTful API 可以自动生成服务端和客户端代码.。
  3. Swagger UI:UI部分,根据 OpenAPI(以前称为Swagger )规范自动生成的 HTML 界面,可视化 API 资源,能够与之交互。

SpringBoot中使用Swagger

在 SpringBoot 项目中一般使用 Swagger 用的是 Swagger UI 部分,用于接口的文档在线自动生成和功能测试。 常见的库是使用 springfox 提供的 springfox,不过最新更新在2020年,已经两年没更新,最新的替代品是 springdoc-openapi

下面会介绍 springfox swagger 的使用,虽然已经不再维护,但文档齐全,使用的人多,有什么问题可以十分方便找到答案。

一、pom依赖

不同 swagger 版本选择pom依赖也不同

swagger2

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<dependency>
	<groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

swagger3(openapi3) 提供了boot-starter版本,但自此之后再无更新

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-boot-starter</artifactId>
	<version>3.0.0</version>
</dependency>

swagger2 ui 访问路径:http://localhost:8080/swagger-ui.html

swagger3 ui 访问路径:http://localhost:8080/swagger-ui/index.html

下面会使用 swagger3 为例,探索 swagger 使用

二、常用注解

swagger 以下注解都位于 io.swagger.annotations 包内

注解名标记范围含义
@ApiTYPE用于标注一个 Controller,表明是 swagger 资源 。在默认情况下,Swagger-Core 只会扫描解析具有 @Api 注解的类
@ApiImplicitParamMETHOD用于标注 @ApiOperation 操作中的单个参数
@ApiImplicitParamsMETHOD用于标注 @ApiOperation 操作中的多个参数
@ApiModelTYPE用于标注一个类,表明此类是 swagger 的 Model 类
@ApiModelPropertyMETHOD,FIELD用于描述被 @ApiModel 标记类的属性
@ApiOperationMETHOD,FIELD表明是一个 http 请求的操作
@ApiParamPARAMETER,METHOD,FIELD和 @ApiImplicitParam 类似, 但只能和 JAX-RS 1.x/2.x 注解结合使用
@ApiResponseMETHOD用于描述 @ApiOperation 单个响应
@ApiResponsesMETHOD用于描述 @ApiOperation 多个响应
@AuthorizationMETHOD定义要在资源或操作上使用的授权方案。应该在 @Api 或 @ApiOperation 中使用
@AuthorizationScopeMETHOD描述 OAuth2 授权范围,应该在 @Authorization 中使用
@ExampleANNOTATION_TYPE用于描述一个示例
@ExamplePropertyANNOTATION_TYPE用于描述一个示例属性
@ResponseHeaderMETHOD定义 @ApiResponse head部分

下面注解不在 io.swagger.annotations 包下面

注解名标记范围含义
@EnableSwaggerTYPE启用 swagger 1.2 规范
@EnableSwagger2TYPE启用 swagger 2.0 规范
@EnableOpenApiTYPE启用 open api 3.0.3 规范
@ApiIgnoreMETHOD, TYPE, PARAMETER忽略所标记的方法、类或参数,不会在UI界面显示

三、配置

一个 Swagger3 文档例子 UI 如下

swagger_index_html

可以通过 java 代码对界面进行配置

@Configuration
public class SwaggerConfig {
    @Bean
    public Docket createRestApi(Environment environment) {
       	//设置要显示的swagger的环境
        Profiles profiles = Profiles.of("dev", "test");

        //通过environment.acceptsProfiles判断是否处在自己设定的环境中
        boolean flag = environment.acceptsProfiles(profiles);
        
        return new Docket(DocumentationType.OAS_30)
            	.enable(flag)
                .apiInfo(apiInfo())
                .directModelSubstitute(LocalTime.class, String.class)
                .directModelSubstitute(LocalDate.class, String.class)
                .directModelSubstitute(LocalDateTime.class, String.class)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
                .paths(PathSelectors.any()) // 可以通过PathSelectors.ant("/a/**")来分隔 进行分组
                .build();
    }

    //生成接口信息,包括标题、联系人、版本等
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger3 demo接口文档")
                .description("如有疑问,请联系开发工程师。")
                .contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
                .version("1.0")
                .build();
    }

    @Bean
    public UiConfiguration uiConfig() {
        return UiConfigurationBuilder.builder()
                .docExpansion(DocExpansion.NONE) // 接口文档展开
                .operationsSorter(OperationsSorter.ALPHA) // http操作排序
                .defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
                .supportedSubmitMethods(DEFAULT_SUBMIT_METHODS) // 支持的http方法
                .build();
    }
}

Docket 是 主要的配置类,如果包含分组可以配置多个,上面使用 directModelSubstitute 是为了在前台用 String 接收时间类型

UiConfiguration 中可以配置一些UI界面选项

docExpansion 接口文档展开

  • none 不展开
  • list 展开
  • full 全部展开,包括接口的详细信息

operationsSorter http操作排序

  • alpha 字母排序
  • method 根据 http 方法排序 (http方法根据字母顺序排序)

defaultModelRendering Model渲染

  • EXAMPLE 接口 response 默认显示 example

    swagger_reponse_example

  • MODEL 接口 response 默认显示 schema

    swagger_response_schema

四、示例

下面以一个选课系统来演示 swagger 使用

4.1 代码

课程类

@ApiModel
@Data
public class Course {
    @ApiModelProperty(value = "课程ID")
    private Long id;

    @ApiModelProperty(value = "名字")
    private String name;

    @ApiModelProperty(value = "类别")
    private String category;

    @ApiModelProperty(value = "学分")
    private String credit;

    @ApiModelProperty(value = "学生数量")
    private Integer number;

    @ApiModelProperty(value = "老师名字")
    private List<String> teachers;

    @ApiModelProperty(value = "上课时间-星期")
    private DayOfWeek timeWeek;

    @ApiModelProperty(value = "开始时间")
    @DateTimeFormat(pattern = "HH:mm:ss")
    private LocalTime startTime;

    @ApiModelProperty(value = "结束时间")
    @DateTimeFormat(pattern = "HH:mm:ss")
    private LocalTime endTime;

    @ApiModelProperty(value = "地点")
    private String place;
}

学生类

@ApiModel
@Data
public class Student {

    @ApiModelProperty(value = "学号")
    private String id;

    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "专业")
    private String major;

    @ApiModelProperty(value = "年级")
    private CollegeGradeEnum grade;

    @ApiModelProperty(value = "性别")
    private String gender;
}

学生年级枚举

public enum CollegeGradeEnum {
    FRESHMAN, 
    SOPHOMOREER, 
    JUNIOR, 
    SENIOR 
}

公共VO

@ApiModel
@Data
@Builder
public class CommonVo<T> {

    @ApiModelProperty("状态码")
    private String code;

    @ApiModelProperty("状态信息")
    private String message;

    @ApiModelProperty("数据")
    private T data;
}

课程Controller

@Api(value = "courseAPI",tags = "课程API")
@RestController
@RequestMapping
public class CourseController {

    private static final Logger logger = LoggerFactory.getLogger(CourseController.class);

    @ApiOperation("创建一门课程")
    @ApiImplicitParam(name = "course", value = "课程信息", dataTypeClass = Course.class, paramType = "body")
    @RequestMapping(value = "/createCourse",method = RequestMethod.POST)
    public ResponseEntity<CommonVo> createCourse(@RequestBody Course course) {
        return ResponseEntity.ok(CommonVo.builder().code("0").message("success").build());
    }


    @ApiOperation("修改课程信息")
    @ApiImplicitParams({
            @ApiImplicitParam( name = "id", value = "课程id", paramType = "query", dataTypeClass = Long.class, required = true),
            @ApiImplicitParam( name = "category", value = "课程类别", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "name", value = "课程名称", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "timeWeek", value = "星期", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "startTime", value = "上课开始时间", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "endTime", value = "上课结束时间", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "credit", value = "学分", paramType = "query", dataTypeClass = String.class),
            @ApiImplicitParam( name = "teachers", value = "老师", paramType = "query", dataTypeClass = String.class, allowMultiple = true),
            @ApiImplicitParam( name = "number", value = "上课人数", paramType = "query", dataTypeClass = Integer.class),
            @ApiImplicitParam( name = "place", value = "上课地点", paramType = "query", dataTypeClass = String.class),
    })
    @RequestMapping(value = "/updateCourse",method = RequestMethod.PUT)
    public ResponseEntity<CommonVo<Course>> updateCourse(Course course){
        logger.info("{}}", course);
        return ResponseEntity.ok(CommonVo.<Course>builder().code("200").data(course).build());
    }
    
    @ApiOperation("操作课程")
    @RequestMapping(value = "/testCourse", method = RequestMethod.POST)
    public ResponseEntity<CommonVo<Course>> test(@ApiParam(name = "course") @RequestBody Course course) {
        return null;
    }



    @ApiOperation("查看课程信息")
    @RequestMapping(value = "/courseList",method = RequestMethod.GET)
    public ResponseEntity<CommonVo<List<Course>>> courseList(@ApiParam(name = "studentId", value = "学号") String studentId) {
        return null;
    }

    @ApiOperation("查看单个课程信息")
    @RequestMapping(value = "/course",method = RequestMethod.GET)
    public ResponseEntity<CommonVo<Course>> course(@ApiParam(name = "courseId", value = "课程Id") Long courseId) {
        return null;
    }

    @ApiOperation("删除课程")
    @RequestMapping(value = "/deleteCourse",method = RequestMethod.DELETE)
    public ResponseEntity<CommonVo> deleteCourse(Long courseId) {
        return null;
    }
}

学生Controller

@Api(value = "student API", tags = "学生API")
@RestController
@RequestMapping("/home")
public class StudentController {

    @ApiOperation("新增学生")
    @ApiImplicitParam(name = "student", value = "学生信息", dataTypeClass = Student.class, paramType = "body")
    @RequestMapping(value = "/createCourse",method = RequestMethod.POST)
    public Object createCourse(@RequestBody Student student) {
        return null;
    }
}

4.2 后台接收前端字段

一般通过 @ApiImplicitParam 或 @ApiParam 来定义接收的字段,@ApiImplicitParam 用处比较广,可以接收表单和 json 等各种字段,常用属性如下所示

  1. name

    参数名字

  2. value

    参数描述

  3. paramType

    参数类型,可以为以下部分

    • header:@RequestHeader

    • query:@RequestParam

    • path:@PathVariable

    • body:@RequestBody

    • form:表单

  4. dataTypeClass

    参数类型,String类型用的比较多,默认 Void.class

  5. required

    参数是否必填,默认 false

  6. allowMultiple

    是否数组,,默认 false

@ApiImplicitParam

swagger_no_json_query.png

@ApiParam 在官方文档中注明需要和 JAX-RS 1.x/2.x 注解结合使用,实际使用发现可以和 SpringBoot 中注解一起使用,使用效果和 @ApiImplicitParam 类似

swagger_api_param_example

字段含义可以通过Scchema 查看

4.3 后台返回前端字段

后台返回前端字段,不需要特别指出,只需要使用 @ApiModel @ApiModelProperty将返回数据类定义好,swagger 会自动将 controller 返回值解析为 json 数据.。以上面示例为例,ResponseEntity<CommonVo<Course>>会自动解析为下面字段

swagger_response_common_data

每个字段的含义,可以通过schema标签查看

swagger_response_common_scheme

五、拓展使用

5.1 隐藏前端的 schema

在UiConfiguration uiConfig配置defaultModelsExpandDepth(-1)

5.2 分组

当Controller很多时,页面查找不方便,可以根据路径将各个Controller进行分组

@Bean
public Docket studentApi(Environment environment) {
    //设置要显示的swagger的环境
    Profiles profiles = Profiles.of("dev", "test");

    //通过environment.acceptsProfiles判断是否处在自己设定的环境中
    boolean flag = environment.acceptsProfiles(profiles);   

    return new Docket(DocumentationType.OAS_30)
        	.enable(flag)
            .apiInfo(apiInfo())
            .groupName("学生")
            .directModelSubstitute(LocalTime.class, String.class)
            .directModelSubstitute(LocalDate.class, String.class)
            .directModelSubstitute(LocalDateTime.class, String.class)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
            .paths(PathSelectors.ant("/student/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
            .build();
}

@Bean
public Docket courseApi(Environment environment) {
    //设置要显示的swagger的环境
    Profiles profiles = Profiles.of("dev", "test");

    //通过environment.acceptsProfiles判断是否处在自己设定的环境中
    boolean flag = environment.acceptsProfiles(profiles); 
    
    return new Docket(DocumentationType.OAS_30)
            .enable(flag)
            .apiInfo(apiInfo())
            .groupName("课程")
            .directModelSubstitute(LocalTime.class, String.class)
            .directModelSubstitute(LocalDate.class, String.class)
            .directModelSubstitute(LocalDateTime.class, String.class)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
            .paths(PathSelectors.ant("/course/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
            .build();
}

@Bean
public Docket defaultApi(Environment environment) {
    //设置要显示的swagger的环境
    Profiles profiles = Profiles.of("dev", "test");

    //通过environment.acceptsProfiles判断是否处在自己设定的环境中
    boolean flag = environment.acceptsProfiles(profiles); 
    
    return new Docket(DocumentationType.OAS_30)
            .enable(flag)
            .apiInfo(apiInfo())
            .groupName("default")
            .directModelSubstitute(LocalTime.class, String.class)
            .directModelSubstitute(LocalDate.class, String.class)
            .directModelSubstitute(LocalDateTime.class, String.class)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
            .paths(PathSelectors.any()) 
            .build();
}

//生成接口信息,包括标题、联系人等
private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
            .title("Swagger3 demo接口文档")
            .description("如有疑问,请联系开发工程师。")
            .contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
            .version("1.0")
            .build();
}

5.3 生成离线文档

swagger 通过 swagger2markup 这个项目将文档转成静态文档。注意:暂时只能生成 swagger2 文档,通过查找源码发现,解析类Swagger20Parser 解析json数据前会判断是否包含 swagger 这个节点,而 swagger3 的 swagger 节点名字已经改成 openapi 了,这个问题暂时未修复,相关问题已经有人提ISSUE Swagger v3 cannot use swagger2markup(1.3.3)

所以需要生成文档,临时的解决方案是将 new Docket(DocumentationType.OAS_30) 修改为 new Docket(DocumentationType.SWAGGER_2)

  1. 生成 asciidoc 文档 mvn swagger2markup:convertSwagger2markup

    <properties>
    	<swagger2markup.version>1.3.3</swagger2markup.version>
    	<asciidoctor.input.directory>${project.basedir}/src/docs/asciidoc</asciidoctor.input.directory>
        <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
        <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
    </properties> 
    
    <plugin>
        <groupId>io.github.swagger2markup</groupId>
        <artifactId>swagger2markup-maven-plugin</artifactId>
        <version>1.3.3</version>
        <configuration>
            <swaggerInput>http://localhost:8080/v2/api-docs</swaggerInput>
            <outputDir>${asciidoctor.input.directory}</outputDir>
            <config>
                <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
            </config>
        </configuration>
    </plugin>
    
  2. 生成 HTML 和 PDF 文档 mvn test

    <plugin>
        <groupId>org.asciidoctor</groupId>
        <artifactId>asciidoctor-maven-plugin</artifactId>
        <version>1.5.5</version>
        <configuration>
            <!--上一步生成的asciidoc文件路径-->
            <sourceDirectory>${asciidoctor.input.directory}</sourceDirectory>
            <headerFooter>true</headerFooter>
            <doctype>book</doctype>
            <sourceHighlighter>coderay</sourceHighlighter>
            <attributes>
                <!--菜单栏在左边-->
                <toc>left</toc>
                <!--多标题排列-->
                <toclevels>3</toclevels>
                <!--自动打数字序号-->
                <sectnums>true</sectnums>
            </attributes>
        </configuration>
    
    
        <dependencies>
            <dependency>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctorj-pdf</artifactId>
                <version>1.5.0-alpha.16</version>
            </dependency>
            <dependency>
                <groupId>org.jruby</groupId>
                <artifactId>jruby-complete</artifactId>
                <version>1.7.21</version>
            </dependency>
        </dependencies>
    
        <executions>
            <execution>
                <id>output-html</id>
                <phase>test</phase>
                <goals>
                    <goal>process-asciidoc</goal>
                </goals>
                <configuration>
                    <backend>html5</backend>
                    <outputDirectory>${asciidoctor.html.output.directory}</outputDirectory>
                </configuration>
            </execution>
    
            <execution>
                <id>output-pdf</id>
                <phase>test</phase>
                <goals>
                    <goal>process-asciidoc</goal>
                </goals>
                <configuration>
                    <backend>pdf</backend>
                    <outputDirectory>${asciidoctor.pdf.output.directory}</outputDirectory>
                </configuration>
            </execution>
    
        </executions>
    </plugin>
    

最终在 target 目录下生成

swagger2_static_doc

PDF样式如下,可以发现中文有丢失情况,解决方法:swagger+asciidoctor 导出PDF中文缺失乱码问题解决

swagger2_doc_pdf

5.4 第三方主题

推荐一个第三方主题,访问路径为/doc.html

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

swagger_doc_html

六、顺序问题(坑)

3.0版本的各种顺序默认都变成了字母顺序,而且不好更改,是个大坑。介意这个问题的可以考虑换成 springdoc-openapi

6.1 Controller tag 顺序

默认字母顺序,理论上讲可以通过 tagsSorter 这个方法修改,但是 TagsSorter 这个枚举也只有 ALPHA(“alpha”) 一个值,所以说还是不能修改

@Bean
public UiConfiguration uiConfig() {
    return UiConfigurationBuilder.builder()
            .docExpansion(DocExpansion.NONE) // 接口文档展开
            .operationsSorter(OperationsSorter.ALPHA) // http操作排序
            .defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
            .tagsSorter(TagsSorter.ALPHA)
            .build();
}

6.2 operation 顺序

@ApiOperation 中 position 也已经被废弃,但是可以通过 operationsSorter 来进行更改, OperationsSorter 有两种配置,一种是 ALPHA (字母顺序),另一种是 METHOD (根据HTTP方法排序)。这两种排序也还行,但是还是希望可以根据类中方法顺序进行排序。

@Bean
public UiConfiguration uiConfig() {
    return UiConfigurationBuilder.builder()
            .docExpansion(DocExpansion.NONE) // 接口文档展开
            .operationsSorter(OperationsSorter.ALPHA) // http操作排序
            .defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
            .tagsSorter(TagsSorter.ALPHA)
            .build();
}

6.3 参数顺序

自 V2.9 后,参数顺序就变为字母顺序,不能更改,具体 ISSUE: 2.9.0 change the order of parameters in operations。这非常影响查看,比如参数 startTime 和 endTime 就分隔开来了,希望改成类中的字段顺序。

6.4 model property 顺序

@ApiModelProperty position 失效,具体 ISSUE : Model properties are alphabetically sorted (without order attribute),维护者说会在v3.0.1 版本修复这个 bug ,但是已经两年没更新,最新版本是 v3.0.0.

参考

  1. https://en.wikipedia.org/wiki/Swagger_(software)

  2. http://springfox.github.io/springfox/docs/current/#springfox-spring-mvc-and-spring-boot

  3. https://github.com/springfox/springfox-demos

  4. Swagger2 中 paramType

  5. https://github.com/Swagger2Markup/swagger2markup-maven-project-template

  6. swagger+asciidoctor 导出PDF中文缺失乱码问题解决

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aabond

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值