名称解释
在开始使用 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 的默认值是 */*。有两种指定方式。
- 在 Controller 类或方法上的 @RequestMapping 注解中增加 produces 属性值。
@RequestMapping(value = "/healthcheck",produces = MediaType.APPLICATION_JSON_VALUE)
- 在 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 版本的区别
- Maven 依赖包不同。
- 启用 Swagger 的注解不同。
- 修复了 @ApiResponse 中 examples 属性没有生效的 bug 。
- 修复了 @ApiModelProperty throwing NumberFormatException if example value is not set 的 Bug。
访问 Swagger UI
- 启动 SpringBoot 应用。
- 访问 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 不完善之处
- 当请求 Content-Type 为
application/json
和application/x-www-form-urlencoded
时,不展示 @ApiImplicitParam example 属性(x-example 节点) 的值。 - 当请求 Content-Type 为
application/json
时,不展示 @ApiImplicitParam examples 属性(x-examples 节点) 的值。 - 当 @ApiResponse 下 @ExampleProperty 的 mediaType 属性值中包含 json 字符(不区分大小写)时,页面展示的 JSON 没有转义,如图: