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或者给我留言吧,我会尽力帮助你解决你遇到的问题

    相关文章
    相关标签/搜索