使用 OpenAPI 構建 RESTful API 文檔

作爲一名開發者,往往需要編寫程序的 API 文檔,尤其是 Web 後端開發者,在跟前端對接 HTTP 接口的時候,一個好的 API 文檔能夠大大提高協作效率,降低溝通成本,本文就來聊聊如何使用 OpenAPI 構建 HTTP 接口文檔。

OpenAPI

什麼是OpenAPI

OpenAPI 是規範化描述 API 領域應用最廣泛的行業標準,由 OpenAPI Initiative 組織定義並維護,同時也是 Linux 基金會下的一個開源項目。它使用規定的格式來描述 HTTP RESTful API 的定義,以此來規範 RESTful 服務開發過程。使用 JSON 或 YAML 來描述一個標準的、與編程語言無關的 HTTP API 接口。

簡單來說,OpenAPI 就是用來定義 HTTP 接口文檔的一種規範,大家都按照同一套規範來編寫接口文檔,能夠極大地減少溝通成本。

OpenAPI規範

OpenAPI 文檔編寫在一個 .json 或 .yaml 中,推薦將其命名爲 openapi.json 或 openapi.yaml,OpenAPI 文檔其實就是一個單一的 JSON 對象,其中包含符合 OpenAPI 規範中定義的結構字段。
OpenAPI 規範基本信息如下:圖片
OpenAPI 規範內容包含非常多的細節,本文無法一一講解,這裏僅介紹常見的基本信息,以 YAML 爲例進行說明。

一個 YAML 格式的 OpenAPI 文檔示例如下:

openapi: 3.1.0info:  title: Tic Tac Toe  description: |    This API allows writing down marks on a Tic Tac Toe board    and requesting the state of the board or of individual squares.  version: 1.0.0 # 此爲 API 接口文檔版本,與 openapi 版本無關tags:  - name: Gameplaypaths:  # Whole board operations  /board:    get:      summary: Get the whole board      description: Retrieves the current state of the board and the winner.      tags:        - Gameplay      operationId: get-board      responses:        "200":          description: "OK"          content:            application/json:              schema:                $ref: "#/components/schemas/status"  # Single square operations  /board/{row}/{column}:    parameters:      - $ref: "#/components/parameters/rowParam"      - $ref: "#/components/parameters/columnParam"    get:      summary: Get a single board square      description: Retrieves the requested square.      tags:        - Gameplay      operationId: get-square      responses:        "200":          description: "OK"          content:            application/json:              schema:                $ref: "#/components/schemas/mark"        "400":          description: The provided parameters are incorrect          content:            text/html:              schema:                $ref: "#/components/schemas/errorMessage"              example: "Illegal coordinates"...

