SpringBoot + Swagger + ReDoc 笔记

名称解释

在开始使用 Swagger 之前,我们先来了解下几个概念。

名词 释义
Swagger Swagger 是一个 RESTful 接口规范,现在流行的版本有 2.0 和 3.0 。
OpenAPI OpenAPI 规范就是之前的 Swagger 规范,只是换了个名字。
swagger.json/swagger.yaml swagger.json 或 swagger.yaml 是符合 Swagger 规范的接口描述文件。文件名称随意,这里只是举例。
Springfox Springfox 套件可以为 Spring 系列项目自动生成 swagger.json,还可以集成 Swagger UI。
Swagger UI Swagger UI 通过解析 swagger.[json/yaml],来生成在线接口文档。
ReDoc ReDoc 是另一个 Swagger UI 工具。

Springfox

Springfox 当前有两个主要版本:正式版 2.9.2快照版 3.0.0-SNAPSHOT。下面先介绍正式版的使用,建议读者先试用正式版。

2.9.2

Maven 依赖



    2.9.2



    
    
        io.springfox
        springfox-swagger2
        ${springfox.version}
    
    
    
        io.springfox
        springfox-swagger-ui
        ${springfox.version}
    

编写 Swagger 配置类

在 SpringBoot 启动类同级目录下添加该配置类。
配置类 SwaggerConfig 上添加 @Configuration 注解,是为了让 Spring 识别到这是一个配置类。

package org.qadoc.demo.web;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;

/**
 * Swagger 配置类 
* 创建时间:2019/4/10 15:35
* @author xxx */ @Configuration public class SwaggerConfig { @Bean public Docket demoAPI(){ return new Docket(DocumentationType.SWAGGER_2) //采用 Swagger 2.0 规范 .select() .apis(RequestHandlerSelectors.any()) //所有API接口类都生成文档 .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))//方法一:不展示 Spring 自带的 error Controller //.apis(RequestHandlerSelectors.basePackage("org.qadoc.demo.web.controller"))//方法二:不展示 Spring 自带的 error Controller //.paths(Predicates.not(PathSelectors.regex("/error.*")))//方法三(3.0.0不适用):不展示 Spring 自带的 error Controller .paths(PathSelectors.any()) .build() //.pathMapping("/") //.directModelSubstitute(LocalDate.class,String.class) //.genericModelSubstitutes(ResponseEntity.class) .useDefaultResponseMessages(false) //.tags(new Tag("tagName","description")) .apiInfo(apiInfo()) ; } //接口文档基础信息 private ApiInfo apiInfo(){ Contact contact = new Contact("","",""); return new ApiInfo( "Demo API 文档", "Demo API 文档(Web端)", "1.0.0", "", contact, "", "", new ArrayList<>() ); } }

启用 Swagger

在 SpringBoot 的启动类上添加 @EnableSwagger2 注解。

package org.qadoc.demo.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
public class DemoWebApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoWebApplication.class, args);
  }

}

Swagger 文档注解

Swagger 的文档注解有三类:

  • resource(Controller 类) 注解
  • operation(API 方法)注解
  • API models(实体类)注解

注解概览

注解 描述
@Api 标记一个类为 Swagger 资源。
@ApiImplicitParam 表示 API Operation 中的单个参数。
@ApiImplicitParams 包装注解,包含多个 @ApiImplicitParam 注解
@ApiModel 提供 Swagger models 的附加信息
@ApiModelProperty 添加和操作 model 属性的数据。
@ApiOperation 描述一个特定路径的 operation(通常是 HTTP 方法)
@ApiParam 为 operation 参数添加额外的 meta-data。
@ApiResponse 描述 operation 可能的响应。
@ApiResponses 包装注解,包含多个 @ApiResponse 注解。
@ResponseHeader 表示响应头。

Resource API 注解

@Api

声明该 API 接口类需要生成文档。

@Api(tags = {"应用健康检查"})
@RestController
@RequestMapping(value = "/healthcheck")
public class HealthCheckController {
    ...
}

属性列表

