使用 go-swagger 爲 golang API 自動生成 swagger 文檔

【導讀】swagger 定義接口、自動生成 go 接口代碼的操作如何落地?本文做了詳細介紹。

什麼是 swagger?

Swagger 是一個簡單但功能強大的 API 表達工具。它具有地球上最大的 API 工具生態系統,數以千計的開發人員,使用幾乎所有的現代編程語言,都在支持和使用 Swagger。使用 Swagger 生成 API,我們可以得到交互式文檔,自動生成代碼的 SDK 以及 API 的發現特性等。

swagger 文檔長啥樣?

一個最簡單的 swagger 文檔示例:

swagger: "2.0"

info:
  version: 1.0.0
  title: Simple API
  description: A simple API to learn how to write OpenAPI Specification

schemes:
  - https
host: simple.api
basePath: /openapi101

paths: {}

本文背景介紹

寫作本文的原因是因爲公司要求 api 文檔都使用 swagger 格式,項目是用 golang 編寫的,作爲一個懶癌程序員,怎麼能夠忍受去編寫這麼複雜的 swagger 文檔呢?有沒有一鍵生成的工具呢?google 一下,還真有, 那就是 go-swagger 項目。go-swagger 衆多特色功能之一就是 Generate a spec from source, 即通過源碼生成文檔,很符合我的需求。

下面就簡單介紹下如何爲項目加上 swagger 註釋,然後一鍵生成 API 文檔

開始之前需要安裝兩個工具:

安裝 swagger-editor, 我這裏使用 docker 運行,其他安裝方式,請查看官方文檔:

docker pull swaggerapi/swagger-editor
docker run --rm -p 80:8080 swaggerapi/swagger-editor

安裝 go-swagger, 我這邊使用 brew 安裝,其他安裝方式,請查看官方文檔

brew tap go-swagger/go-swagger
brew install go-swagger

好了,現在終於開始正題:start coding!!!

開始編寫註釋

  1. 假設有一個 user.server,提供一些 REST API,用於對用戶數據的增刪改查。

比如這裏有一個getOneUser接口,是查詢用戶信息的:

package service

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "user.server/models"
    "github.com/Sirupsen/logrus"
)

type GetUserParam struct {
    Id int `json:"id"`
}

func GetOneUser(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()
    decoder := json.NewDecoder(r.Body)
    var param GetUserParam
    err := decoder.Decode(¶m)
    if err != nil {
        WriteResponse(w, ErrorResponseCode, "request param is invalid, please check!", nil)
        return
    }

    // get user from db
    user, err := models.GetOne(strconv.Itoa(param.Id))
    if err != nil {
        logrus.Warn(err)
        WriteResponse(w, ErrorResponseCode, "failed", nil)
        return
    }
    WriteResponse(w, SuccessResponseCode, "success", user)
}

根據 swagger 文檔規範,一個 swagger 文檔首先要有 swagger 的版本和 info 信息。利用 go-swagger 只需要在聲明 package 之前加上如下注釋即可:

// Package classification User API.
//
// The purpose of this service is to provide an application
// that is using plain go code to define an API
//
//      Host: localhost
//      Version: 0.0.1
//
// swagger:meta
package service

然後在項目根目錄下使用swagger generate spec \-o ./swagger.json命令生成swagger.json文件:

此命令會找到 main.go 入口文件,然後遍歷所有源碼文件,解析然後生成 swagger.json 文件

{
  "swagger""2.0",
  "info"{
    "description""The purpose of this service is to provide an application\nthat is using plain go code to define an API",
    "title""User API.",
    "version""0.0.1"
  },
  "host""localhost",
  "paths"{}
}
  1. 基本信息有了,然後就要有路由,請求,響應等,下面針對 getOneUser 接口編寫 swagger 註釋:
// swagger:parameters getSingleUser
type GetUserParam struct {
    // an id of user info
    //
    // Required: true
    // in: path
    Id int `json:"id"`
}

