beego使用(2)-控制器

控制器是beego框架中mvc中c那一部分,其逻辑通过路由访问的时候调用。其主要做的是获取请求的参数,解析参数,然后调用model层或者view的内容,然后返回响应数据给客户端。这里主要说明控制器构建一个完整应用时应该具备的一些东西,以及其是怎么被调用。

路由关联

上一节中有说一般放在单独的routers/router.go文件中。并且在init函数中关联。
在main.go中通过一下方式使用

1
2
3
import {
_ "project/routers"
}

关联页面

在init函数中关联相应的路由,下面是关联页面。在浏览器输入网站地址访问页面,需要将页面关联到”/“路由上,这里用该路由关联一个MainController,MainController用于访问页面。

1
beego.Router("/", &controllers.MainController{})

beego中默认的模板目录是在views下面,也可以通过以下方式设置。

1
beego.ViewsPath = "/a/b"

一般情况下都会直接放在views中,beego会解析目录下所有模板文件并缓存,但是开发模式下是每次都重新解析,不做缓存。MainController中实现Get函数。

1
2
3
4
func (c *MainController) Get() {
c.Data['item'] = c.value //用于给模板中元素赋值
c.TplName = "index.html" //用于设置访问的模板
}

上面代码中可以看到页面已经和访问关联起来了。

关联API

日常开发中,我们访问的路由可能都是分类的,比如a/b/1,a/b/2,/a/c等,这种情况下面可以通过命名空间的方式关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
ns := beego.NewNamespace("/api",
beego.NSNamespace("/test1",
beego.NSInclude(
&controllers.Test1Controller{},
),
),
beego.NSNamespace("/test2",
beego.NSInclude(
&controllers.Test2Controller{},
),
),
)
beego.AddNamespace(ns)

通过这种方式可以访问api/test1/router和api/test2/router了。在对应的控制器中需要使用对应的Restful函数或者自定义函数,需要使用注解路由的方式。注解路由可以让beego框架自己生成对应的路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type UserController struct {
APIController
}

// Post ...
// @Title Post
// @Description 创建
// @Param id formData int true "ID"
// @Param name formData string true "名称"
// @Success 0 返回新增的分类实例
// @Failure 非零 msg标识错误信息
// @router /create [post]
func (c *UserController) Create() {
}

上面给出了一个基本的注解路由,其中包括参数,返回,路由,请求方式等。

控制器编写

对于一般的web系统来说,除了提供页面的访问,还会有api接口提供,这里给出一个继承结构。

BaseController实现

1
2
3
4
5
6
type BaseController struct {
User string
ReqId string
StartTime time.Time
beego.Controller
}

这里ReqId用于记录这次访问的id,用于唯一标志一次访问;StartTime是访问的时间,便于在日志输出访问时长。
Controller提供了Prepare和Finish函数,用于在控制器函数执行前和后面的处理。这里可以对日志进行输出。

1
2
3
4
5
6
7
8
9
10
func (b *BaseController) Prepare() {
b.StartTime = time.Now()
b.ReqId = uuid.NewV4().String()
logs.Info("req start|%s|%s", b.ReqId, b.Ctx.Request.URL)
}

func (b *BaseController) Finish() {
cost := time.Since(b.StartTime).Seconds()
logs.Info("req end|%s|%s|%f", b.ReqId, b.UserName, cost)
}

APIController实现

APIController从BaseController继承,这里对于接口控制器,需要包括执行错误信息,获取的响应数据。

1
2
3
4
5
6
type APIController struct {
RspData interface{}
Error error
User string
BaseController
}

对于返回的数据,我们一般需要包括一个接口调用的编码,响应的数据,调用中的错误或者其它信息。这里定义API响应的结构体。

1
2
3
4
5
type APIRsp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}

接口调用,或者页面进入可能需要鉴权处理,这里需要有一个鉴权函数。

1
2
3
4
5
6
7
8
func (b *BaseController) CheckUser() {
if beego.AppConfig.String("debug") == "true" {
b.User = "user"
return
}
header := b.Ctx.Request.Header
sign := header.Get("signature")
}

