beego API開(kāi)發(fā)以及自動(dòng)化文檔
beego API開(kāi)發(fā)以及自動(dòng)化文檔
beego1.3版本已經(jīng)在上個(gè)星期發(fā)布了,但是還是有很多人不了解如何來(lái)進(jìn)行開(kāi)發(fā),也是在一步一步的測(cè)試中開(kāi)發(fā),期間QQ群里面很多人都問(wèn)我如何開(kāi)發(fā),我的業(yè)余時(shí)間實(shí)在是排的太滿了,實(shí)在是沒(méi)辦法一一回復(fù)大家,在這里和大家說(shuō)聲對(duì)不起,這兩天我又不斷的改進(jìn),寫了一個(gè)應(yīng)用示例展示如何使用beego開(kāi)發(fā)API已經(jīng)自動(dòng)化文檔和測(cè)試,這里就和大家詳細(xì)的解說(shuō)一下。
自動(dòng)化文檔開(kāi)發(fā)的初衷
我們需要開(kāi)發(fā)一個(gè)API應(yīng)用,然后需要和手機(jī)組的開(kāi)發(fā)人員一起合作,當(dāng)然我們首先想到的是文檔先行,我們也根據(jù)之前的經(jīng)驗(yàn)寫了我們需要的API原型文檔,我們還是根據(jù)github的文檔格式寫了一些漂亮的文檔,但是我們開(kāi)始擔(dān)心這個(gè)文檔如果兩邊不同步怎么辦?因?yàn)楫吘故窃臀臋n,變動(dòng)是必不可少的。手機(jī)組有一個(gè)同事之前在雅虎工作過(guò),他推薦我看一個(gè)swagger的應(yīng)用,看了swagger的標(biāo)準(zhǔn)和文檔化的要求,感覺(jué)太棒了,這個(gè)簡(jiǎn)直就是神器啊,通過(guò)swagger可以方便的查看API的文檔,同時(shí)使用API的用戶可以直接通過(guò)swagger進(jìn)行請(qǐng)求和獲取結(jié)果。所以我就開(kāi)始學(xué)習(xí)swagger的標(biāo)準(zhǔn),同時(shí)開(kāi)始進(jìn)行Go源碼的研究,通過(guò)Go里面的AST進(jìn)行源碼分析,針對(duì)comments解析,然后生成swagger標(biāo)準(zhǔn)的json格式,這樣最后就可以和swagger完美結(jié)合了。
這樣做的好處有三個(gè):
-
注釋標(biāo)準(zhǔn)化
-
有了注釋之后,以后API代碼維護(hù)相當(dāng)方便
-
根據(jù)注釋自動(dòng)化生成文檔,方便調(diào)用的用戶查看和測(cè)試
beego API應(yīng)用入門
請(qǐng)大家更新到最新的bee和beego
go get -u github.com/beego/bee go get -u github.com/astaxie/beego
然后進(jìn)入到你的GOPATH/src
目錄,執(zhí)行命令bee api bapi
,進(jìn)入目錄cd bapi
,執(zhí)行命令bee run -downdoc=true -docgen=true
.請(qǐng)看下面我執(zhí)行的效果圖:
執(zhí)行完成之后就打開(kāi)瀏覽器,輸入U(xiǎn)RL:http://127.0.0.1:8080/swagger/swagger-1/
記住這里必須要用127.0.0.1,不能使用localhost,存在CORS問(wèn)題,Ajax跨域 |
我們的效果和應(yīng)用都出來(lái)了,很酷很炫吧,那這后面到底采用了怎么樣的一些技術(shù)呢?讓我們一步一步來(lái)講解這些細(xì)節(jié):
項(xiàng)目目錄
我們首先來(lái)了解一下bee api
創(chuàng)建的應(yīng)用的目錄結(jié)構(gòu):
|-- 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 是程序的統(tǒng)一入口文件
-
bapi 是生成的二進(jìn)制文件
-
conf 配置文件目錄,app.conf
-
controllers 控制器目錄,主要是邏輯的處理
-
models 是數(shù)據(jù)處理層的目錄
-
docs 是自動(dòng)化生成文檔的目錄
-
lastupdate.tmp 是一個(gè)注解路由的緩存文件
-
routers是路由目錄,主要涉及一些路由規(guī)則
-
swagger 是一個(gè)html靜態(tài)資源目錄,是通過(guò)bee自動(dòng)下載的,主要就是展示我們看到的界面及測(cè)試
-
test 目錄是針對(duì)應(yīng)用的測(cè)試用例,beego相比其他revel框架的好處之一就是無(wú)需啟動(dòng)應(yīng)用就可以執(zhí)行test case。
#p#
入口文件main
我們第一步先來(lái)看一下入口是怎么寫的?
- 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()
- }
入口文件就是一個(gè)普通的beego應(yīng)用的標(biāo)準(zhǔn)代碼,只是這里多了幾行代碼,把swagger加入了static,因?yàn)槲覀冃枰盐臋n服務(wù)器集成到beego的API應(yīng)用中來(lái)。然后增加了docs的初始化引入,和router的效果一樣。接下里我們先來(lái)看看自動(dòng)化API的路由是怎么設(shè)計(jì)的
namespace路由
自動(dòng)化路由才有了namespace來(lái)進(jìn)行設(shè)計(jì),而且注意兩點(diǎn),第一目前只支持namespace的路由支持自動(dòng)化文檔,第二只支持NSNamespace和NSInclude解析,而且是只能兩個(gè)層級(jí),先看我們的路由設(shè)置:
- func init() {
- ns := beego.NewNamespace("/v1",
- beego.NSNamespace("/object",
- beego.NSInclude(
- &controllers.ObjectController{},
- ),
- ),
- beego.NSNamespace("/user",
- beego.NSInclude(
- &controllers.UserController{},
- ),
- ),
- )
- beego.AddNamespace(ns)
- }
我們先來(lái)看一下這個(gè)代碼,首先是使用beego.NewNamespace創(chuàng)建一個(gè)ns的變量,這個(gè)變量里面其實(shí)就是存儲(chǔ)了一棵路由樹(shù),我們可以把這棵樹(shù)加到其他任意已經(jīng)存在的樹(shù)中去,這也就是namespace的好處,可以在任意的模塊中設(shè)計(jì)自己的namespace,然后把這個(gè)namespace加到其他的應(yīng)用中去,可以增加任意的前綴等。
這里我們分析一下NewNamespace這個(gè)函數(shù),這個(gè)函數(shù)的定義是這樣的NewNamespace(prefix string, params ...innnerNamespace) *Namespace
,他的第一個(gè)參數(shù)就是前綴,第二個(gè)參數(shù)是innnerNamespace
多參數(shù),那么我們來(lái)看看innnerNamespace
的定義是什么:
- type innnerNamespace func(*Namespace)
它是一個(gè)函數(shù),也就是只要是符合參數(shù)是*Namespace
的函數(shù)都可以。那么在beego里面定義了如下的方法支持返回這個(gè)函數(shù)類型:
-
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
這個(gè)函數(shù)的第二個(gè)參數(shù)列表中使用上面的任意函數(shù)作為參數(shù)調(diào)用。
我們看一下路由代碼,這是一個(gè)層級(jí)嵌套的函數(shù),第一個(gè)參數(shù)是/v1
,即為/v1
開(kāi)頭的路由樹(shù),第二個(gè)參數(shù)是beego.NSNamespace
,第三個(gè)參數(shù)也是beego.NSNamespace
,也就是路由樹(shù)嵌套了路由樹(shù),而我們的beego.NSNamespace
里面也是和NewNamespace
一樣的參數(shù),第一個(gè)參數(shù)是路由前綴,第二個(gè)參數(shù)是slice參數(shù)。這里我們調(diào)用了beego.NSInclude
來(lái)進(jìn)行注解路由的引入,這個(gè)函數(shù)是專門為注解路由設(shè)計(jì)的,我們可以看到這個(gè)設(shè)計(jì)里面我們沒(méi)有任何的路由信息,只是設(shè)置了前綴,那么這個(gè)的路由是在哪里設(shè)置的呢?我們接下來(lái)分析什么是注解路由。
注解路由
可能有些同學(xué)不了解什么是注解路由,也就是在Controller類上添加一個(gè)注釋讓框架給自動(dòng)添加Route,那么我們來(lái)看一下ObjectController
和UserController
中怎么寫路由注解的:
- // 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()
- }
我們看到我們的每一個(gè)函數(shù)上面有大段的注釋,注解路由其實(shí)主要關(guān)注最后一行// @router / [post]
,這一行的注釋就是表示這個(gè)函數(shù)是注冊(cè)到路由/
,支持方法是post
。
和我們平常的時(shí)候使用beego.Router("/", &ObjectController{},"post:Post")
的效果是一模一樣的,只是這一次框架幫你自動(dòng)注冊(cè)了這樣的路由,框架是如何來(lái)自動(dòng)注冊(cè)的呢?在應(yīng)用啟動(dòng)的時(shí)候,會(huì)判斷是否有調(diào)用NSInclude
,在調(diào)用的時(shí)候,判斷RunMode是否是dev
模式,是的話就會(huì)判斷是否之前有分析過(guò),并且分析對(duì)象目錄有更新,就使用Go的AST進(jìn)行源碼分析(當(dāng)然只分析NSInclude
調(diào)用的controller
),然后生成文件routers/commentsRouter.go
,在該文件中會(huì)自動(dòng)注冊(cè)我們需要的路由信息。這樣就完成了整個(gè)的注解路由注冊(cè)。
注解路由是使用// @router
開(kāi)頭來(lái)申明的,而且必須放在你要注冊(cè)的函數(shù)的上方,和其他注釋@Title @Description
的順序無(wú)關(guān),你可以放在第一行,也可以最后一行。有兩個(gè)參數(shù),第一個(gè)是需要注冊(cè)的路由,第二個(gè)是支持的方法。
路由可以支持beego支持的任意規(guī)則,例如/object/:key
這樣的參數(shù)路由,也可以固定路由/object
,也可以是正則路由/cms_:id([0-9]+).html
支持的HTTP方法必須使用[]
中間是支持的方法列表,多個(gè)使用,
分割,例如[post,get]
。但是目前自動(dòng)化文檔只支持一個(gè)方法,也就是你多個(gè)的方法的時(shí)候無(wú)法做到RESTFul到同一個(gè)函數(shù),也不鼓勵(lì)你這樣設(shè)計(jì)的API。如果你API設(shè)計(jì)的時(shí)候支持了多個(gè)方法,那么文檔生成的時(shí)候默認(rèn)是取第一個(gè)作為支持的方法。
上面我們看到我們的方法上面有很多注釋,那么接下來(lái)就進(jìn)入我們今天的重點(diǎn):自動(dòng)化文檔
自動(dòng)化文檔
所謂的自動(dòng)化文檔,說(shuō)白了就是根據(jù)我們的注釋自動(dòng)的生成我們可以看得懂的漂亮文檔。我們上面也說(shuō)了寫注釋不僅僅是方便我們的代碼維護(hù),邏輯闡述,同時(shí)如果能夠自動(dòng)生成文檔,那么對(duì)于使用API的用戶來(lái)說(shuō)也是很大的幫助。那么如何進(jìn)行自動(dòng)化文檔生成呢?
我當(dāng)初看了swagger的展示效果之后,首先研究了他的spec,發(fā)現(xiàn)是一些json數(shù)據(jù),只要我們的API能夠生成swagger認(rèn)識(shí)的json就可以了,因此我的思路就來(lái)了,根據(jù)注釋生成swagger的JSON標(biāo)準(zhǔn)數(shù)據(jù)輸出。swagger提供了一個(gè)例子代碼:petstore 我就是根據(jù)這個(gè)例子的格式一步一步實(shí)現(xiàn)了現(xiàn)在的自動(dòng)化文檔。
#p#
首先第一步就是API的描述:
API文檔
我們看到在router.go
里面頭部有一大段的注釋,這些注釋就是描述整個(gè)項(xiàng)目的一些信息:
- // @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
這里面主要是幾個(gè)標(biāo)志:
-
@APIVersion
-
@Title
-
@Description
-
@Contact
-
@TermsOfServiceUrl
-
@LicenseUrl
這里每一個(gè)都不是必須的,你可以寫也可以不寫,后面就是一個(gè)字符串,你可以使用任意喜歡的字符進(jìn)行描述。我們來(lái)看一下生成的: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"
- }
- }
這是首次請(qǐng)求的一些信息,那么apis是怎么來(lái)的呢?這個(gè)就是根據(jù)你的namespace進(jìn)行源碼AST分析獲取的,所以目前只支持兩層的namespace嵌套,而且必須是兩層,第一層是baseurl,第二層就是嵌套的namespace的prefix。也就是上面的path信息,那么里面的description那里獲取的呢?請(qǐng)看控制器的注釋,
控制器注釋文檔
針對(duì)每一個(gè)控制我們可以增加注釋,用來(lái)描述該控制器的作用:
- // Operations about object
- type ObjectController struct {
- beego.Controller
- }
這個(gè)注釋就是用來(lái)表示我們的每一個(gè)控制器API的作用,而控制器的函數(shù)里面的注釋就是用來(lái)表示調(diào)用的路由、參數(shù)、作用以及返回的信息。
- // @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
接口的標(biāo)題,用來(lái)標(biāo)示唯一性,唯一,可選
格式:之后跟一個(gè)描述字符串
-
@Description
接口的作用,用來(lái)描述接口的用途,唯一,可選
格式:之后跟一個(gè)描述字符串
-
請(qǐng)求的參數(shù),用來(lái)描述接受的參數(shù),多個(gè),可選
格式:變量名 傳輸類型 類型 是否必須 描述
傳輸類型:
類型:
變量名和描述是一個(gè)字符串
是否必須:true 或者false
-
string
-
int
-
int64
-
對(duì)象,這個(gè)地方大家寫的時(shí)候需要注意,需要是相對(duì)于當(dāng)前項(xiàng)目的路徑.對(duì)象,例如
models.Object
表示models
目錄下的Object對(duì)象,這樣bee在生成文檔的時(shí)候會(huì)去掃描改對(duì)象并顯示給用戶改對(duì)象。 -
query 表示帶在url串里面?aa=bb&cc=dd
-
form 表示使用表單遞交數(shù)據(jù)
-
path 表示URL串中得字符,例如/user/{uid} 那么uid就是一個(gè)path類型的參數(shù)
-
body 表示使用raw body進(jìn)行數(shù)據(jù)的傳輸
-
header 表示通過(guò)header進(jìn)行數(shù)據(jù)的傳輸
-
-
成功返回的code和對(duì)象或者信息
格式:code 對(duì)象類型 信息或者對(duì)象路徑
code:表示HTTP的標(biāo)準(zhǔn)status code,200 201等
對(duì)象類型:{object}表示對(duì)象,其他默認(rèn)都認(rèn)為是字符類型,會(huì)顯示第三個(gè)參數(shù)給用戶,如果是{object}類型,那么就會(huì)去掃描改對(duì)象,并顯示給用戶
對(duì)象路徑和上面Param中得對(duì)象類型一樣,使用路徑.對(duì)象的方式來(lái)描述
-
錯(cuò)誤返回的信息,
格式: code 信息
code:同上Success
錯(cuò)誤信息:字符串描述信息
-
上面已經(jīng)描述過(guò)支持兩個(gè)參數(shù),第一個(gè)是路由,第二個(gè)表示支持的HTTP方法
那么我們通過(guò)上面的注釋會(huì)生成怎么樣的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": ""
- }
- ]
- }
- ]
- }
上面闡述的這些描述都是可以使用一個(gè)或者多個(gè) '\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP)進(jìn)行分割 |
#p#
對(duì)象自定義注釋
我們的對(duì)象定義如下:
- type Object struct {
- ObjectId string
- Score int64
- PlayerName string
- }
通過(guò)掃描生成的代碼如下:
- Object {
- ObjectId (string, optional): ,
- PlayerName (string, optional): ,
- Score (int64, optional):
- }
我們發(fā)現(xiàn)字段都是optional
的,而且沒(méi)有任何針對(duì)字段的描述,其實(shí)我們可以在對(duì)象定義里面增加如下的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"`
- }
而且如果你的對(duì)象tag里面如果存在json或者thrift描述,那么就會(huì)使用改描述作為字段名,即如下的代碼:
- type Object struct {
- ObjectId string `json:"object_id"`
- Score int64 `json:"player_score"`
- PlayerName string `json:"player_name"`
- }
就會(huì)輸出如下的文檔信息:
- Object {
- object_id (string, optional): ,
- player_score (string, optional): ,
- player_name (int64, optional):
- }
常見(jiàn)錯(cuò)誤及問(wèn)題
-
Q:bee沒(méi)有上面執(zhí)行的命令?
A:請(qǐng)更新bee到最新版本,目前bee的版本是1.1.2,beego的版本是1.3.1
-
Q:bee更新的時(shí)候出錯(cuò)了?
A:第一可能是GFW的問(wèn)題,第二可能是你修改過(guò)了源碼,刪除重新下載,第三可能你升級(jí)了Go版本,你需要?jiǎng)h除GOPATH/pkg下的所有文件
-
Q:下載swagger很慢?
A:想辦法讓他變快,因?yàn)槲椰F(xiàn)在放在了github上面
-
Q:文檔生成了,但是我沒(méi)辦法測(cè)試請(qǐng)求?
A:你看看你訪問(wèn)的地址是不是和請(qǐng)求的URL是同一個(gè)地址,因?yàn)閟wagger是使用Ajax請(qǐng)求數(shù)據(jù)的,所以跨域的問(wèn)題,解決CORS的辦法就是保持域一致,包括URL和端口。
-
Q:運(yùn)行的時(shí)候發(fā)生了未知的錯(cuò)誤?
A:那就來(lái)提issue或者給我留言吧,我會(huì)盡力幫助你解決你遇到的問(wèn)題