func GetOneUser(w http.ResponseWriter, r *http.Request) {
    // swagger:route GET /users/{id} users getSingleUser
    //
    // get a user by userID
    //
    // This will show a user info
    //
    //     Responses:
    //       200: UserResponse
    decoder := json.NewDecoder(r.Body)
    var param GetUserParam
    err := decoder.Decode(¶m)
    if err != nil {
        WriteResponse(w, ErrorResponseCode, "request param is invalid, please check!", nil)
        return
    }

    // get user from db
    user, err := models.GetOne(strconv.Itoa(param.Id))
    if err != nil {
        logrus.Warn(err)
        WriteResponse(w, ErrorResponseCode, "failed", nil)
        return
    }
    WriteResponse(w, SuccessResponseCode, "success", user)
}

可以看到在GetUserParam結構體上面加了一行swagger:parameters getSingleUser的註釋信息,這是聲明接口的入參註釋,結構體內部的幾行註釋指明瞭 id 這個參數必填,並且查詢參數 id 是在 url path 中。詳細用法,參考: swagger:params

GetOneUser函數中:

然後再聲明響應:

// User Info
//
// swagger:response UserResponse
type UserWapper struct {
    // in: body
    Body ResponseMessage
}

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

使用swagger:response語法聲明返回值,其上兩行是返回值的描述(我也不清楚,爲啥描述信息要寫在上面,歡迎解惑), 詳細用法,參考;swagger:response

然後瀏覽器訪問localhost, 查看 swagger-editor 界面, 點擊工具欄中的File->Impoprt File上傳剛纔生成的 swagger.json文件,就可以看到界面:

這樣一個簡單的 api 文檔就生成了

  1. 怎麼樣?是不是很簡單?可是又感覺那裏不對,嗯,註釋都寫在代碼裏了,很不美觀,而且不易維護。想一下 go-swagger 的原理是掃描目錄下的所有 go 文件,解析註釋信息。那麼是不是可以把 api 註釋都集中寫在單個文件內,統一管理,免得分散在各個源碼文件內。

新建一個doc.go文件,這裏還有一個接口是UpdateUser, 那麼我們在 doc.go 文件中聲明此接口的 api 註釋。先看一下UpdateUser接口的代碼:

func UpdateUser(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()
    // decode body data into user struct
    decoder := json.NewDecoder(r.Body)
    user := models.User{}
    err := decoder.Decode(&user)
    if err != nil {
        WriteResponse(w, ErrorResponseCode, "user data is invalid, please check!", nil)
        return
    }

    // check if user exists
    data, err := models.GetUserById(user.Id)
    if err != nil {
        logrus.Warn(err)
        WriteResponse(w, ErrorResponseCode, "query user failed", nil)
        return
    }
    if data.Id == 0 {
        WriteResponse(w, ErrorResponseCode, "user not exists, no need to update", nil)
        return
    }

    // update
    _, err = models.Update(user)
    if err != nil {
        WriteResponse(w, ErrorResponseCode, "update user data failed, please try again!", nil)
        return
    }
    WriteResponse(w, SuccessResponseCode, "update user data success!", nil)
}

然後再 doc.go 文件中編寫如下聲明:

package service

import "user.server/models"

// swagger:parameters UpdateUserResponseWrapper
type UpdateUserRequest struct {
    // in: body
    Body models.User
}

// Update User Info
//
// swagger:response UpdateUserResponseWrapper
type UpdateUserResponseWrapper struct {
    // in: body
    Body ResponseMessage
}

// swagger:route POST /users users UpdateUserResponseWrapper
//
// Update User
//
// This will update user info
//
//     Responses:
//       200: UpdateUserResponseWrapper

這樣就把 api 聲明註釋給抽離出來了,然後使用命令swagger generate spec \-o ./swagger.json生成 json 文件, 就可以看到這樣的結果:

很簡單吧,參照文檔編寫幾行註釋,然後一個命令生成 API 文檔。懶癌程序員福音~

轉自:
juejin.cn/post/6844903609390333965

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DfZf8FEx-jgqb3yYRWNkWw