在开发中可能不需要去做登录,所以这里在app.conf中给出debug标志。然后从头信息中获取相关的信息去做鉴权。如果鉴权不通过就要跳转到登录页面。这里APIController重写Prepare。代码中可以看到首先是继承了BaseController的Prepare,这样可以生成日志信息。然后鉴权,鉴权出错则返回了一个错误信息,这个信息是自己定义的,包括返回状态码,重定向url和错误信息。由于是自己定义的信息,所以需要和前端达成统一,前端需要按照这个定义去处理。

1
2
3
4
5
6
7
8
9
10
11
func (c *APIController) Prepare() {
c.BaseController.Prepare()
err := c.CheckUser()
if err != nil {
params := util.Params{"url": fmt.Sprintf("http://%s", c.Ctx.Request.Host)}
url := fmt.Sprintf("")
c.Controller.Data["json"] = map[string]interface{}{"code": -302, "location": url, "msg": fmt.Sprintf("login err|%v", err)}
defer c.ServeJSON()
return
}
}

上面代码中用到ServerJSON,这个函数用于将调用的结果转成json格式的数据。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (c *APIController) ServeJSON(encoding ...bool) {
//方法使用了recover来捕获运行时异常, 所以调用方必须使用defer调用
if r := recover(); r != nil {
logs.Error("panic|%v|%v|%v|%v", c.ReqId, c.UserName, c.Ctx.Request.URL, r)
c.Error = errors.New(fmt.Sprintf("panic|%v", r))
}
c.Ctx.Output.SetStatus(200)
var rsp APIRsp
if c.Error != nil {
code := -1
if ae, ok := c.Error.(*app_err.AppError); ok {
code = ae.Code
}
rsp.Code, rsp.Msg = code, fmt.Sprintf("%v", c.Error.Error())
} else {
rsp.Code, rsp.Msg = 0, ""
}
rsp.Data = c.RspData
c.Controller.Data["json"] = rsp
c.Controller.ServeJSON(encoding...)
c.BaseController.Finish()
c.StopRun()
}

这里使用到了错误处理,错误处理在app_err目录下的err.go中编写,这种方式可以统一管理错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type AppError struct {
Message string
Code int
RawError error
}

func (ae *AppError) Error() string {
return ae.Message
}

func New(code int, message string, rawError error) (ae *AppError) {
ae = &AppError{Message: message, Code: code, RawError: rawError}
return
}

const (
ECODE_PARAMS_ERROR int = 1000
ECODE_NOT_FOUND = 2000
ECODE_EXIST = 2001
}

除了以上内容,Controllel还用于接收参数,所以定义了一些获取参数的函数,通常有一些会需要重写或者定义,其主要场景有:
1、错误处理。比如这里重定义GetInt64函数,加入错误处理。

1
2
3
4
5
6
7
8
func (c *APIController) GetInt64(key string, def ...int64) (int64, error) {
v, err := c.Controller.GetInt64(key, def...)
if err != nil {
msg := fmt.Sprintf("invalid %s[%s]", key, c.GetString(key))
c.Error = app_err.New(app_err.ECODE_PARAMS_ERROR, msg, err)
}
return v, err
}

2、处理参数,比如批量删除时用逗号或者分号分隔的id。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (c *APIController) GetInt64Array(key string, def ...[]int64) (values []int64, err error) {
valuesArg := c.GetString(key)
if valuesArg != "" {
for _, valuestr := range strings.Split(valuesArg, ",") {
value, err := strconv.ParseInt(valuestr, 10, 64)
if err != nil {
msg := fmt.Sprintf("invalid case_ids [%s]", valuesArg)
err = app_err.New(app_err.ECODE_PARAMS_ERROR, msg, err)
return values, err
}
values = append(values, value)
}
}
return
}

3、通用参数。比如page和pagesize参数。

1
2
3
4
5
6
7
8
9
10
11
func (c *APIController) GetPageAndPagesize() (page, pagesize int, err error) {
page, err = c.GetInt("page", 1)
if err != nil {
return
}
pagesize, err = c.GetInt("pagesize", 10)
if err != nil {
return
}
return
}

4、一次获取多个参数。

1
2
3
4
5
6
7
8
type Filters map[string]string
func (c *APIController) GetQueryFilters(params []string) (filters models.Filters) {
filters = models.Filters{}
for _, param := range params {
filters[param] = c.GetString(param)
}
return
}

以上就是APIController的所需要具备的一些基本内容。对于具体的Controller则只需要去实现相关的函数即可。