當前位置:首頁 > IT技術 > Windows編程 > 正文

API中文文檔:Swagger詳解
2021-10-27 14:31:33

目前來說,在 Java 領域使用?Springboot?構建微服務是比較流行的,在構建微服務時,我們大多數(shù)會選擇暴漏一個 REST API 以供調用。又或者公司采用前后端分離的開發(fā)模式,讓前端和后端的工作由完全不同的工程師進行開發(fā)完成。不管是微服務還是這種前后端分離開發(fā),維持一份完整的及時更新的 REST API 文檔,會極大的提高我們的工作效率。而傳統(tǒng)的文檔更新方式(如手動編寫),很難保證文檔的及時性,經常會年久失修,失去應有的意義。因此選擇一種新的 API 文檔維護方式很有必要,這也是這篇文章要介紹的內容。

1. OpenAPI 規(guī)范介紹

OpenAPI Specification 簡稱 OAS,中文也稱 OpenAPI 描述規(guī)范,使用 OpenAPI 文件可以描述整個 API,它制定了一套的適合通用的與語言無關的 REST API 描述規(guī)范,如 API 路徑規(guī)范、請求方法規(guī)范、請求參數(shù)規(guī)范、返回格式規(guī)范等各種相關信息,使人類和計算機都可以不需要訪問源代碼就可以理解和使用服務的功能。

下面是 OpenAPI 規(guī)范中建議的 API 設計規(guī)范,基本路徑設計規(guī)范。

https://api.example.com/v1/users?role=admin&status=active________________________/____/ ______________________/ server URL endpoint query parameters path

對于傳參的設計也有規(guī)范,可以像下面這樣:

路徑參數(shù) , 例如 /users/{id}

查詢參數(shù) , 例如 /users?role=未讀代碼

header 參數(shù) , 例如 X-MyHeader: Value

cookie 參數(shù) , 例如 Cookie: debug=0; csrftoken=BUSe35dohU3O1MZvDCU

OpenAPI 規(guī)范的東西遠遠不止這些,目前 OpenAPI 規(guī)范最新版本是 3.0.2,如果你想了解更多的 OpenAPI 規(guī)范,可以訪問下面的鏈接。

OpenAPI Specification (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)

2. Swagger 介紹

很多人都以為 Swagger 只是一個接口文檔生成框架,其實并不是。 Swagger 是一個圍繞著 OpenAPI Specification (OAS,中文也稱 OpenAPI規(guī)范)構建的一組開源工具??梢詭椭銖?API 的設計到 API 文檔的輸出再到 API 的測試,直至最后的 API 部署等整個 API 的開發(fā)周期提供相應的解決方案,是一個龐大的項目。 Swagger 不僅免費,而且開源,不管你是企業(yè)用戶還是個人玩家,都可以使用 Swagger 提供的方案構建令人驚艷的 REST API 。

Swagger 有幾個主要的產品。

Swagger Editor – 一個基于瀏覽器的 Open API 規(guī)范編輯器。

Swagger UI – 一個將 OpenAPI 規(guī)范呈現(xiàn)為可交互在線文檔的工具。

Swagger Codegen – 一個根據 OpenAPI 生成調用代碼的工具。

如果你想了解更多信息,可以訪問 Swagger 官方網站 https://swagger.io 。

3. Springfox 介紹

源于 Java 中 Spring 框架的流行,讓一個叫做 Marrty Pitt 的老外有了為 SpringMVC 添加接口描述的想法,因此他創(chuàng)建了一個遵守 OpenAPI 規(guī)范(OAS)的項目,取名為 swagger-springmvc,這個項目可以讓 Spring 項目自動生成 JSON 格式的 OpenAPI 文檔。這個框架也仿照了 Spring 項目的開發(fā)習慣,使用注解來進行信息配置。

后來這個項目發(fā)展成為 Springfox ,再后來擴展出 springfox-swagger2 ,為了讓 JSON 格式的 API 文檔更好的呈現(xiàn),又出現(xiàn)了 springfox-swagger-ui 用來展示和測試生成的 OpenAPI 。這里的 springfox-swagger-ui 其實就是上面介紹的 Swagger-ui,只是它被通過 webjar 的方式打包到 jar 包內,并通過 maven 的方式引入進來。

上面提到了 Springfox-swagger2 也是通過注解進行信息配置的,那么是怎么使用的呢?下面列舉常用的一些注解,這些注解在下面的 Springboot 整合 Swagger 中會用到。

注解示例描述@ApiModel@ApiModel(value = “用戶對象”)描述一個實體對象@ApiModelProperty@ApiModelProperty(value = “用戶ID”, required = true, example = “1000”)描述屬性信息,執(zhí)行描述,是否必須,給出示例@Api@Api(value = “用戶操作 API(v1)”, tags = “用戶操作接口”)用在接口類上,為接口類添加描述@ApiOperation@ApiOperation(value = “新增用戶”)描述類的一個方法或者說一個接口@ApiParam@ApiParam(value = “用戶名”, required = true)描述單個參數(shù)

