在開始使用 Swagger 以前,咱們先來了解下幾個概念。css
名詞 | 釋義 |
---|---|
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 當前有兩個主要版本:正式版 2.9.2
和 快照版 3.0.0-SNAPSHOT
。下面先介紹正式版的使用,建議讀者先試用正式版。html
<properties> <springfox.version>2.9.2</springfox.version> </properties> <dependencies> <!-- 生成 Swagger 文檔 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox.version}</version> </dependency> <!-- 添加 Springfox Swagger UI 支持 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox.version}</version> </dependency> </dependencies>
在 SpringBoot 啓動類同級目錄下添加該配置類。
配置類 SwaggerConfig
上添加 @Configuration
註解,是爲了讓 Spring
識別到這是一個配置類。java
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 配置類 <br> * 建立時間:2019/4/10 15:35<br> * @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<>() ); } }
在 SpringBoot 的啓動類上添加 @EnableSwagger2
註解。git
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 的文檔註解有三類:github
註解概覽web
註解 | 描述 |
---|---|
@Api | 標記一個類爲 Swagger 資源。 |
@ApiImplicitParam | 表示 API Operation 中的單個參數。 |
@ApiImplicitParams | 包裝註解,包含多個 @ApiImplicitParam 註解 |
@ApiModel | 提供 Swagger models 的附加信息 |
@ApiModelProperty | 添加和操做 model 屬性的數據。 |
@ApiOperation | 描述一個特定路徑的 operation(一般是 HTTP 方法) |
@ApiParam | 爲 operation 參數添加額外的 meta-data。 |
@ApiResponse | 描述 operation 可能的響應。 |
@ApiResponses | 包裝註解,包含多個 @ApiResponse 註解。 |
@ResponseHeader | 表示響應頭。 |
@Apispring
聲明該 API 接口類須要生成文檔。apache
@Api(tags = {"應用健康檢查"}) @RestController @RequestMapping(value = "/healthcheck") public class HealthCheckController { ... }
屬性列表npm
屬性 | 描述 |
---|---|
tags | 屬性用來對接口進行分組管理。固然你能夠添加多個 tag,那麼該類下的接口會在這兩個分組裏出現。 |
注意事項:json
若是沒有指定響應的 Content-Type ,springfox 的默認值是 */*。有兩種指定方式。
@RequestMapping(value = "/healthcheck",produces = MediaType.APPLICATION_JSON_VALUE)
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 { ... }
@ApiOperation
用於接口方法上,描述該接口相關信息。
@ApiOperation( nickname = "healthCheckUsingGet", value = "應用健康檢查", notes = "用於檢查應用是否能夠正常訪問。", produces = MediaType.APPLICATION_JSON_VALUE, response = HealthCheckRes.class ) @GetMapping() public BaseResult<HealthCheckRes.AppStatus> 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<SwaggerTestRes> 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<SwaggerTestRes> 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 爲示例值。 |
@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 爲:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> <exclusions> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> </exclusion> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.21</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>1.5.21</version> </dependency>
實體類
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 接口返回值<br> * 做者: xxx */ @ApiModel(value = "BaseResult",description = "接口返回值統必定義實體") public class BaseResult<T> 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 測試:接口返回值實體 <br> * 建立時間:2019/4/11 18:44<br> * 做者: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 類 <br> * 建立時間:2019/4/11 18:32<br> * 做者: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<SwaggerTestRes> 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<List<SwaggerTestRes>> 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<SwaggerTestRes> 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<String> testSwaggerWithForm(String username,String password){ BaseResult<String> 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<SwaggerTestRes> testSwaggerWithJSON(@RequestBody SwaggerTestRes user){ return new BaseResult(user); } }
<properties> <springfox.version>3.0.0-SNAPSHOT</springfox.version> </properties> <repositories> <repository> <id>jcenter-snapshots</id> <name>jcenter</name> <url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-http</artifactId> </dependency> <!-- Swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-spring-webmvc</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-spring-integration-webmvc</artifactId> <version>${springfox.version}</version> </dependency> <!-- 筆者在 Swagger 配置類時用到,能夠不引入 --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.1-jre</version> </dependency> </dependencies>
在 SpringBoot 的啓動類上添加 @EnableSwagger2WebMvc
註解。
啓動 SpringBoot 應用後,獲得 swagger.json 的接口地址 http://ip:port/v2/api-docs
。
這裏使用 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 頁面,放到內網服務器上。
<!DOCTYPE html> <html> <head> <title>ReDoc</title> <!-- needed for adaptive design --> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> <!-- ReDoc doesn't change outer page styles --> <style> body { margin: 0; padding: 0; } </style> </head> <body> <!-- 這裏填寫 swagger 文件訪問地址或接口訪問地址 --> <redoc spec-url='https://doc.xxx.cn/swagger/mocklab' untrustedSpec=true></redoc> <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script> </body> </html>
方法二:使用官方的在線交互 Demo,把本身的接口地址 https://doc.xxx.cn/swagger/mocklab
填進去。
application/json
和 application/x-www-form-urlencoded
時,不展現 @ApiImplicitParam example 屬性(x-example 節點) 的值。application/json
時,不展現 @ApiImplicitParam examples 屬性(x-examples 節點) 的值。