Skip to content

swag

swaggo/swag 是 Swagger API 2.0 在 go 語言中的一個實現,通過在書寫指定格式的注釋就可以生成swagger.jsonswagger.yaml類型的接口文檔,方便導出和導入。

倉庫:swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

文檔:swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

swag 默認支持的 web 框架如下所示,本文以 gin 為例子,來演示 gin 結合 swagger 快速生成接口文檔的例子。

TIP

如果不熟悉 swagger 語法,可以前往About Swagger Specification | Documentation | Swagger

安裝

首先下載 swagger 命令行工具

go install github.com/swaggo/swag/cmd/swag@latest

然後下載 swagger 源碼依賴

go get github.com/swaggo/swag

TIP

為避免出現問題,兩者版本必須保持一致。

然後下載 swagger 的靜態文件庫,html,css,js 之類的,都被嵌到了 go 代碼中。

go get github.com/swaggo/files@latest

最後下載 swagger 的 gin 適配庫

go get github.com/swaggo/gin-swagger@latest

因為本文是只用 gin 做示例,其他 web 框架的適配器請自行了解,基本都是大同小異。

使用

使用 go mod 創建一個最基本的 go 項目,新建main.go,寫入如下內容。

go
package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

// @title           Swagger Example API
// @version         1.0
// @description     This is a sample server celler server.

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @BasePath  /api/v1
func main() {
  engine := gin.Default()
  engine.GET("/api/v1/ping", Ping)
  engine.Run(":80")
}

// Ping godoc
// @Summary      say hello world
// @Description  return hello world json format content
// @param       name query    string  true  "name"
// @Tags         system
// @Produce      json
// @Router       /ping [get]
func Ping(ctx *gin.Context) {
  ctx.JSON(200, gin.H{
    "message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
  })
}

這是一個很簡單的 gin web 例子,main 函數上的注釋是文檔的基本信息,Ping 函數則是一個普通的接口。接下來執行命令生成文檔,默認是在main.go同級的 docs 目錄下

swag init

修改main.go代碼

go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    // 匿名導入生成的接口文檔包
    _ "golearn/docs"
)

// @title           Swagger Example API
// @version         1.0
// @description     This is a sample server celler server.

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @BasePath  /api/v1
func main() {
    engine := gin.Default()
    // 注冊swagger靜態文件路由
    engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    engine.GET("/api/v1/ping", Ping)
    engine.Run(":80")
}

// Ping godoc
// @Summary      say hello world
// @Description  return hello world json format content
// @param       name query    string  true  "name"
// @Tags         system
// @Produce      json
// @Router       /ping [get]
func Ping(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
       "message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
    })
}

運行程序,訪問127.0.0.1/swagger/index.html,界面如下

如此便運行起了一個基本的接口文檔。接下來除了一些特別要注意的點,基本上和其他語言使用起來沒有什麼太大的差別。

參數

定義參數的格式為

@param name paramtype datatype isRequired comment

一個例子如下

go
@param userId query int true "user unique id"

其中支持的參數類型有

  • query
  • path
  • header
  • body
  • formData

數據類型有

  • string (string)
  • integer (int, uint, uint32, uint64)
  • number (float32)
  • boolean (bool)
  • user defined struct

參數類型也可以是你自己的類型,前提是能夠被 swagger 掃描到。

響應

定義接口響應的基本格式如下

go
// @Success      200  {array}   model.Account
// @Failure      400  {object}  httputil.HTTPError
// @Failure      404  {object}  httputil.HTTPError
// @Failure      500  {object}  httputil.HTTPError

由狀態碼,基本類型,數據類型組成。{array}表示是一個數組,會展現數據類型的數組形式,{object}就會展現數據類型的原有形式。比如一般我們會定義一個統一的響應體

go
type JSONResult struct {
    Code    int          `json:"code" `
    Message string       `json:"message"`
    Data    interface{}  `json:"data"`
}

Data字段的類型是不確定的,在描述響應用例時,可以將其組合,如下

go
// 組合
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"

// 數組
@success 200 {object} jsonresult.JSONResult{data=[]Account} "desc"

模型

給結構體字段加注釋會被被 swagger 掃描為模型字段注釋

go
package model

type Account struct {
  // account id
    ID   int    `json:"id" example:"1"`
    // username
    Name string `json:"name" example:"account name"`
}

其中example標簽的值會被作為示例值在頁面中展示,當然它還支持字段限制

go
type Foo struct {
    Bar string `minLength:"4" maxLength:"16"`
    Baz int `minimum:"10" maximum:"20" default:"15"`
    Qux []string `enums:"foo,bar,baz"`
}

所有的模型在使用時都要確保能被 swagger 掃描到,否則不會起作用。

認證

在認證這塊支持

  • Basic Auth
  • API Key
  • OAuth2 app auth
  • OAuth2 implicit auth
  • OAuth2 password auth
  • OAuth2 access code auth

假如接口認證用的是 JWT,存放在 header 中的Authorization字段中,我們可以如下定義

go
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization

本質上這只是一個 apikey,如果你傳入的是 bearer token 的話,需要自己手動加上 Bearer 前綴。

然後在你需要認證的接口上加上如下注釋

go
// @security Bearer

它的值是你的securityDefinitions定義的名稱。

配置

swag 實際上是將多個不同的 swagger 實例存放在一個 map 中,ginSwagger 作為適配器從實例中讀取doc.json也就是 API 接口的定義文件,swaggerFiles 提供靜態的 HTML 文件用於展示網頁,解析 API 定義並生成界面,整個流程明白以後,就可以進行自定義的操作了。