属性 描述
tags 属性用来对接口进行分组管理。当然你可以添加多个 tag,那么该类下的接口会在这两个分组里出现。

注意事项:

如果没有指定响应的 Content-Type ,springfox 的默认值是 */*。有两种指定方式。

  1. 在 Controller 类或方法上的 @RequestMapping 注解中增加 produces 属性值。
@RequestMapping(value = "/healthcheck",produces = MediaType.APPLICATION_JSON_VALUE)
  1. 在 Swagger 配置类中指定默认值。
docket
...
    .produces(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
    .consumes(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
...

@ApiIgnore

声明该 API 接口类不需要生成文档。

@ApiIgnore("过时的API")
@ConditionalOnExpression("false")
@RestController
@RequestMapping(value = "/record/xianbank")
public class RecordXianBankController {
    ...
}

Operation 注解

@ApiOperation

用于接口方法上,描述该接口相关信息。

@ApiOperation(
  nickname = "healthCheckUsingGet",
  value = "应用健康检查",
  notes = "用于检查应用是否可以正常访问。",
  produces = MediaType.APPLICATION_JSON_VALUE,
  response = HealthCheckRes.class
)
@GetMapping()
public BaseResult healthCheck() {
    ...
}

属性列表

属性 描述
nickname operationID,接口唯一标识
value 接口名称
notes 接口描述信息
produces 响应 Content-Type,示例:"application/json, application/xml"
consumes 请求 Content-Type,示例:"application/json, application/xml"
response response body Model,响应体结构及单个字段示例

@ApiImplicitParams 和 @ApiImplicitParam

描述接口的请求信息。

@ApiOperation(
    ...
)
//请求参数
@ApiImplicitParams({
    @ApiImplicitParam(
        name = "id",value = "用户 ID",paramType = ParamType.PATH,required = true, dataType = "Long", example = "823928392"
    )
})
@DeleteMapping("/path/{id}")
public BaseResult testSwaggerWithPath(@PathVariable Long id){
  logger.info(id.toString());
  SwaggerTestRes res = new SwaggerTestRes();
  res.setId(id);
  res.setName("刘备");
  res.setAge(180);
  res.setSex('0');
  res.setAddress("蜀山大地");
  return new BaseResult(res);
}

@ApiImplicitParam 属性列表

属性 类型 描述
name String 参数名称。
value String 参数描述。
paramType String 请求参数类型,String类型,枚举值包括:path、query、body、header、form。
required boolean 是否必传,true 为必传,默认为 false。
dataType String 参数数据类型,一般为类名。
dataTypeClass Class 参数数据类型,值为 Xxx.class。
example String 参数示例,针对非 Body 类型的参数。
examples Example 参数示例,针对 Body 类型的参数。
详细例子,请看本节后面的完整示例。

@ApiResponses 和 @ApiResponse

描述接口的响应信息。

@ApiOperation(
    ...
)
//请求参数
@ApiImplicitParams({
    ...
})
//响应
@ApiResponses({
    //code重复的情况下,第一个声明的生效。
    @ApiResponse(code = 200,message = "成功"
        //examples 属性,springfox 2.x.x 不支持,需要 3.0.0及以上
        ,examples = @Example(
            value = {
                @ExampleProperty(mediaType = "一个示例",value = "{\n" +
                    "  \"code\": \"0000\",\n" +
                    "  \"data\": {\n" +
                    "    \"address\": \"马尔代夫\",\n" +
                    "    \"age\": 66,\n" +
                    "    \"id\": 888888,\n" +
                    "    \"name\": \"小虾米\",\n" +
                    "    \"sex\": 0\n" +
                    "  },\n" +
                    "  \"message\": \"OK\"\n" +
                    "}")}
        )
    ),
    @ApiResponse(code = 400,message = "你一定是干坏事了")
})
@DeleteMapping("/path/{id}")
public BaseResult testSwaggerWithPath(@PathVariable Long id){
  logger.info(id.toString());
  SwaggerTestRes res = new SwaggerTestRes();
  res.setId(id);
  res.setName("刘备");
  res.setAge(180);
  res.setSex('0');
  res.setAddress("蜀山大地");
  return new BaseResult(res);
}
属性 类型 描述
code String 接口响应状态码。
message String 接口响应状态信息。
examples Example 响应示例,mediaType 为示例名称,value 为示例值。

Model 注解

@ApiModel

描述 Model(实体类)。

@ApiModel(value = "SwaggerTestRes",description = "Swagger Demo 演示实体类")
public class SwaggerTestRes {

  ...

}
属性 类型 描述
value String Model 名称,一般为实体类类名。
description String Model 描述信息。

@ApiModelProperty

描述 Model 属性。

@ApiModel(value = "SwaggerTestRes",description = "Swagger Demo 演示实体类")
public class SwaggerTestRes {

  @ApiModelProperty(value = "用户 ID",example = "23829832983")
  private Long id;
  @ApiModelProperty(value = "姓名",example = "老顽童")
  private String name;
  @ApiModelProperty(value = "年龄",example = "199",allowableValues = "range[0,200]")
  private int age;
  @ApiModelProperty(value = "性别。0:男,1:女。",example = "0",allowableValues = "0,1")
  private char sex;
  @ApiModelProperty(value = "家庭住址",example = "中国浙江省杭州市滨江区上峰电商产业园")
  private String address;

  ...

}
属性 类型 描述
value String 属性描述。
example String 属性示例。
allowableValues String 限制参数值的范围。有三种限制类型。第一种,用英文逗号分隔的枚举值,如:first, second, third。第二种,固定范围,如:range[1, 5]range(1, 5)range[1, 5)。第三种,接受无穷大的值范围,如:range[1, infinity]range[-infinity, 100]

如果出现 @ApiModelProperty throwing NumberFormatException if example value is not set  错误,修改 pom.xml 为:


    io.springfox
    springfox-swagger2
    2.9.2
    
        
            io.swagger
            swagger-annotations
        
        
            io.swagger
            swagger-models
        
    


    io.springfox
    springfox-swagger-ui
    2.9.2


    io.swagger
    swagger-annotations
    1.5.21


    io.swagger
    swagger-models
    1.5.21

Swagger 注解完整示例

实体类

package org.qadoc.demo.web.pojo.base;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;

/**
 * Rest 接口返回值
* 作者: xxx */ @ApiModel(value = "BaseResult",description = "接口返回值统一定义实体") public class BaseResult implements Serializable { private static final long serialVersionUID = -814929216218701299L; @ApiModelProperty(value = "状态码",example = "0000") private String code; @ApiModelProperty(value = "消息",example = "OK") private String message; @ApiModelProperty(value = "接口响应数据") private T data; ... }
package org.qadoc.demo.web.swagger.model.res;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * Swagger 测试:接口返回值实体 
* 创建时间:2019/4/11 18:44
* 作者:xxx */ @ApiModel public class SwaggerTestRes { @ApiModelProperty(value = "用户 ID",example = "23829832983") private Long id; @ApiModelProperty(value = "姓名",example = "老顽童") private String name; @ApiModelProperty(value = "年龄",example = "199",allowableValues = "range[0,200]") private int age; @ApiModelProperty(value = "性别。0:男,1:女。",example = "0",allowableValues = "0,1") private char sex; @ApiModelProperty(value = "家庭住址",example = "中国浙江省xxx产业园") private String address; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }

