go restful 生成 swagger 2.0 文檔

什么是swagger

Swagger 允許您描述 API 的結(jié)構(gòu),以便機器能夠讀取它們。Swagger 所有能力中最卓越的是 api 描述自身結(jié)構(gòu)的能力。為什么他這么棒?通過閱讀API的結(jié)構(gòu),我們可以自動構(gòu)建漂亮的交互式 API 文檔。我們還可以用多種語言為您的 API 自動生成服務端庫,并探索其他可能性,比如自動化測試。Swagger 通過請求 API 返回包含整個 API 詳細描述的 YAML 或 JSON 來實現(xiàn)這一點。這個文件本質(zhì)上是您的API的資源列表,它遵循OpenAPI規(guī)范。該規(guī)范要求您包括以下信息:

  • 您的API支持哪些操作?
  • 您的API的參數(shù)是什么?它返回什么?
  • 您的API需要一些授權(quán)嗎?
  • 甚至還有一些有趣的東西,比如術(shù)語、聯(lián)系信息和使用API的許可。

您可以手動為您的 API 編寫一個 Swagger 規(guī)范,或者讓它從源代碼中的注釋自動生成。

一些與swagger 相關(guān)的工具:

  • swagger ui 生成交互式API文檔,讓用戶直接在瀏覽器中嘗試API調(diào)用。
  • swagger CodeGen 用API文檔生成代碼
  • swagger edit 集合了 swagger ui 和 swagger CodeGen 的部分功能

搭建swagger相關(guān)的工具

這里只需要搭建 swagger ui 和 swagger edit 就可以了,搭建swagger edit 是為了從網(wǎng)頁端生成代碼。 搭建swagger ui 是為了可以讀取API文檔,生成交互式API。
以下是使用docker搭建的方式:

搭建 swagger edit

docker run -d -p 8081:8080 swaggerapi/swagger-editor

訪問 http://IP:8081即可看到 swagger editor頁面了。swagger editor 既可以編寫 swagger 2.0 也可編寫 swagger 3.0 。 但是,swagger 3.0 可生成代碼的種類明顯少于2.0,這也是我為什么用2.0 的原因。

搭建 swagger ui

docker run -d -p 8082:8080 swaggerapi/swagger-ui

swagger 2.0 API 文檔的結(jié)構(gòu)

swagger 2.0的文檔結(jié)構(gòu)可以參考https://swagger.io/docs/specification/2-0/basic-structure/,如果你不愿意讀英文的,我簡單翻譯了一下下https://github.com/SongJXin/swagger-2.0-translate/blob/master/%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.md。
下面這個 API 文檔是由之后的 go-restful 生成的。

swagger: '2.0'
info:
  description: Resource for managing Users
  title: UserService
  contact:
    name: john
    url: 'http://johndoe.org'
    email: john@doe.rp
  license:
    name: MIT
    url: 'http://mit.org'
  version: 1.0.0
paths:
  /users:
    get:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: get all users
      operationId: findAllUsers
      responses:
        '200':
          description: OK
          schema:
            type: array
            items:
              $ref: '#/definitions/main.User'
    put:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: create a user
      operationId: createUser
      parameters:
        - name: body
          in: body
          required: true
          schema:
            $ref: '#/definitions/main.User'
      responses:
        '200':
          description: OK
  '/users/{user-id}':
    get:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: get a user
      operationId: findUser
      parameters:
        - type: integer
          default: 1
          description: identifier of the user
          name: user-id
          in: path
          required: true
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/main.User'
        '404':
          description: Not Found
    put:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: update a user
      operationId: updateUser
      parameters:
        - type: string
          description: identifier of the user
          name: user-id
          in: path
          required: true
        - name: body
          in: body
          required: true
          schema:
            $ref: '#/definitions/main.User'
      responses:
        '200':
          description: OK
    delete:
      consumes:
        - application/xml
        - application/json
      produces:
        - application/json
        - application/xml
      tags:
        - usersa
      summary: delete a user
      operationId: removeUser
      parameters:
        - type: string
          description: identifier of the user
          name: user-id
          in: path
          required: true
      responses:
        '200':
          description: OK