go
// Name is a unique name be used to register swag instance.
// 默認實例名稱
const Name = "swagger"

var (
  swaggerMu sync.RWMutex
    // 實例表
  swags     map[string]Swagger
)
go
func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc {
    var once sync.Once

    if config.InstanceName == "" {
       config.InstanceName = swag.Name
    }

    if config.Title == "" {
       config.Title = "Swagger UI"
    }

    // create a template with name
    index, _ := template.New("swagger_index.html").Parse(swaggerIndexTpl)

    var matcher = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`)

    return func(ctx *gin.Context) {
       if ctx.Request.Method != http.MethodGet {
          ctx.AbortWithStatus(http.StatusMethodNotAllowed)

          return
       }

       // 路由匹配
       matches := matcher.FindStringSubmatch(ctx.Request.RequestURI)

       if len(matches) != 3 {
          ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))

          return
       }

       path := matches[2]
       once.Do(func() {
          handler.Prefix = matches[1]
       })

       switch filepath.Ext(path) {
       case ".html":
          ctx.Header("Content-Type", "text/html; charset=utf-8")
       case ".css":
          ctx.Header("Content-Type", "text/css; charset=utf-8")
       case ".js":
          ctx.Header("Content-Type", "application/javascript")
       case ".png":
          ctx.Header("Content-Type", "image/png")
       case ".json":
          ctx.Header("Content-Type", "application/json; charset=utf-8")
       }

       switch path {
       // 主頁
       case "index.html":
          _ = index.Execute(ctx.Writer, config.toSwaggerConfig())
       // API描述文件
       case "doc.json":
          doc, err := swag.ReadDoc(config.InstanceName)
          if err != nil {
             ctx.AbortWithStatus(http.StatusInternalServerError)

             return
          }

          ctx.String(http.StatusOK, doc)
       default:
          handler.ServeHTTP(ctx.Writer, ctx.Request)
       }
    }
}

通過生成的 go 代碼來自動完成實例注冊,下方是自動生成的docs.go的部分代碼

go
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
  Version:          "",
  Host:             "",
  BasePath:         "",
  Schemes:          []string{},
  Title:            "",
  Description:      "",
  InfoInstanceName: "swagger",
  SwaggerTemplate:  docTemplate,
  LeftDelim:        "{{",
  RightDelim:       "}}",
}

func init() {
    // 注冊實例
  swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

可以看到在init函數中有一個 Register 函數用來注冊當前實例,如果想要修改實例名稱不建議在該文件進行編輯,因為docs.go文件是自動生成的,只需要在生成代碼時使用--instanceName appapi參數。為了方便,可以使用 go generate 命令嵌入的到 go 文件中,方便自動生成代碼,如下。

go
// swagger declarative api comment

// @title App Internal API Documentation
// @version v1.0.0
// @description Wilson api documentation
// @BasePath /api/v1
//go:generate swag init --generatedTime --instanceName appapi -g api.go -d ./ --output ./swagger

個人並不喜歡將 swagger 的通用信息注釋寫在main.gomain函數上,將這些注釋寫在go generate上方最合適不過。

TIP

如果需要多個實例,務必保持實例名稱唯一,否則會panic

為了定制化一些配置,需要用ginSwagger.CustomWrapHandler,它相比前者多了一個 Config 參數,釋義如下

go
// Config stores ginSwagger configuration variables.
type Config struct {
  // The url pointing to API definition (normally swagger.json or swagger.yaml). Default is `doc.json`.
  URL                      string
    // 接口列表展開狀態
  DocExpansion             string
    // 實例名稱
  InstanceName             string
    // 標題
  Title                    string
    // 展開深度
  DefaultModelsExpandDepth int
    // 顧名思義
  DeepLinking              bool
  PersistAuthorization     bool
  Oauth2DefaultClientID    string
}

使用swaggerFiles.NewHandler()來替代默認的 Handler,在多個實例時尤其要如此。

go
engine.GET(openapi.ApiDoc, ginSwagger.CustomWrapHandler(openapi.Config, swaggerFiles.NewHandler()))

除此之外還可以進行類型重寫等一系列操作,都是比較簡單的,更多內容可以閱讀官方文檔。

注意事項

  1. swag 是根據注釋來生成 openapi 的接口描述文件的,在生成時,指定的目錄必須要包含接口文檔的基本信息,默認是在main.go裡面查找

  2. swag init默認指定的是當前目錄,值為./,可以使用swag init -d指定多個目錄,使用逗號分隔,第一個指定的目錄必須包含接口文檔的基本信息。例如

    swag init -d ./,./api
  3. -g,接口文檔的基本信息的存放文件可以自定義文件名,默認是main.go,在生成文檔時,使用-g參數指定文件名

    swag init -g api.go -d ./,./api

    該命令的意思是在./api.go解析接口文檔的基本信息,同時在././api兩個目錄下查找和解析其他接口的注釋信息並生成對應的文檔。

  4. -o參數可以指定文檔描述文件的輸出路徑,默認是./docs,例:

    swag init -o ./api/docs
  5. --ot可以指定輸出文件類型,默認是(docs.go,swagger.json,swagger.yaml),如果想要使用 go 程序加載 swagger ui,go 文件是必不可少的。

    swag init --ot go,yaml

    其余生成的 json 和 yaml 文件可以方便在其他接口管理軟件上導入數據。

  6. 注釋寫在哪裡都一樣,就算不寫在函數上也一樣能解析,只是寫在函數上可讀性好一些,本質上還是一個以注釋形式和 go 源代碼嵌在一起的 DSL。

  7. swag 還支持很多其他的參數,可以使用swag init -h查看。

Golang學習網由www.golangdev.cn整理維護