Controller 类

package org.qadoc.demo.web.controller;

import org.qadoc.demo.web.constant.ParamType;
import org.qadoc.demo.web.pojo.base.BaseResult;
import org.qadoc.demo.web.swagger.model.res.SwaggerTestRes;
import io.swagger.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Swagger 测试 API 类 
* 创建时间:2019/4/11 18:32
* 作者:xxx */ @Api(tags = {"Swagger 测试"}) @RestController @RequestMapping(value = "/swagger/test") public class SwaggerTestController { private static final Logger logger = LoggerFactory.getLogger(SwaggerTestController.class); @ApiOperation( value = "Path 参数测试", notes = "根据用户 ID 删除用户。", produces = MediaType.APPLICATION_JSON_VALUE ) //请求参数 @ApiImplicitParams({ @ApiImplicitParam( name = "id",value = "用户 ID",paramType = ParamType.PATH,required = true, dataType = "Long", example = "823928392" ) }) //响应 @ApiResponses({ //code重复的情况下,第一个声明的生效。 @ApiResponse(code = 200,message = "成功" //springfox 2.x.x 不支持,需要 3.0.0及以上 ,examples = @Example( value = { @ExampleProperty(mediaType = "一个示例",value = "{\n" + " \"code\": \"0000\",\n" + " \"data\": {\n" + " \"address\": \"马尔代夫\",\n" + " \"age\": 66,\n" + " \"id\": 888888,\n" + " \"name\": \"小虾米\",\n" + " \"sex\": 0\n" + " },\n" + " \"message\": \"OK\"\n" + "}")} ) ), @ApiResponse(code = 400,message = "你一定是干坏事了") }) @DeleteMapping("/path/{id}") public BaseResult testSwaggerWithPath(@PathVariable Long id){ logger.info(id.toString()); SwaggerTestRes res = new SwaggerTestRes(); res.setId(id); res.setName("刘备"); res.setAge(180); res.setSex('0'); res.setAddress("蜀山大地"); return new BaseResult(res); } @ApiOperation( value = "Query 参数测试", notes = "根据用户姓名和性别查询用户列表。", produces = MediaType.APPLICATION_JSON_VALUE ) //请求参数 @ApiImplicitParams({ @ApiImplicitParam( name = "name",value = "用户姓名",paramType = ParamType.QUERY,required = true, dataType = "String", example = "成龙" ), @ApiImplicitParam( name = "sex",value = "用户性别",paramType = ParamType.QUERY, dataType = "String", example = "0" ) }) //响应 @ApiResponses({ //code重复的情况下,第一个声明的生效。 @ApiResponse(code = 200,message = "成功" //springfox 2.x.x 不支持,需要 3.0.0及以上 ,examples = @Example(value = { @ExampleProperty(mediaType = "名字中有JSON",value = "{\n" + " \"code\": \"0000\",\n" + " \"data\": [{\n" + " \"address\": \"有JSON王国\",\n" + " \"age\": 88,\n" + " \"id\": 2353534,\n" + " \"name\": \"蜘蛛侠\",\n" + " \"sex\": 0\n" + " }],\n" + " \"message\": \"OK\"\n" + "}"), @ExampleProperty(mediaType = "名字中无JS0N",value = "{\n" + " \"code\": \"0000\",\n" + " \"data\": [{\n" + " \"address\": \"无JSON王国\",\n" + " \"age\": 99,\n" + " \"id\": 673423,\n" + " \"name\": \"蝙蝠侠\",\n" + " \"sex\": 0\n" + " }],\n" + " \"message\": \"OK\"\n" + "}")} ) ) }) @GetMapping("/query") public BaseResult> testSwaggerWithQuery(@RequestParam String name, @RequestParam(required = false) char sex){ SwaggerTestRes res1 = new SwaggerTestRes(); res1.setId(23923842L); res1.setName("张成龙"); res1.setSex('0'); res1.setAge(26); res1.setAddress("火星殖民地A区76街道"); SwaggerTestRes res2 = new SwaggerTestRes(); res2.setId(92839427947L); res2.setName("成龙龙"); res2.setSex('1'); res2.setAge(24); res2.setAddress("小行星带阿尔法北方殖民地872号"); List list = new ArrayList<>(); list.add(res1); list.add(res2); return new BaseResult(list); } @ApiOperation( value = "Form 参数测试", notes = "登录XX后台。", //实际三种都支持: application/x-www-form-urlencoded, multipart/form-data , queryParam consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE ) //请求参数 @ApiImplicitParams({ @ApiImplicitParam( name = "username",value = "用户名",paramType = ParamType.FORM,required = true, dataType = "String", example = "18868871111" ), @ApiImplicitParam( name = "password",value = "密码",paramType = ParamType.FORM, required = true,dataType = "String", example = "123456" ) }) @PostMapping("/form") public BaseResult testSwaggerWithForm(String username,String password){ BaseResult result = new BaseResult<>(); result.setData(username); return result; } @ApiOperation( value = "JSON 参数测试", notes = "新增一个用户。", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE ) //请求参数 @ApiImplicitParams({ @ApiImplicitParam( name = "user",value = "用户信息",paramType = ParamType.BODY,required = true, dataType = "SwaggerTestRes", examples = @Example(value = { @ExampleProperty(mediaType = "示例",value = "{\n" + "\"address\": \"花果山水帘洞\",\n" + "\"age\": 500,\n" + "\"id\": 66666666,\n" + "\"name\": \"孙悟空\",\n" + "\"sex\": 0\n" + "}") }) ) }) @PostMapping("/body") public BaseResult testSwaggerWithJSON(@RequestBody SwaggerTestRes user){ return new BaseResult(user); } }

3.0.0-SNAPSHOT

Maven 依赖


    3.0.0-SNAPSHOT



    
        jcenter-snapshots
        jcenter
        http://oss.jfrog.org/artifactory/oss-snapshot-local/
    




    
        org.springframework.integration
        spring-integration-http
    
    
    
        io.springfox
        springfox-swagger2
        ${springfox.version}
    
    
        io.springfox
        springfox-swagger-ui
        ${springfox.version}
    
    
        io.springfox
        springfox-spring-webmvc
        ${springfox.version}
    
    
        io.springfox
        springfox-spring-integration-webmvc
        ${springfox.version}
    

    
    
        com.google.guava
        guava
        27.1-jre
    

启用 Swagger

在 SpringBoot 的启动类上添加 @EnableSwagger2WebMvc 注解。

和 2.9.2 版本的区别

  1. Maven 依赖包不同。
  2. 启用 Swagger 的注解不同。
  3. 修复了 @ApiResponse 中 examples 属性没有生效的 bug 。
  4. 修复了 @ApiModelProperty throwing NumberFormatException if example value is not set  的 Bug。

访问 Swagger UI

  1. 启动 SpringBoot 应用。
  2. 访问 http://ip:port/swagger-ui.html 。

ReDoc

获取 swagger.json 的接口

启动 SpringBoot 应用后,得到 swagger.json 的接口地址 http://ip:port/v2/api-docs

配置 Nginx 代理

这里使用 Nginx 做反向代理,是为了解决 ReDoc 的跨域问题。

server
{
    listen 80;
    listen 443;
    server_name doc.xxx.cn;

    ssl on;
    ssl_certificate   cert/xxx.cn/cert.xxx.cn.crt;
    ssl_certificate_key  cert/xxx.cn/cert.xxx.cn.key;
    ssl_session_timeout 5m;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

    add_header Access-Control-Allow-Origin *; //必须加这句,否则访问时会有跨域问题
    index index.html index.htm;
      root /home/wwwroot/doc.xxx.cn/;

    access_log      logs/doc.xxx.cn.access.log;
    error_log       logs/doc.xxx.cn.error.log;
    
    location ^~ /swagger/mocklab {
      proxy_pass  http://10.200.4.26:9101/v2/api-docs;
    }

}

访问在线接口文档

方法一:自己写个静态 HTML 页面,放到内网服务器上。



  
    ReDoc
    
    
    
    

    
    
  
  
    
    
    
  

方法二:使用官方的在线交互 Demo,把自己的接口地址 https://doc.xxx.cn/swagger/mocklab 填进去。

文档效果

ReDoc 不完善之处

  1. 当请求 Content-Type 为 application/jsonapplication/x-www-form-urlencoded 时,不展示 @ApiImplicitParam example 属性(x-example 节点) 的值。
  2. 当请求 Content-Type 为 application/json 时,不展示 @ApiImplicitParam examples 属性(x-examples 节点) 的值。
  3. 当 @ApiResponse 下 @ExampleProperty 的 mediaType 属性值中包含 json 字符(不区分大小写)时,页面展示的 JSON 没有转义,如图:

你可能感兴趣的:(swagger,springboot)