definitions:
  main.User:
    required:
      - id
      - name
      - age
    properties:
      age:
        description: age of the user
        type: integer
        format: int32
        default: 21
      id:
        description: identifier of the user
        type: string
      name:
        description: name of the user
        type: string
        default: john
tags:
  - description: Managing users
    name: users

go restful 集成 swagger 2.0

獲取 go restful 和 swagger 相關(guān)的包

go get github.com/emicklei/go-restful
go get github.com/emicklei/go-restful-openapi

代碼先貼為敬(這個代碼是go-restful-openapi中自帶的例子):

package main

import (
    "log"
    "net/http"

    "github.com/emicklei/go-restful"
    restfulspec "github.com/emicklei/go-restful-openapi"
    "github.com/go-openapi/spec"
)

type UserResource struct {
    // normally one would use DAO (data access object)
    users map[string]User
}

func (u UserResource) WebService() *restful.WebService {
    ws := new(restful.WebService)
    ws.
        Path("/users").
        Consumes(restful.MIME_XML, restful.MIME_JSON).
        Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

    tags := []string{"users"}

    ws.Route(ws.GET("/").To(u.findAllUsers).
        // docs
        Doc("get all users").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Writes([]User{}).
        Returns(200, "OK", []User{}))

    ws.Route(ws.GET("/{user-id}").To(u.findUser).
        // docs
        Doc("get a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Writes(User{}). // on the response
        Returns(200, "OK", User{}).
        Returns(404, "Not Found", nil))

    ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
        // docs
        Doc("update a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Reads(User{})) // from the request

    ws.Route(ws.PUT("").To(u.createUser).
        // docs
        Doc("create a user").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Reads(User{})) // from the request

    ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
        // docs
        Doc("delete a user").
        Metadata(restfulspec.KeyOpenAPITags, tags).
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))

    return ws
}

// GET http://localhost:8080/users
//
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
    list := []User{}
    for _, each := range u.users {
        list = append(list, each)
    }
    response.WriteEntity(list)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
    id := request.PathParameter("user-id")
    usr := u.users[id]
    if len(usr.ID) == 0 {
        response.WriteErrorString(http.StatusNotFound, "User could not be found.")
    } else {
        response.WriteEntity(usr)
    }
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
    usr := new(User)
    err := request.ReadEntity(&usr)
    if err == nil {
        u.users[usr.ID] = *usr
        response.WriteEntity(usr)
    } else {
        response.WriteError(http.StatusInternalServerError, err)
    }
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
    usr := User{ID: request.PathParameter("user-id")}
    err := request.ReadEntity(&usr)
    if err == nil {
        u.users[usr.ID] = usr
        response.WriteHeaderAndEntity(http.StatusCreated, usr)
    } else {
        response.WriteError(http.StatusInternalServerError, err)
    }
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
    id := request.PathParameter("user-id")
    delete(u.users, id)
}

func main() {
    u := UserResource{map[string]User{}}
    restful.DefaultContainer.Add(u.WebService())

    config := restfulspec.Config{
        WebServices: restful.RegisteredWebServices(), // you control what services are visible
        APIPath:     "/apidocs.json",
        PostBuildSwaggerObjectHandler: enrichSwaggerObject}
    restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))

    // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
    // You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
    // Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
    //http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))

    // Optionally, you may need to enable CORS for the UI to work.
    cors := restful.CrossOriginResourceSharing{
        AllowedHeaders: []string{"Content-Type", "Accept"},
        AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
        CookiesAllowed: false,
        Container:      restful.DefaultContainer}
    restful.DefaultContainer.Filter(cors.Filter)

    log.Printf("Get the API using http://localhost:8080/apidocs.json")
    log.Printf("Open Swagger UI using http://10.110.27.102:8082/?url=http://localhost:8080/apidocs.json")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func enrichSwaggerObject(swo *spec.Swagger) {
    swo.Info = &spec.Info{
        InfoProps: spec.InfoProps{
            Title:       "UserService",
            Description: "Resource for managing Users",
            Contact: &spec.ContactInfo{
                Name:  "john",
                Email: "john@doe.rp",
                URL:   "http://johndoe.org",
            },
            License: &spec.License{
                Name: "MIT",
                URL:  "http://mit.org",
            },
            Version: "1.0.0",
        },
    }
    swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
        Name:        "users",
        Description: "Managing users"}}}
}