更多的 Springfox 介紹,可以訪問 Springfox 官方網站。

Springfox Reference Documentation (http://springfox.github.io)

4. Springboot 整合 Swagger

就目前來說 ,Springboot 框架是非常流行的微服務框架,在微服務框架下,很多時候我們都是直接提供 REST API 的。REST API 如果沒有文檔的話,使用者就很頭疼了。不過不用擔心,上面說了有一位叫 Marrty Pitt 的老外已經創(chuàng)建了一個發(fā)展成為 Springfox 的項目,可以方便的提供 JSON 格式的 OpenAPI 規(guī)范和文檔支持。且擴展出了 springfox-swagger-ui 用于頁面的展示。

需要注意的是,這里使用的所謂的 Swagger 其實和真正的 Swagger 并不是一個東西,這里使用的是 Springfox 提供的 Swagger 實現(xiàn)。它們都是基于 OpenAPI 規(guī)范進行 API 構建。所以也都可以 Swagger-ui 進行 API 的頁面呈現(xiàn)。

(1)創(chuàng)建項目

如何創(chuàng)建一個 Springboot 項目這里不提,你可以直接從 Springboot 官方 下載一個標準項目,也可以使用 idea 快速創(chuàng)建一個 Springboot 項目,也可以順便拷貝一個 Springboot 項目過來測試,總之,方式多種多樣,任你選擇。

下面演示如何在 Springboot 項目中使用 swagger2。

(2)引入依賴

這里主要是引入了 springfox-swagger2,可以通過注解生成 JSON 格式的 OpenAPI 接口文檔,然后由于 Springfox 需要依賴 jackson,所以引入之。springfox-swagger-ui 可以把生成的 OpenAPI 接口文檔顯示為頁面。Lombok 的引入可以通過注解為實體類生成 get/set 方法。

org.springframework.boot spring-boot-starter-web  spring-boot-starter-json org.springframework.bootio.springfox springfox-swagger2 2.9.2io.springfox springfox-swagger-ui 2.9.2com.fasterxml.jackson.core jackson-databind 2.5.4org.projectlombok lombok true

(3)配置 Springfox-swagger

Springfox-swagger 的配置通過一個 Docket 來包裝,Docket 里的 apiInfo 方法可以傳入關于接口總體的描述信息。而 apis 方法可以指定要掃描的包的具體路徑。在類上添加 @Configuration 聲明這是一個配置類,最后使用 @EnableSwagger2 開啟 Springfox-swagger2。

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;/** * 

* Springfox-swagger2 配置 * * @Author niujinpeng * @Date 2019/11/19 23:17 */@Configuration@EnableSwagger2public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("net.codingme.boot.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("未讀代碼 API") .description("公眾號:未讀代碼(weidudaima) springboot-swagger2 在線借口文檔") .termsOfServiceUrl("https://www.codingme.net") .contact("達西呀") .version("1.0") .build(); }}

(4)代碼編寫

文章不會把所有代碼一一列出來,這沒有太大意義,所以只貼出主要代碼,完整代碼會上傳到 Github,并在文章底部附上 Github 鏈接。

參數(shù)實體類 User.java ,使用 @ApiModel 和 @ApiModelProperty 描述參數(shù)對象,使用 @NotNull 進行數(shù)據校驗,使用 @Data 為參數(shù)實體類自動生成 get/set 方法。

import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.format.annotation.DateTimeFormat;import javax.validation.constraints.NotNull;import java.util.Date;/** * 

* 用戶實體類 * * @Author niujinpeng * @Date 2018/12/19 17:13 */@Data@NoArgsConstructor@AllArgsConstructor@ApiModel(value = "用戶對象")public class User { /** * 用戶ID * * @Id 主鍵 * @GeneratedValue 自增主鍵 */ @NotNull(message = "用戶 ID 不能為空") @ApiModelProperty(value = "用戶ID", required = true, example = "1000") private Integer id; /** * 用戶名 */ @NotNull(message = "用戶名不能為空") @ApiModelProperty(value = "用戶名", required = true) private String username; /** * 密碼 */ @NotNull(message = "密碼不能為空") @ApiModelProperty(value = "用戶密碼", required = true) private String password; /** * 年齡 */ @ApiModelProperty(value = "用戶年齡", example = "18") private Integer age; /** * 生日 */ @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") @ApiModelProperty(value = "用戶生日") private Date birthday; /** * 技能 */ @ApiModelProperty(value = "用戶技能") private String skills;}

編寫 Controller 層,使用 @Api 描述接口類,使用 @ApiOperation 描述接口,使用 @ApiParam描述接口參數(shù)。代碼中在查詢用戶信息的兩個接口上都添加了 tags = "用戶查詢" 標記,這樣這兩個方法在生成 Swagger 接口文檔時候會分到一個共同的標簽組里。

import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import lombok.extern.slf4j.Slf4j;import net.codingme.boot.domain.Response;import net.codingme.boot.domain.User;import net.codingme.boot.enums.ResponseEnum;import net.codingme.boot.utils.ResponseUtill;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.*;import javax.validation.Valid;import javax.validation.constraints.NotNull;import java.util.ArrayList;import java.util.List;/** * 

* 用戶操作 * * @Author niujinpeng * @Date 2019/11/19 23:17 */@Slf4j@RestController(value = "/v1")@Api(value = "用戶操作 API(v1)", tags = "用戶操作接口")public class UserController { @ApiOperation(value = "新增用戶") @PostMapping(value = "/user") public Response create(@Valid User user, BindingResult bindingResult) throws Exception { if (bindingResult.hasErrors()) { String message = bindingResult.getFieldError().getDefaultMessage(); log.info(message); return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message); } else { // 新增用戶信息 do something return ResponseUtill.success("用戶[" + user.getUsername() + "]信息已新增"); } } @ApiOperation(value = "刪除用戶") @DeleteMapping(value = "/user/{username}") public Response delete(@PathVariable("username") @ApiParam(value = "用戶名", required = true) String name) throws Exception { // 刪除用戶信息 do something return ResponseUtill.success("用戶[" + name + "]信息已刪除"); } @ApiOperation(value = "修改用戶") @PutMapping(value = "/user") public Response update(@Valid User user, BindingResult bindingResult) throws Exception { if (bindingResult.hasErrors()) { String message = bindingResult.getFieldError().getDefaultMessage(); log.info(message); return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message); } else { String username = user.getUsername(); return ResponseUtill.success("用戶[" + username + "]信息已修改"); } } @ApiOperation(value = "獲取單個用戶信息", tags = "用戶查詢") @GetMapping(value = "/user/{username}") public Response get(@PathVariable("username") @NotNull(message = "用戶名稱不能為空") @ApiParam(value = "用戶名", required = true) String username) throws Exception { // 查詢用戶信息 do something User user = new User(); user.setId(10000); user.setUsername(username); user.setAge(99); user.setSkills("cnp"); return ResponseUtill.success(user); } @ApiOperation(value = "獲取用戶列表", tags = "用戶查詢") @GetMapping(value = "/user") public Response selectAll() throws Exception { // 查詢用戶信息列表 do something User user = new User(); user.setId(10000); user.setUsername("未讀代碼"); user.setAge(99); user.setSkills("cnp"); List userList = new ArrayList<>(); userList.add(user); return ResponseUtill.success(userList); }}

最后,為了讓代碼變得更加符合規(guī)范和好用,使用一個統(tǒng)一的類進行接口響應。

@Data@AllArgsConstructor@NoArgsConstructor@ApiModel(value = "響應信息")public class Response { /** * 響應碼 */ @ApiModelProperty(value = "響應碼") private String code; /** * 響應信息 */ @ApiModelProperty(value = "響應信息") private String message; /** * 響應數(shù)據 */ @ApiModelProperty(value = "響應數(shù)據") private Collection content;}

(5)運行訪問

直接啟動 Springboog 項目,可以看到控制臺輸出掃描到的各個接口的訪問路徑,其中就有 /2/api-docs 。

這個也就是生成的 OpenAPI 規(guī)范的描述 JSON 訪問路徑,訪問可以看到。

因為上面我們在引入依賴時,也引入了 springfox-swagger-ui 包,所以還可以訪問 API 的頁面文檔。訪問路徑是 /swagger-ui.html,訪問看到的效果可以看下圖。

也可以看到用戶查詢的兩個方法會歸到了一起,原因就是這兩個方法的注解上使用相同的 tag 屬性。

(6)調用測試

springfox-swagger-ui 不僅是生成了API文檔,還提供了調用測試功能。下面是在頁面上測試獲取單個用戶信息的過程。

點擊接口 [/user/{username}] 獲取單個用戶信息。

點擊 **Try it out** 進入測試傳參頁面。

輸入參數(shù),點擊 Execute 藍色按鈕執(zhí)行調用。

查看返回信息。

5. 常見報錯

如果你在程序運行中經常發(fā)現(xiàn)像下面這樣的報錯。

java.lang.NumberFormatException: For input string: ""at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_111]at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_111]at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_111]at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:536) [jackson-databind-2.5.4.jar:2.5.4]at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666) [jackson-databind-2.5.4.jar:2.5.4]at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156) [jackson-databind-2.5.4.jar:2.5.4]at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:113) [jackson-databind-2.5.4.jar:2.5.4]

那么你需要檢查使用了 @ApiModelProperty 注解且字段類型為數(shù)字類型的屬性上, @ApiModelProperty 注解是否設置了 example 值,如果沒有,那就需要設置一下,像下面這樣。

@NotNull(message = "用戶 ID 不能為空")@ApiModelProperty(value = "用戶ID", required = true, example = "10


本文摘自 :https://blog.51cto.com/u

開通會員,享受整站包年服務立即開通 >