當(dāng)前位置:首頁(yè) > IT技術(shù) > Windows編程 > 正文

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

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

1. OpenAPI 規(guī)范介紹

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

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

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

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

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

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

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

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

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

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

2. Swagger 介紹

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

Swagger 有幾個(gè)主要的產(chǎn)品。

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

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

Swagger Codegen – 一個(gè)根據(jù) OpenAPI 生成調(diào)用代碼的工具。

如果你想了解更多信息,可以訪問(wèn) Swagger 官方網(wǎng)站 https://swagger.io 。

3. Springfox 介紹

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

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

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

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

更多的 Springfox 介紹,可以訪問(wèn) Springfox 官方網(wǎng)站。

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

4. Springboot 整合 Swagger

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

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

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

如何創(chuàng)建一個(gè) Springboot 項(xiàng)目這里不提,你可以直接從 Springboot 官方 下載一個(gè)標(biāo)準(zhǔn)項(xiàng)目,也可以使用 idea 快速創(chuàng)建一個(gè) Springboot 項(xiàng)目,也可以順便拷貝一個(gè) Springboot 項(xiàng)目過(guò)來(lái)測(cè)試,總之,方式多種多樣,任你選擇。

下面演示如何在 Springboot 項(xiàng)目中使用 swagger2。

(2)引入依賴

這里主要是引入了 springfox-swagger2,可以通過(guò)注解生成 JSON 格式的 OpenAPI 接口文檔,然后由于 Springfox 需要依賴 jackson,所以引入之。springfox-swagger-ui 可以把生成的 OpenAPI 接口文檔顯示為頁(yè)面。Lombok 的引入可以通過(guò)注解為實(shí)體類生成 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 的配置通過(guò)一個(gè) Docket 來(lái)包裝,Docket 里的 apiInfo 方法可以傳入關(guān)于接口總體的描述信息。而 apis 方法可以指定要掃描的包的具體路徑。在類上添加 @Configuration 聲明這是一個(gè)配置類,最后使用 @EnableSwagger2 開(kāi)啟 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("公眾號(hào):未讀代碼(weidudaima) springboot-swagger2 在線借口文檔") .termsOfServiceUrl("https://www.codingme.net") .contact("達(dá)西呀") .version("1.0") .build(); }}

(4)代碼編寫(xiě)

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

參數(shù)實(shí)體類 User.java ,使用 @ApiModel 和 @ApiModelProperty 描述參數(shù)對(duì)象,使用 @NotNull 進(jìn)行數(shù)據(jù)校驗(yàn),使用 @Data 為參數(shù)實(shí)體類自動(dòng)生成 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;/** * 

* 用戶實(shí)體類 * * @Author niujinpeng * @Date 2018/12/19 17:13 */@Data@NoArgsConstructor@AllArgsConstructor@ApiModel(value = "用戶對(duì)象")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;}

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

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 = "獲取單個(gè)用戶信息", 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ī)范和好用,使用一個(gè)統(tǒng)一的類進(jìn)行接口響應(yīng)。

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

(5)運(yùn)行訪問(wèn)

直接啟動(dòng) Springboog 項(xiàng)目,可以看到控制臺(tái)輸出掃描到的各個(gè)接口的訪問(wèn)路徑,其中就有 /2/api-docs 。

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

因?yàn)樯厦嫖覀冊(cè)谝胍蕾嚂r(shí),也引入了 springfox-swagger-ui 包,所以還可以訪問(wèn) API 的頁(yè)面文檔。訪問(wèn)路徑是 /swagger-ui.html,訪問(wèn)看到的效果可以看下圖。

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

(6)調(diào)用測(cè)試

springfox-swagger-ui 不僅是生成了API文檔,還提供了調(diào)用測(cè)試功能。下面是在頁(yè)面上測(cè)試獲取單個(gè)用戶信息的過(guò)程。

點(diǎn)擊接口 [/user/{username}] 獲取單個(gè)用戶信息。

點(diǎn)擊 **Try it out** 進(jìn)入測(cè)試傳參頁(yè)面。

輸入?yún)?shù),點(diǎn)擊 Execute 藍(lán)色按鈕執(zhí)行調(diào)用。

查看返回信息。

5. 常見(jiàn)報(bào)錯(cuò)

如果你在程序運(yùn)行中經(jīng)常發(fā)現(xiàn)像下面這樣的報(bào)錯(cuò)。

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 注解是否設(shè)置了 example 值,如果沒(méi)有,那就需要設(shè)置一下,像下面這樣。

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


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

開(kāi)通會(huì)員,享受整站包年服務(wù)立即開(kāi)通 >