beego API開發以及自動化文檔

beego API開發以及自動化文檔

beego1.3版本已經在上個星期發佈了,可是仍是有不少人不瞭解如何來進行開發,也是在一步一步的測試中開發,期間QQ羣裏面不少人都問我如何開發,個人業餘時間實在是排的太滿了,實在是沒辦法一一回復你們,在這裏和你們說聲對不起,這兩天我又不斷的改進,寫了一個應用示例展現如何使用beego開發API已經自動化文檔和測試,這裏就和你們詳細的解說一下。html

自動化文檔開發的初衷

咱們須要開發一個API應用,而後須要和手機組的開發人員一塊兒合做,固然咱們首先想到的是文檔先行,咱們也根據以前的經驗寫了咱們須要的API原型文檔,咱們仍是根據github的文檔格式寫了一些漂亮的文檔,可是咱們開始擔憂這個文檔若是兩邊不一樣步怎麼辦?由於畢竟是原型文檔,變更是必不可少的。手機組有一個同事以前在雅虎工做過,他推薦我看一個swagger的應用,看了swagger的標準和文檔化的要求,感受太棒了,這個簡直就是神器啊,經過swagger能夠方便的查看API的文檔,同時使用API的用戶能夠直接經過swagger進行請求和獲取結果。因此我就開始學習swagger的標準,同時開始進行Go源碼的研究,經過Go裏面的AST進行源碼分析,針對comments解析,而後生成swagger標準的json格式,這樣最後就能夠和swagger完美結合了。git

這樣作的好處有三個:github

  1. 註釋標準化apache

  2. 有了註釋以後,之後API代碼維護至關方便json

  3. 根據註釋自動化生成文檔,方便調用的用戶查看和測試api

beego API應用入門

請你們更新到最新的bee和beego跨域

go get -u github.com/beego/bee
go get -u github.com/astaxie/beego

而後進入到你的GOPATH/src目錄,執行命令bee api bapi,進入目錄cd bapi,執行命令bee run -downdoc=true -docgen=true.請看下面我執行的效果圖:瀏覽器

執行完成以後就打開瀏覽器,輸入URL:http://127.0.0.1:8080/swagger/swagger-1/緩存

記住這裏必需要用127.0.0.1,不能使用localhost,存在CORS問題,Ajax跨域服務器

咱們的效果和應用都出來了,很酷很炫吧,那這後面到底採用了怎麼樣的一些技術呢?讓咱們一步一步來說解這些細節:

項目目錄

咱們首先來了解一下bee api建立的應用的目錄結構:

