beego API開發以及自動化文檔

原文:http://www.kuqin.com/shuoit/20140704/341003.html

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

 

目錄[-]

 

 

 

 

beego API開發以及自動化文檔

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

自動化文檔開發的初衷

我們需要開發一個API應用,然後需要和手機組的開發人員一起合作,當然我們首先想到的是文檔先行,我們也根據之前的經驗寫了我們需要的API原型文檔,我們還是根據github的文檔格式寫了一些漂亮的文檔,但是我們開始擔心這個文檔如果兩邊不同步怎麼辦?因爲畢竟是原型文檔,變動是必不可少的。手機組有一個同事之前在雅虎工作過,他推薦我看一個swagger的應用,看了swagger的標準和文檔化的要求,感覺太棒了,這個簡直就是神器啊,通過swagger可以方便的查看API的文檔,同時使用API的用戶可以直接通過swagger進行請求和獲取結果。所以我就開始學習swagger的標準,同時開始進行Go源碼的研究,通過Go裏面的AST進行源碼分析,針對comments解析,然後生成swagger標準的json格式,這樣最後就可以和swagger完美結合了。

這樣做的好處有三個:

  1. 註釋標準化

  2. 有了註釋之後,以後API代碼維護相當方便

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

beego API應用入門

請大家更新到最新的bee和beego

goget-ugithub.com/beego/bee
goget-ugithub.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

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

packagemain

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

"github.com/astaxie/beego"
)

funcmain(){
ifbeego.RunMode=="dev"{
beego.DirectoryIndex=true
beego.StaticDir["/swagger"]="swagger"
}
beego.Run()
}

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

namespace路由

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

funcinit(){
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的定義是什麼:

typeinnnerNamespacefunc(*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中怎麼寫路由註解的:

//Operationsaboutobject
typeObjectControllerstruct{
beego.Controller
}

//@Titlecreate
//@Descriptioncreateobject
//@Parambodybodymodels.Objecttrue"Theobjectcontent"
//@Success200{string}models.Object.Id
//@Failure403bodyisempty
//@router/[post]
func(this*ObjectController)Post(){
varobmodels.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裏面頭部有一大段的註釋,這些註釋就是描述整個項目的一些信息:

//@APIVersion1.0.0
//@TitlebeegoTestAPI
//@DescriptionbeegohasaverycooltoolstoautogeneratedocumentsforyourAPI
//@[email protected]
//@TermsOfServiceUrlhttp://beego.me/
//@LicenseApache2.0
//@LicenseUrlhttp://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":"Operationsaboutobjectn"
},
{
"path":"/user",
"description":"OperationsaboutUsersn"
}
],
"info":{
"title":"beegoTestAPI",
"description":"beegohasaverycooltoolstoautogeneratedocumentsforyourAPI",
"contact":"[email protected]",
"termsOfServiceUrl":"http://beego.me/",
"license":"Urlhttp://www.apache.org/licenses/LICENSE-2.0.html"
}
}

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

控制器註釋文檔

針對每一個控制我們可以增加註釋,用來描述該控制器的作用:

//Operationsaboutobject
typeObjectControllerstruct{
beego.Controller
}

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

//@TitleGet
//@Descriptionfindobjectbyobjectid
//@ParamobjectIdpathstringtrue"theobjectidyouwanttoget"
//@Success200{object}models.Object
//@Failure403:objectIdisempty
//@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":"findobjectbyobjectid",
"parameters":[
{
"paramType":"path",
"name":"objectId",
"description":""theobjectidyouwanttoget"",
"dataType":"string",
"type":"",
"format":"",
"allowMultiple":false,
"required":true,
"minimum":0,
"maximum":0
}
],
"responseMessages":[
{
"code":200,
"message":"models.Object",
"responseModel":"Object"
},
{
"code":403,
"message":":objectIdisempty",
"responseModel":""
}
]
}
]
}

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

對象自定義註釋

我們的對象定義如下:

typeObjectstruct{
ObjectIdstring
Scoreint64
PlayerNamestring
}

通過掃描生成的代碼如下:

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

我們發現字段都是optional的,而且沒有任何針對字段的描述,其實我們可以在對象定義裏面增加如下的tag:

typeObjectstruct{
ObjectIdstring`required:"true"description:"objectid"`
Scoreint64`required:"true"description:"players'sscores"`
PlayerNamestring`required:"true"description:"plaersname,usedinsystem"`
}

而且如果你的對象tag裏面如果存在json或者thrift描述,那麼就會使用改描述作爲字段名,即如下的代碼:

typeObjectstruct{
ObjectIdstring`json:"object_id"`
Scoreint64`json:"player_score"`
PlayerNamestring`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或者給我留言吧,我會盡力幫助你解決你遇到的問題

發佈了16 篇原創文章 · 獲贊 5 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章