如果你開發過 API 接口,相信能看懂文檔大部分內容所代表的含義。不必完全掌握其語法,這並不會對閱讀本文接下來的內容造成困擾,因爲稍後我會介紹如何通過代碼註釋的方式自動生成此文檔。如果你想手動編寫 OpenAPI 文檔,那我還是推薦你閱讀下 OpenAPI 規範。閱讀規範是一個比較枯燥的過程,如果你沒有耐心讀完,強烈建議閱讀 OpenAPI 規範入門(https://oai.github.io/Documentation/),
相較於完整版的規範要精簡得多,並且講解更加易於理解。

另外還推薦訪問 OpenAPI Map 網站來掌握 OpenAPI 規範,該網站以思維導圖的形式展現規範的格式以及說明。圖片

OpenAPI.Tools

現在我們知道了 OpenAPI 規範,接下來要做什麼?當然是瞭解 OpenAPI 開放了哪些能力。
有一個叫 OpenAPI.Tools 的網站,分類整理並記錄了社區圍繞 OpenAPI 規範開發的流行工具。圖片
可以看到列表中有很多分類,在我們日常開發中,最經常使用的有三類:

文檔編輯器

文檔編輯器方便我們用來編寫符合 OpenAPI 規範的文檔,有助於提高編寫文檔的效率,就像 VS Code 能夠方便我們編寫代碼一樣。

文檔編輯器有兩種:文本編輯器以及圖形編輯器。

文本編輯器推薦使用在線的 Swagger Editor,能夠實現格式校驗和實時預覽 Swagger 交互式 API 文檔功能,效果如下圖所示:圖片
如果你習慣使用 VS Code,也有相應插件可供使用。圖形編輯器的好處是能夠以可視化的形式編輯內容,不瞭解 OpenAPI 規範語法也能編輯。可以根據自己喜好來進行選擇,如 Stoplight Studio、APIGit 等。

Mock 服務器

當我們使用 OpenAPI 規範來進行接口開發時,往往採用文檔先行的策略,也就是前後端在開發代碼前,先定義好接口文檔,再進行代碼的編寫。此時前端如果想測試接口可用性,而後端代碼還沒有編寫完成,Mock 服務器就派上用場了。Mock 服務器能夠根據所提供的 OpenAPI 接口文檔,自動生成一個模擬的 Web Server。使用 Mock 服務器能夠輕鬆模擬真實的後端接口,方便前端同學進行接口調試。

上面提到的 APIGit 也同時具備此功能。

代碼生成器

還有一種很實用的工具是代碼生成器,代碼生成器有兩種類型:一種是從代碼/註釋生成 OpenAPI 文檔,另一種是從 OpenAPI 文檔生成代碼。

這類工具同樣非常多,且更爲實用。比如我們有一份寫好了的 Go Web Server 代碼,想要自動生成一份 OpenAPI 文檔,就可以使用 go-swagger 這個工具來生成一份 openapi.yaml 文檔。

而如果我們有一份 openapi.yaml 文檔,就可以利用 go-swagger 生成一份 Go SDK 代碼,甚至它還能根據這份 OpenAPI 文檔生成 Go Web Server 的框架代碼,我們只需要在對應的接口裏面實現具體的業務邏輯即可。

Swagger

Swagger是什麼

Swagger 是一套圍繞 OpenAPI 規範所構建的開源工具集,提供了強大和易於使用的工具來充分利用 OpenAPI 規範,Swagger 工具集由最初的 Swagger 規範背後的團隊所開發。Swagger 工具集提供了 API 設計、開發、測試、治理和監控等能力,其中最主要的工具包含如下三個:

  • Swagger Codegen:根據 OpenAPI 規範定義生成服務器存根和客戶端 SDK。
  • Swagger Editor:基於瀏覽器的在線 OpenAPI 規範編輯器。
  • Swagger UI:以 UI 界面的方式可視化展示 OpenAPI 規範定義,並且能夠在瀏覽器中進行交互。

當然 Swagger 也有爲企業用戶提供的收費版本工具 SwaggerHub Enterprise,感興趣的同學可以自行了解。

Swagger和OpenAPI的關係

講到了 Swagger,就不得不提及 Swagger 和 OpenAPI 的聯繫與區別,因爲這二者經常在一起出現。

在 OpenAPI 尚未出現之前,Swagger 代表了 Swagger 規範以及一系列圍繞 Swagger 規範的開源工具集。Swagger 規範最後一個版本是 2.0,之後就捐贈給了 OAI 並被重新命名爲 OpenAPI 規範,所以 OpenAPI 規範第一個版本是 2.0,也就是 Swagger 規範 2.0,而由 OAI 這個組織發佈的第一個 OpenAPI 規範正式版本是3.0.0。

現在,Swagger 規範已被 OpenAPI 規範完全接管並取代。OpenAPI 代表了 OpenAPI 規範以及一系列生態,而 Swagger 則是這個生態中的一部分,是 Swagger 團隊圍繞 OpenAPI 規範所開發的一系列工具集。

Swagger 是 OpenAPI 生態中非常重要的組成部分,因爲它給出了一整套方案,且非常流行。

OpenAPI 在實際開發的應用

接下來我們聊聊以 Go 語言爲例講解 OpenAPI 在實際開發中的應用。前文介紹了編寫 OpenAPI 文檔的兩種編輯器:文本編輯器以及圖形編輯器。在日常開發中,後端可以先使用這類編輯器如 Swagger Editor 編寫出 OpenAPI 文檔,然後將這份文檔交給前端,前端拿到 OpenAPI 文檔後將其導入到 Swagger Editor,就可以在線閱讀接口文檔並與之進行交互,之後前後端就可以並行開發了。

這樣的工作流看起來似乎沒什麼問題,不過編寫 OpenAPI 文檔畢竟是個苦力活,不僅有大量的重複工作,還要求開發者熟練掌握 OpenAPI 規範語法。這對於“愛偷懶”的開發者顯然是無法接受的,就像段子裏說的,程序員最討厭兩件事:1. 寫文檔,2. 別人不寫文檔。而這個問題的解法,當然就是前文提到的代碼生成器。

用Swag生成Swagger文檔

在 Go 語言生態裏,目前有兩個比較流行的開源工具可以生成 Swagger 文檔,分別是 go-swagger 和 swag。它們都能根據代碼中的註釋生成 Swagger 文檔,go-swagger 作爲一款 OpenAPI.Tools 推薦的工具,其功能比 swag 更加強大且 Github Star 數量也更高。不過本文將選擇 swag 來進行介紹,一是因爲 swag 比較輕量,更適合微服務開發;二是如果使用 swag,那麼註釋代碼會離接口代碼更近,升級時方便維護。如果你有更高級的需求,如根據 Swagger 文檔生成客戶端 SDK,服務端存根等,則推薦使用 go-swagger。

注意:在這裏我一直提到的都是生成 Swagger 文檔,而沒有說是 OpenAPI 文檔。因爲無論是 swag 還是功能更強大的 go-swagger,它們目前都僅支持生成 OpenAPI 2.0 文檔,並不支持生成 OpenAPI 3.0+ 文檔,而 OpenAPI 2.0 版本我們更習慣稱其爲 Swagger 文檔。

安裝 Swag

使用 go install 命令即可安裝。

$ go install github.com/swaggo/swag/cmd/swag@latest # 安裝
$ swag --version # 查看版本
swag version v1.8.10

Swag 命令行工具

swag 非常簡潔,僅提供了兩個主要命令 init 和 fmt。

  • 在包含 main.go 文件(默認情況下)的項目根目錄運行 swag init 命令,將會解析 swag 註釋並生成 docs/ 目錄以及 /docs/docs.go、docs/swagger.json、docs/swagger.yaml 三個文件。
$ swag init -h # 查看 init 子命令使用方法NAME:   swag init - Create docs.goUSAGE:   swag init [command options] [arguments...]OPTIONS:   --quiet, -q                            不在控制檯輸出日誌 (default: false)   --generalInfo value, -g value          API 通用信息所在的 Go 源文件路徑,如果是相對路徑則基於 API 解析目錄 (default: "main.go")   --dir value, -d value                  API 解析目錄,多個目錄可用逗號分隔 (default: "./")   --exclude value                        解析掃描時排除的目錄,多個目錄可用逗號分隔   --propertyStrategy value, -p value     結構體字段命名規則,三種:snake_case,camelCase,PascalCase (default: "camelCase")   --output value, -o value               所有生成文件的輸出目錄(swagger.json, swagger.yaml and docs.go)(default:"./docs")   --outputTypes value, --ot value        生成文件的輸出類型(docs.go, swagger.json, swagger.yaml)三種:go,json,yaml (default: "go,json,yaml")   --parseDependency, --pd                解析依賴目錄中的 Go 文件 (default: false)   --markdownFiles value, --md value      指定 API 的描述信息所使用的 Markdown 文件所在的目錄,默認禁用   --parseInternal                        解析 internal 包中的 Go 文件 (default: false)   --generatedTime                        輸出時間戳到輸出文件 `docs.go` 頂部 (default: false)   --parseDepth value                     依賴項解析深度 (default: 100)   --requiredByDefault                    默認情況下,爲所有字段設置 `required` 驗證 (default: false)   --instanceName value                   設置文檔實例名 (default: "swagger")   --parseGoList                          通過 'go list' 解析依賴關係 (default: true)   --tags value, -t value                 逗號分隔的標籤列表,用於過濾指定標籤生成 API 文檔。特殊情況下,如果標籤前綴是 '!' 字符,那麼帶有該標記的 API 將被排除   --help, -h                             顯示幫助信息 (default: false)

注意:以上 swag init 命令可選參數介紹略有刪減,只列出了常用選項,更完整的文檔請參考官方 GitHub 倉庫。

  • swag fmt 命令可以格式化 swag 註釋。
$ swag fmt -h # 查看 fmt 子命令使用方法
NAME:
    swag fmt - format swag comments 
    
USAGE:  
    swag fmt [command options] [arguments...] 
    
OPTIONS:
    --dir value, -d value           API 解析目錄,多個目錄可用逗號分隔 (default: "./") 
    --exclude value                 解析掃描時排除的目錄,多個目錄可用逗號分隔 
    generalInfo value, -g value     API 通用信息所在的 Go 源文件路徑,如果是相對路徑則基於 API 解析目錄 (default: "main.go")
    --help, -h                      顯示幫助信息 (default: false) 

在 Gin 中使用 Swag

在 gin 框架能夠很方便的使用 swag,步驟如下:

  1. 準備項目目錄結構如下:
.
├── go.mod
├── go.sum
└── main.go
  1. 初始化項目
$ go mod init gin-swag
  1. 編寫 main.go 代碼如下:
package main

import (
    "net/http" 
    
    "github.com/gin-gonic/gin"
)
// @title             Swagger Example API
// @version           1.0
// @schemes           http
// @host              localhost:8080
// @BasePath          /api/v1
// @tag.name          example
// @tag.description   示例接口
// Helloworld godoc
//
// @Summary           該操作的簡短摘要
// @Description       操作行爲的詳細說明
// @Tags              example
// @Accept            json
// @Produce           json
// @Success           200 {string} string "Hello World!"
// @Router            /example/helloworld [get]

func Helloworld(g *gin.Context) {
    g.JSON(http.StatusOK, "Hello World!")
}
func main() {
    r := gin.Default()
    v1 := r.Group("/api/v1")
    {
        eg := v1.Group("/example")
        {
            eg.GET("/helloworld", Helloworld)
         }
     } 
    if err := r.Run(":8080"); err != nil {
        panic(err)
        }
 }

代碼中的註釋部分即爲 swag 的註釋語法,稍後通過這些註釋生成 Swagger 文檔。

其中通用 API 信息部分註釋含義如下:圖片
還有一部分註釋代表了 API 操作,其含義如下:圖片
以上這些註釋最終都會對應到 OpenAPI 2.0 規範的某個字段上。

  1. 使用 swag 根據註釋生成 Swagger 文檔,在項目根目錄下(.)執行 swag init,將得到新的目錄結構:
.
├── docs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
└── main.go

可以發現 swag init 生成的三個文件 docs.go、swagger.json、swagger.yaml 默認都在 docs/ 目錄下。

其中 swagger.json、swagger.yaml 正是符合 OpenAPI 2.0 規範的 JSON 和 YAML 接口文檔,例如 swagger.yaml 內容如下:

basePath: /api/v1
host: localhost:8080
info:
    contact: {} 
    title: Swagger Example API 
    version: "1.0" 
  paths: 
    /example/helloworld: 
      get: 
        consumes: 
        - application/json 
        description: 操作行爲的詳細說明 
        produces: 
        - application/json 
        responses: 
          "200": 
            description: Hello World! 
            schema: 
              type: string 
        summary: 該操作的簡短摘要 
        tags: 
        - example 
 schemes:
 - http
 swagger: "2.0"
 tags:
 - description: 示例接口
   name: example 

對比上面代碼中的註釋,很容易將其對應起來,相比於直接編寫 YAML 格式文檔,顯然在代碼中編寫註釋更爲簡單。

將其複製到 Swagger Editor 編輯器中即可查看 Swagger UI 預覽。
圖片

將 Gin 作爲 Swagger UI 服務器

上面我們通過 Swag 生成了 Swagger 文檔,並手動將生成的 swagger.yaml 複製到 Swagger Editor 編輯器進行 Swagger UI 預覽。不過這麼做顯然有點麻煩,好在 swag 作者也考慮到了這一點,所以他又提供了另外兩個項目 gin-swagger 和 files,能夠直接將 gin 作爲 Swagger UI 服務器,這樣就不用每次都將 swagger.yaml 手動複製到 Swagger Editor 編輯器才能實現 Swagger UI 預覽。

使用步驟如下:

  1. 下載 gin-swagger、files
$ go get -u github.com/swaggo/gin-swagger
$ go get -u github.com/swaggo/files
  1. 在代碼中導入 gin-swagger、files
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files
  1. 註冊 Swagger 文檔路由地址
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

完整代碼如下:

package mainimport (  "net/http"  "github.com/gin-gonic/gin"  swaggerFiles "github.com/swaggo/files"  ginSwagger "github.com/swaggo/gin-swagger"  "gin-swag/docs" // 當前包名爲 gin-swag)// @title           Swagger Example API// @version         1.0// @schemes         http// @host            localhost:8080// @BasePath        /api/v1// @tag.name        example// @tag.description 示例接口// Helloworld godoc//// @Summary     該操作的簡短摘要// @Description 操作行爲的詳細說明// @Tags        example// @Accept      json// @Produce     json// @Success     200 {string} string "Hello World!"// @Router      /example/helloworld [get]func Helloworld(g *gin.Context) {  g.JSON(http.StatusOK, "Hello World!")}func main() {  // 會覆蓋上面註釋部分 title 屬性的設置  docs.SwaggerInfo.Title = "Swag Example API"  r := gin.Default()  v1 := r.Group("/api/v1")  {    eg := v1.Group("/example")    {      eg.GET("/helloworld", Helloworld)    }  }  // Swagger 文檔接口地址  r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))  if err := r.Run(":8080"); err != nil {    panic(err)  }}
  1. 執行 go run main.go 啓動服務,訪問 http://localhost:8080/swagger/index.html 即可查看 Swagger UI 交互式文檔界面。圖片

這個本地的 Swagger UI 服務器同樣支持交互式操作。

與前端對接時,我們只需要將接口文檔地址給到前端,前端就可以根據這個 Swagger UI 界面進行接口查閱和調試了,非常方便。

讓 Swag 支持多版本 API 文檔

實際工作中,我們的項目會比這個只有一個接口的 demo 複雜得多,同時 API 也可能會支持多版本,比如 /api/v1、/api/v2,針對這些場景,我實現了一個使用示例 https://github.com/jianghushinian/swag-example 供你參考。

爲了更加方便地使用 swag 命令,在 swag-example 項目中,我把 swag fmt 和 swag init 命令都寫在 Makefile 文件中,最重要的就是以下兩條命令:

swag init -g internal/api/controller/v1/docs.go --exclude internal/api/controller/v2 --instanceName v1
swag init -g internal/api/controller/v2/docs.go --exclude internal/api/controller/v1 --instanceName v2

這兩條命令分別生成 v1、v2 兩個版本的 API 文檔,這樣可以將不同版本的接口分開展示,更加清晰。

其中 -g 參數指明 API 通用註釋信息所在的 Go 源文件路徑,大型項目中爲了保持代碼架構整潔,這些註釋應該獨立於一個文件,而不是直接寫在 main.go 中。

--exclude 參數指明生成 Swagger 文檔時,需要排除的目錄。可以發現,在生成 v1 版本接口文檔時,我排除了 v2 接口目錄,在生成 v2 版本接口文檔時,排除了 v1 接口目錄,這樣就實現了多版本接口分離。

關於這個項目的更多細節就留給你自己去探索了,相信你閱讀完代碼後會有所收穫。

Swag 使用建議

在前文介紹的 swag 使用流程中,不知道你有沒有注意到,我們是先編寫的代碼,然後再生成的 Swagger 文檔,最後將這份文檔交給前端使用。

這顯然違背了「文檔先行」的思想,實際工作中,我們更多的時候是先跟前端約定好接口,然後後端提供 Swagger 文檔供前端使用,最後纔是前後端編碼階段。

要想解決這個問題,最直接的解決方案是不使用 swag 工具,而是直接使用 Swagger Editor 這種編輯器手寫 Swagger 文檔,這樣就能實現文檔先行了。

但這又違背了 OpenAPI 給出的「最佳實踐」,推薦自動生成 Swagger 文檔,而非手動編寫。

我自己的解決方案是,依舊選擇使用 swag 工具,不過在編寫代碼時,先寫接口的框架代碼,而不寫具體的業務邏輯,這樣就能夠先通過接口註釋生成 Swagger 文檔,供前端使用,然後再編寫業務代碼。

另外,較爲遺憾的是,目前 swag 生成的文檔是 OpenAPI 2.0 版本,並不能直接生成 OpenAPI 3.0 版本,如果你想使用 OpenAPI 3.0 版本的文檔,一個變通的方法是使用工具將 OpenAPI 2.0 文檔轉換成 OpenAPI 3.0,如前文提到的 Swagger Editor 就支持此操作。

使用ReDoc風格的API文檔

也許相較於 Swagger UI 多年不變的界面風格,你更喜歡 ReDoc 風格的 UI,那麼 go-redoc 是一個比較不錯的選擇。

在 gin 中使用 go-redoc 非常簡單,只需要將如下套路代碼加入到我們的 main.go 文件中即可。

import (
    "github.com/gin-gonic/gin" 
    "github.com/mvrilo/go-redoc" 
    ginRedoc "github.com/mvrilo/go-redoc/gin" 
)
    
...
doc := redoc.Redoc{
    Title:       "Example API", 
    Description: "Example API Description", 
    SpecFile: "./openapi.json", // "./openapi.yaml" 
    SpecPath: "/openapi.json", // "/openapi.yaml" 
    DocsPath: "/docs", 
}
r := gin.New()
r.Use(ginRedoc.New(doc))

執行 go run main.go 啓動服務,訪問 http://localhost:8080/redoc 即可查看 Redoc UI。圖片
不過,相較於 Swagger UI,Redoc UI 有個弊端是不能實現交互式操作,如果僅以此作爲文檔查閱工具,沒有交互式操作的需求,那麼還是比較推薦使用的。

更先進的API工具推薦

除了 OpenAPI.Tools 推薦的開源工具,社區中其實還有很多其他優秀工具值得嘗試使用,比如我這裏要推薦的一款國產工具 Apifox,官方將其定義爲 Apifox = Postman + Swagger + Mock + JMeter,集 API 設計/開發/測試 於一身。
Apifox 可謂一站式圖形化工具,其功能非常強大,就像前文提到的 APIGit 同時具備了編輯器和 Mock 服務器的功能,Apifox 有過之而無不及。圖片

圖形化工具上手難度不大,加上 Apifox 本身由國內開發,非常容易上手,所以本文也就不深入介紹了,你可以觀看官方教程 21 分鐘學會 Apifox 來學習使用。希望本文對你有所幫助~

參考

OpenAPI 官網:https://www.openapis.org/

OpenAPI入門:https://oai.github.io/Documentation/

OpenAPI規範:https://spec.openapis.org/oas/latest.html

OpenAPI規範中文版:https://openapi.apifox.cn/

OpenAPI規範思維導圖版:https://openapi-map.apihandyman.io/

OpenAPI.Tools:https://openapi.tools/

Swagger官網:https://swagger.io/

swag:https://github.com/swaggo/swag

swag-example:https://github.com/jianghushinian/swag-example

go-redoc:https://github.com/mvrilo/go-redoc

Apifox官網:https://www.apifox.com/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章