// User is just a sample type
type User struct {
    ID   string `json:"id" description:"identifier of the user"`
    Name string `json:"name" description:"name of the user" default:"john"`
    Age  int    `json:"age" description:"age of the user" default:"21"`
}

api 文檔生成規(guī)則

package mainimport部分就略過了。
UserResource這個類是go-restful的基本結(jié)構(gòu) 關(guān)于go-restful框架的這里就不多說了。

enrichSwaggerObject這個函數(shù),是對應生成了swagger 2.0 API 文檔(下稱API文檔)的info和 tags部分。

User 這個結(jié)構(gòu),即被go-restful使用,作為操作的對象,也是對應了API文檔的definitions部分。在反單引號(`)之間的內(nèi)容規(guī)定了這個結(jié)構(gòu)體與definitions的對應關(guān)系。

WebService函數(shù)中:
ws.path().Consumes().Produces()對應swagger 2.0 中的basePathConsumes、Produces,只不過,在生成API過程中把他們分解到了具體的每一個path
tags就是個字符串數(shù)組,在ws.Route.Metadata中用到,規(guī)定了這個path的標簽,如果enrichSwaggerObject中定義的tags定義了這個tag,將從里面取對應的Description信息,如果沒有定義,就當作一個全新的tag。
ws.Route生成了API文檔的path
ws.Route(ws.GET)對應了一個path的一個operation
ws.Route.To這里是go-restful框架將請求路由到相應的函數(shù)(用詞可能不準確,大概就是這么個意思,這函數(shù)處理這次請求),同時,在生成API文檔的時候,函數(shù)的名稱會做為path.operation.operationId。
ws.Route.Doc對應生成了API文檔的 path.operation.summary
ws.Route.Writes生成的path.operation.responses.default(這里有個很奇怪的現(xiàn)象。我用 chrome 和 firefox 看到的生成的API是不一樣的,chrome 不會顯示這個path.operation.responses.default字段,在swagger ui中可以看到這個字段)
ws.Route.Return這里生成path.operation.responses對應的狀態(tài)碼的返回。第一個參數(shù),對應狀態(tài)碼,第二個參數(shù)對應description,第三個參數(shù)對應了schema
ws.Route.Param生成API文檔的parameter
ws.Route.Reads也是生成API文檔的parameter,區(qū)別是它使用了schema,引用了definitions中的對象

main函數(shù)中:
config設置了生成API文檔的一些配置:

  • WebServices,為哪個WebServices 生成 API文檔
  • APIPath,訪問API文檔的路徑。
  • PostBuildSwaggerObjectHandler,使用的基本信息(infotags

http.Handle是這個程序和swagger ui在同一個服務器上,啟動這個程序的時候同時啟動swagger ui用的,這里用了外部的,就把它注釋掉了,不用它了。
cors 這個過濾器是為了讓swagger ui能夠訪問到這個 API文檔,沒有的話,用swagger ui訪問會報錯。

swagger UI 訪問API文檔。

訪問http://swaggerUI-ip:8082/?url=http://localhost:8080/apidocs.json就可以看到 swagger ui 解析API 文檔后的交互式API文檔了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容