|-- bapi
|-- conf
|   `-- app.conf
|-- controllers
|   |-- object.go
|   `-- user.go
|-- docs
|   |-- doc.go
|   `-- docs.go
|-- lastupdate.tmp
|-- main.go
|-- models
|   |-- object.go
|   `-- user.go
|-- routers
|   |-- commentsRouter.go
|   `-- router.go
|-- swagger
`-- tests
    `-- default_test.go
  • main.go 是程序的統一入口文件

  • bapi 是生成的二進制文件

  • conf 配置文件目錄,app.conf

  • controllers 控制器目錄,主要是邏輯的處理

  • models 是數據處理層的目錄

  • docs 是自動化生成文檔的目錄

  • lastupdate.tmp 是一個註解路由的緩存文件

  • routers是路由目錄,主要涉及一些路由規則

  • swagger 是一個html靜態資源目錄,是經過bee自動下載的,主要就是展現咱們看到的界面及測試

  • test 目錄是針對應用的測試用例,beego相比其餘revel框架的好處之一就是無需啓動應用就能夠執行test case。

入口文件main

咱們第一步先來看一下入口是怎麼寫的?

package main

import (
    _ "bapi/docs"
    _ "bapi/routers"

    "github.com/astaxie/beego"
)

func main() {
    if beego.RunMode == "dev" {
        beego.DirectoryIndex = true
        beego.StaticDir["/swagger"] = "swagger"
    }
    beego.Run()
}

入口文件就是一個普通的beego應用的標準代碼,只是這裏多了幾行代碼,把swagger加入了static,由於咱們須要把文檔服務器集成到beego的API應用中來。而後增長了docs的初始化引入,和router的效果同樣。接下里咱們先來看看自動化API的路由是怎麼設計的

namespace路由

自動化路由纔有了namespace來進行設計,並且注意兩點,第一目前只支持namespace的路由支持自動化文檔,第二隻支持NSNamespace和NSInclude解析,並且是隻能兩個層級,先看咱們的路由設置:

func init() {
    ns := beego.NewNamespace("/v1",
        beego.NSNamespace("/object",
            beego.NSInclude(
                &controllers.ObjectController{},
            ),
        ),
        beego.NSNamespace("/user",
            beego.NSInclude(
                &controllers.UserController{},
            ),
        ),
    )
    beego.AddNamespace(ns)
}

咱們先來看一下這個代碼,首先是使用beego.NewNamespace建立一個ns的變量,這個變量裏面其實就是存儲了一棵路由樹,咱們能夠把這棵樹加到其餘任意已經存在的樹中去,這也就是namespace的好處,能夠在任意的模塊中設計本身的namespace,而後把這個namespace加到其餘的應用中去,能夠增長任意的前綴等。

這裏咱們分析一下NewNamespace這個函數,這個函數的定義是這樣的NewNamespace(prefix string, params ...innnerNamespace) *Namespace,他的第一個參數就是前綴,第二個參數是innnerNamespace多參數,那麼咱們來看看innnerNamespace的定義是什麼:

type innnerNamespace func(*Namespace)

它是一個函數,也就是隻要是符合參數是*Namespace的函數均可以。那麼在beego裏面定義了以下的方法支持返回這個函數類型:

  • NSCond(cond namespaceCond) innnerNamespace

  • NSBefore(filiterList ...FilterFunc) innnerNamespace

  • NSAfter(filiterList ...FilterFunc) innnerNamespace

  • NSInclude(cList …ControllerInterface) innnerNamespace

  • NSRouter(rootpath string, c ControllerInterface, mappingMethods …string) innnerNamespace

  • NSGet(rootpath string, f FilterFunc) innnerNamespace

  • NSPost(rootpath string, f FilterFunc) innnerNamespace

  • NSDelete(rootpath string, f FilterFunc) innnerNamespace

  • NSPut(rootpath string, f FilterFunc) innnerNamespace

  • NSHead(rootpath string, f FilterFunc) innnerNamespace

  • NSOptions(rootpath string, f FilterFunc) innnerNamespace

  • NSPatch(rootpath string, f FilterFunc) innnerNamespace

  • NSAny(rootpath string, f FilterFunc) innnerNamespace

  • NSHandler(rootpath string, h http.Handler) innnerNamespace

  • NSAutoRouter(c ControllerInterface) innnerNamespace

  • NSAutoPrefix(prefix string, c ControllerInterface) innnerNamespace

  • NSNamespace(prefix string, params …innnerNamespace) innnerNamespace

所以咱們能夠在NewNamespace這個函數的第二個參數列表中使用上面的任意函數做爲參數調用。

咱們看一下路由代碼,這是一個層級嵌套的函數,第一個參數是/v1,即爲/v1開頭的路由樹,第二個參數是beego.NSNamespace,第三個參數也是beego.NSNamespace,也就是路由樹嵌套了路由樹,而咱們的beego.NSNamespace裏面也是和NewNamespace同樣的參數,第一個參數是路由前綴,第二個參數是slice參數。這裏咱們調用了beego.NSInclude來進行註解路由的引入,這個函數是專門爲註解路由設計的,咱們能夠看到這個設計裏面咱們沒有任何的路由信息,只是設置了前綴,那麼這個的路由是在哪裏設置的呢?咱們接下來分析什麼是註解路由。

註解路由

可能有些同窗不瞭解什麼是註解路由,也就是在Controller類上添加一個註釋讓框架給自動添加Route,那麼咱們來看一下ObjectControllerUserController中怎麼寫路由註解的:

// Operations about object
type ObjectController struct {
    beego.Controller
}

// @Title create
// @Description create object
// @Param   body        body    models.Object   true        "The object content"
// @Success 200 {string} models.Object.Id
// @Failure 403 body is empty
// @router / [post]
func (this *ObjectController) Post() {
    var ob models.Object
    json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
    objectid := models.AddOne(ob)
    this.Data["json"] = map[string]string{"ObjectId": objectid}
    this.ServeJson()
}

咱們看到咱們的每個函數上面有大段的註釋,註解路由其實主要關注最後一行// @router / [post],這一行的註釋就是表示這個函數是註冊到路由/,支持方法是post

和咱們日常的時候使用beego.Router("/", &ObjectController{},"post:Post")的效果是如出一轍的,只是這一次框架幫你自動註冊了這樣的路由,框架是如何來自動註冊的呢?在應用啓動的時候,會判斷是否有調用NSInclude,在調用的時候,判斷RunMode是不是dev模式,是的話就會判斷是否以前有分析過,而且分析對象目錄有更新,就使用Go的AST進行源碼分析(固然只分析NSInclude調用的controller),而後生成文件routers/commentsRouter.go,在該文件中會自動註冊咱們須要的路由信息。這樣就完成了整個的註解路由註冊。

註解路由是使用// @router 開頭來申明的,並且必須放在你要註冊的函數的上方,和其餘註釋@Title @Description的順序無關,你能夠放在第一行,也能夠最後一行。有兩個參數,第一個是須要註冊的路由,第二個是支持的方法。

路由能夠支持beego支持的任意規則,例如/object/:key這樣的參數路由,也能夠固定路由/object,也能夠是正則路由/cms_:id([0-9]+).html

支持的HTTP方法必須使用[]中間是支持的方法列表,多個使用,分割,例如[post,get]。可是目前自動化文檔只支持一個方法,也就是你多個的方法的時候沒法作到RESTFul到同一個函數,也不鼓勵你這樣設計的API。若是你API設計的時候支持了多個方法,那麼文檔生成的時候默認是取第一個做爲支持的方法。

上面咱們看到咱們的方法上面有不少註釋,那麼接下來就進入咱們今天的重點:自動化文檔

自動化文檔

所謂的自動化文檔,說白了就是根據咱們的註釋自動的生成咱們能夠看得懂的漂亮文檔。咱們上面也說了寫註釋不只僅是方便咱們的代碼維護,邏輯闡述,同時若是可以自動生成文檔,那麼對於使用API的用戶來講也是很大的幫助。那麼如何進行自動化文檔生成呢?

我當初看了swagger的展現效果以後,首先研究了他的spec,發現是一些json數據,只要咱們的API可以生成swagger認識的json就能夠了,所以個人思路就來了,根據註釋生成swagger的JSON標準數據輸出。swagger提供了一個例子代碼:petstore 我就是根據這個例子的格式一步一步實現瞭如今的自動化文檔。

首先第一步就是API的描述:

API文檔

咱們看到在router.go裏面頭部有一大段的註釋,這些註釋就是描述整個項目的一些信息:

// @APIVersion 1.0.0
// @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API
// @Contact astaxie@gmail.com
// @TermsOfServiceUrl http://beego.me/
// @License Apache 2.0
// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html

這裏面主要是幾個標誌:

  • @APIVersion

  • @Title

  • @Description

  • @Contact

  • @TermsOfServiceUrl

  • @License

  • @LicenseUrl

這裏每個都不是必須的,你能夠寫也能夠不寫,後面就是一個字符串,你可使用任意喜歡的字符進行描述。咱們來看一下生成的:http://127.0.0.1:8080/docs

{
  "apiVersion": "1.0.0",
  "swaggerVersion": "1.2",
  "apis": [
    {
      "path": "/object",
      "description": "Operations about object\n"
    },
    {
      "path": "/user",
      "description": "Operations about Users\n"
    }
  ],
  "info": {
    "title": "beego Test API",
    "description": "beego has a very cool tools to autogenerate documents for your API",
    "contact": "astaxie@gmail.com",
    "termsOfServiceUrl": "http://beego.me/",
    "license": "Url http://www.apache.org/licenses/LICENSE-2.0.html"
  }
}

這是首次請求的一些信息,那麼apis是怎麼來的呢?這個就是根據你的namespace進行源碼AST分析獲取的,因此目前只支持兩層的namespace嵌套,並且必須是兩層,第一層是baseurl,第二層就是嵌套的namespace的prefix。也就是上面的path信息,那麼裏面的description那裏獲取的呢?請看控制器的註釋,

控制器註釋文檔

針對每個控制咱們能夠增長註釋,用來描述該控制器的做用:

// Operations about object
type ObjectController struct {
    beego.Controller
}

這個註釋就是用來表示咱們的每個控制器API的做用,而控制器的函數裏面的註釋就是用來表示調用的路由、參數、做用以及返回的信息。

// @Title Get
// @Description find object by objectid
// @Param   objectId        path    string  true        "the objectid you want to get"
// @Success 200 {object} models.Object
// @Failure 403 :objectId is empty
// @router /:objectId [get]
func (this *ObjectController) Get() {
}

從上面的註釋咱們能夠把咱們的註釋分爲如下類別:

  • @Title

    接口的標題,用來標示惟一性,惟一,可選

    格式:以後跟一個描述字符串

  • @Description

    接口的做用,用來描述接口的用途,惟一,可選

    格式:以後跟一個描述字符串

  • @Param

    請求的參數,用來描述接受的參數,多個,可選

    格式:變量名 傳輸類型 類型 是否必須 描述

    傳輸類型:

    類型:

    變量名和描述是一個字符串

    是否必須:true 或者false

    • string

    • int

    • int64

    • 對象,這個地方你們寫的時候須要注意,須要是相對於當前項目的路徑.對象,例如models.Object表示models目錄下的Object對象,這樣bee在生成文檔的時候會去掃描改對象並顯示給用戶改對象。

    • query 表示帶在url串裏面?aa=bb&cc=dd

    • form 表示使用表單遞交數據

    • path 表示URL串中得字符,例如/user/{uid} 那麼uid就是一個path類型的參數

    • body 表示使用raw body進行數據的傳輸

    • header 表示經過header進行數據的傳輸

    • @Success

      成功返回的code和對象或者信息

      格式:code 對象類型 信息或者對象路徑

      code:表示HTTP的標準status code,200 201等

      對象類型:{object}表示對象,其餘默認都認爲是字符類型,會顯示第三個參數給用戶,若是是{object}類型,那麼就會去掃描改對象,並顯示給用戶

      對象路徑和上面Param中得對象類型同樣,使用路徑.對象的方式來描述

    • @Failure

      錯誤返回的信息,

      格式: code 信息

      code:同上Success

      錯誤信息:字符串描述信息

    • @router

      上面已經描述過支持兩個參數,第一個是路由,第二個表示支持的HTTP方法

    那麼咱們經過上面的註釋會生成怎麼樣的JSON信息呢?

    {
      "path": "/object/{objectId}",
      "description": "",
      "operations": [
        {
          "httpMethod": "GET",
          "nickname": "Get",
          "type": "",
          "summary": "find object by objectid",
          "parameters": [
            {
              "paramType": "path",
              "name": "objectId",
              "description": "\"the objectid you want to get\"",
              "dataType": "string",
              "type": "",
              "format": "",
              "allowMultiple": false,
              "required": true,
              "minimum": 0,
              "maximum": 0
            }
          ],
          "responseMessages": [
            {
              "code": 200,
              "message": "models.Object",
              "responseModel": "Object"
            },
            {
              "code": 403,
              "message": ":objectId is empty",
              "responseModel": ""
            }
          ]
        }
      ]
    }

    上面闡述的這些描述都是可使用一個或者多個 '\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP)進行分割

    對象自定義註釋

    咱們的對象定義以下:

    type Object struct {
        ObjectId   string
        Score      int64
        PlayerName string
    }

    經過掃描生成的代碼以下:

    Object {
    ObjectId (string, optional): ,
    PlayerName (string, optional): ,
    Score (int64, optional):
    }

    咱們發現字段都是optional的,並且沒有任何針對字段的描述,其實咱們能夠在對象定義裏面增長以下的tag:

    type Object struct {
        ObjectId   string   `required:"true" description:"object id"`
        Score      int64        `required:"true" description:"players's scores"`
        PlayerName string   `required:"true" description:"plaers name, used in system"`
    }

    並且若是你的對象tag裏面若是存在json或者thrift描述,那麼就會使用改描述做爲字段名,即以下的代碼:

    type Object struct {
        ObjectId   string   `json:"object_id"`
        Score      int64        `json:"player_score"`
        PlayerName string   `json:"player_name"`
    }

    就會輸出以下的文檔信息:

    Object {
    object_id (string, optional): ,
    player_score (string, optional): ,
    player_name (int64, optional):
    }

    常見錯誤及問題

    1. Q:bee沒有上面執行的命令?

      A:請更新bee到最新版本,目前bee的版本是1.1.2,beego的版本是1.3.1

    2. Q:bee更新的時候出錯了?

      A:第一多是GFW的問題,第二多是你修改過了源碼,刪除從新下載,第三可能你升級了Go版本,你須要刪除GOPATH/pkg下的全部文件

    3. Q:下載swagger很慢?

      A:想辦法讓他變快,由於我如今放在了github上面

    4. Q:文檔生成了,可是我沒辦法測試請求?

      A:你看看你訪問的地址是否是和請求的URL是同一個地址,由於swagger是使用Ajax請求數據的,因此跨域的問題,解決CORS的辦法就是保持域一致,包括URL和端口。

    5. Q:運行的時候發生了未知的錯誤?

      A:那就來提issue或者給我留言吧,我會盡力幫助你解決你遇到的問題

    相關文章
    相關標籤/搜索