beego之controller层(三)

参数配置

beego 目前支持 INI、XML、JSON、YAML 格式的配置文件解析,但是默认采用了 INI 格式解析,用户可以通过简单的配置就可以获得很大的灵活性。

默认配置解析

beego 默认会解析当前应用下的 conf/app.conf 文件。

通过这个文件你可以初始化很多 beego 的默认参数:

appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"

它们都维护在结构体 beego/server/web#Config

BConfig 就是 beego 里面的默认的配置,也是结构体 beego/server/web#Config 的实例。

你也可以直接通过web.BConfig.AppName="beepkg"这样来修改,和上面的配置效果一样,只是一个在代码里面写死了,而配置文件就会显得更加灵活。

你也可以在配置文件中配置应用需要用的一些配置信息,例如下面所示的数据库信息:

mysqluser = "root"
mysqlpass = "rootpass"
mysqlurls = "127.0.0.1"
mysqldb   = "beego"

那么你就可以通过如下的方式获取设置的配置信息:

web.AppConfig.String("mysqluser")
web.AppConfig.String("mysqlpass")
web.AppConfig.String("mysqlurls")
web.AppConfig.String("mysqldb")

AppConfig 的方法如下:

  • Set(key, val string) error
  • String(key string) string
  • Strings(key string) []string
  • Int(key string) (int, error)
  • Int64(key string) (int64, error)
  • Bool(key string) (bool, error)
  • Float(key string) (float64, error)
  • DefaultString(key string, defaultVal string) string
  • DefaultStrings(key string, defaultVal []string)
  • DefaultInt(key string, defaultVal int) int
  • DefaultInt64(key string, defaultVal int64) int64
  • DefaultBool(key string, defaultVal bool) bool
  • DefaultFloat(key string, defaultVal float64) float64
  • DIY(key string) (interface{}, error)
  • GetSection(section string) (map[string]string, error)
  • SaveConfigFile(filename string) error

在使用 ini 类型的配置文件中, key 支持 section::key 模式.

你可以用 Default* 方法返回默认值.

不同级别的配置

在配置文件里面支持 section,可以有不同的 Runmode 的配置,默认优先读取 runmode 下的配置信息,例如下面的配置文件:

appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"

[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888

上面的配置文件就是在不同的 runmode 下解析不同的配置,例如在 dev 模式下,httpport 是 8080,在 prod 模式下是 8088,在 test 模式下是 8888。其他配置文件同理。解析的时候优先解析 runmode 下的配置,然后解析默认的配置。

读取不同模式下配置参数的方法是“模式::配置参数名”,比如:beego.AppConfig.String(“dev::mysqluser”)

对于自定义的参数,需使用 GetConfig(typ, key string, defaultVal interface{}) 来获取指定 runmode 下的配置(需 1.4.0 以上版本),typ 为参数类型,key 为参数名, defaultVal 为默认值。

多个配置文件

INI 格式配置支持 include 方式,引用多个配置文件,例如下面的两个配置文件效果同上:

app.conf

appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090

include "app2.conf"

app2.conf

runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"

[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888

支持环境变量配置

配置文件解析支持从环境变量中获取配置项,配置项格式:${环境变量}。例如下面的配置中优先使用环境变量中配置的 runmode 和 httpport,如果有配置环境变量 ProRunMode 则优先使用该环境变量值。如果不存在或者为空,则使用 “dev” 作为 runmode。

app.conf

runmode  = "${ProRunMode||dev}"
httpport = "${ProPort||9090}"

系统默认参数

beego 中带有很多可配置的参数,我们来一一认识一下它们,这样有利于我们在接下来的 beego 开发中可以充分的发挥他们的作用(你可以通过在 conf/app.conf 中设置对应的值,不区分大小写):

基础配置
  • BConfig
    保存了所有 beego 里面的系统默认参数,你可以通过 web.BConfig 来访问和修改底下的所有配置信息.

配置文件路径,默认是应用程序对应的目录下的 conf/app.conf,用户可以在程序代码中加载自己的配置文件
beego.LoadAppConfig("ini", "conf/app2.conf")
也可以加载多个文件,只要你调用多次就可以了,如果后面的文件和前面的 key 冲突,那么以最新加载的为最新值

App 配置
  • AppName

    应用名称,默认是 beego。通过 bee new 创建的是创建的项目名。

    web.BConfig.AppName = "beego"

  • RunMode

    应用的运行模式,可选值为 prod, dev 或者 test. 默认是 dev, 为开发模式,在开发模式下出错会提示友好的出错页面,如前面错误描述中所述。

    web.BConfig.RunMode = "dev"

  • RouterCaseSensitive

    是否路由忽略大小写匹配,默认是 true,区分大小写

    web.BConfig.RouterCaseSensitive = true

  • ServerName

    beego 服务器默认在请求的时候输出 server 为 beego。

    web.BConfig.ServerName = "beego"

  • RecoverPanic

    是否异常恢复,默认值为 true,即当应用出现异常的情况,通过 recover 恢复回来,而不会导致应用异常退出。

    web.BConfig.RecoverPanic = true

  • CopyRequestBody

    是否允许在 HTTP 请求时,返回原始请求体数据字节,默认为 false (GET or HEAD or 上传文件请求除外)。

    web.BConfig.CopyRequestBody = false

  • EnableGzip

    是否开启 gzip 支持,默认为 false 不支持 gzip,一旦开启了 gzip,那么在模板输出的内容会进行 gzip 或者 zlib 压缩,根据用户的 Accept-Encoding 来判断。

    web.BConfig.EnableGzip = false

    Gzip允许用户自定义压缩级别、压缩长度阈值和针对请求类型压缩:

    1. 压缩级别, gzipCompressLevel = 9,取值为 1~9,如果不设置为 1(最快压缩)
    2. 压缩长度阈值, gzipMinLength = 256,当原始内容长度大于此阈值时才开启压缩,默认为 20B(ngnix默认长度)
    3. 请求类型, includedMethods = get;post,针对哪些请求类型进行压缩,默认只针对 GET 请求压缩
  • MaxMemory

    文件上传默认内存缓存大小,默认值是 1 << 26(64M)。

    web.BConfig.MaxMemory = 1 << 26

  • EnableErrorsShow

    是否显示系统错误信息,默认为 true。

    web.BConfig.EnableErrorsShow = true

  • EnableErrorsRender

    是否将错误信息进行渲染,默认值为 true,即出错会提示友好的出错页面,对于 API 类型的应用可能需要将该选项设置为 false 以阻止在 dev 模式下不必要的模板渲染信息返回。

Web配置
  • AutoRender

    是否模板自动渲染,默认值为 true,对于 API 类型的应用,应用需要把该选项设置为 false,不需要渲染模板。

    web.BConfig.WebConfig.AutoRender = true

  • EnableDocs

    是否开启文档内置功能,默认是 false

    web.BConfig.WebConfig.EnableDocs = true

  • FlashName

    Flash 数据设置时 Cookie 的名称,默认是 BEEGO_FLASH

    web.BConfig.WebConfig.FlashName = "BEEGO_FLASH"

  • FlashSeperator

    Flash 数据的分隔符,默认是 BEEGOFLASH

    web.BConfig.WebConfig.FlashSeparator = "BEEGOFLASH"

  • DirectoryIndex

    是否开启静态目录的列表显示,默认不显示目录,返回 403 错误。

    web.BConfig.WebConfig.DirectoryIndex = false

  • StaticDir

    静态文件目录设置,默认是static

    可配置单个或多个目录:

    1. 单个目录, StaticDir = download. 相当于 beego.SetStaticPath("/download","download")
    2. 多个目录, StaticDir = download:down download2:down2. 相当于 beego.SetStaticPath("/download","down")beego.SetStaticPath("/download2","down2")

    web.BConfig.WebConfig.StaticDir

  • StaticExtensionsToGzip

    允许哪些后缀名的静态文件进行 gzip 压缩,默认支持 .css 和 .js

    web.BConfig.WebConfig.StaticExtensionsToGzip = []string{".css", ".js"}

    等价 config 文件中

    StaticExtensionsToGzip = .css, .js
    
  • TemplateLeft

    模板左标签,默认值是{{

    web.BConfig.WebConfig.TemplateLeft="{{"

  • TemplateRight

    模板右标签,默认值是}}

    web.BConfig.WebConfig.TemplateRight="}}"

  • ViewsPath

    模板路径,默认值是 views。

    web.BConfig.WebConfig.ViewsPath="views"

  • EnableXSRF

    是否开启 XSRF,默认为 false,不开启。

    web.BConfig.WebConfig.EnableXSRF = false

  • XSRFKEY

    XSRF 的 key 信息,默认值是 beegoxsrf。 EnableXSRF=true 才有效

    web.BConfig.WebConfig.XSRFKEY = "beegoxsrf"

  • XSRFExpire

    XSRF 过期时间,默认值是 0,不过期。

    web.BConfig.WebConfig.XSRFExpire = 0

  • CommentRouterPath

    CommentRouterPath 注解路由所在位置。默认值是controllers。 Beego 会在启动的时候扫描下面的文件生成了路由。
    web.BConfig.WebConfig.CommentRouterPath = "controllers"

监听配置
  • Graceful

    是否开启热升级,默认是 false,关闭热升级。

    web.BConfig.Listen.Graceful=false

  • ServerTimeOut

    设置 HTTP 的超时时间,默认是 0,不超时。

    web.BConfig.Listen.ServerTimeOut=0

  • ListenTCP4

    监听本地网络地址类型,默认是TCP6,可以通过设置为true设置为TCP4。

    web.BConfig.Listen.ListenTCP4 = true

  • EnableHTTP

    是否启用 HTTP 监听,默认是 true。

    web.BConfig.Listen.EnableHTTP = true

  • HTTPAddr

    应用监听地址,默认为空,监听所有的网卡 IP。

    web.BConfig.Listen.HTTPAddr = ""

  • HTTPPort

    应用监听端口,默认为 8080。

    web.BConfig.Listen.HTTPPort = 8080

  • EnableHTTPS

    是否启用 HTTPS,默认是 false 关闭。当需要启用时,先设置 EnableHTTPS = true,并设置 HTTPSCertFileHTTPSKeyFile

    web.BConfig.Listen.EnableHTTPS = false

  • HTTPSAddr

    应用监听地址,默认为空,监听所有的网卡 IP。

    web.BConfig.Listen.HTTPSAddr = ""

  • HTTPSPort

    应用监听端口,默认为 10443

    web.BConfig.Listen.HTTPSPort = 10443

  • HTTPSCertFile

    开启 HTTPS 后,ssl 证书路径,默认为空。

    web.BConfig.Listen.HTTPSCertFile = "conf/ssl.crt"

  • HTTPSKeyFile

    开启 HTTPS 之后,SSL 证书 keyfile 的路径。

    web.BConfig.Listen.HTTPSKeyFile = "conf/ssl.key"

  • EnableAdmin

    是否开启进程内监控模块,默认 false 关闭。

    web.BConfig.Listen.EnableAdmin = false

  • AdminAddr

    监控程序监听的地址,默认值是 localhost 。

    web.BConfig.Listen.AdminAddr = "localhost"

  • AdminPort

    监控程序监听的地址,默认值是 8088 。

    web.BConfig.Listen.AdminPort = 8088

  • EnableFcgi

    是否启用 fastcgi , 默认是 false。

    web.BConfig.Listen.EnableFcgi = false

  • EnableStdIo

    通过fastcgi 标准I/O,启用 fastcgi 后才生效,默认 false。

    web.BConfig.Listen.EnableStdIo = false

Session配置
  • SessionOn

    session 是否开启,默认是 false。

    web.BConfig.WebConfig.Session.SessionOn = false

  • SessionProvider

    session 的引擎,默认是 memory,详细参见 session 模块

    web.BConfig.WebConfig.Session.SessionProvider = ""

  • SessionName

    存在客户端的 cookie 名称,默认值是 beegosessionID。

    web.BConfig.WebConfig.Session.SessionName = "beegosessionID"

  • SessionGCMaxLifetime

    session 过期时间,默认值是 3600 秒。

    web.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600

  • SessionProviderConfig

    配置信息,根据不同的引擎设置不同的配置信息,详细的配置请看下面的引擎设置,详细参见 session 模块

  • SessionCookieLifeTime

    session 默认存在客户端的 cookie 的时间,默认值是 3600 秒。

    web.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600

  • SessionAutoSetCookie

    是否开启SetCookie, 默认值 true 开启。

    web.BConfig.WebConfig.Session.SessionAutoSetCookie = true

  • SessionDomain

    session cookie 存储域名, 默认空。

    web.BConfig.WebConfig.Session.SessionDomain = ""

Log配置
  • AccessLogs

    是否输出日志到 Log,默认在 prod 模式下不会输出日志,默认为 false 不输出日志。此参数不支持配置文件配置。

    web.BConfig.Log.AccessLogs = false

  • FileLineNum

    是否在日志里面显示文件名和输出日志行号,默认 true。此参数不支持配置文件配置。

    web.BConfig.Log.FileLineNum = true

  • Outputs

    日志输出配置,参考 logs 模块,console file 等配置,此参数不支持配置文件配置。

    web.BConfig.Log.Outputs = map[string]string{"console": ""}

    or

    web.BConfig.Log.Outputs["console"] = ""

路由设置

什么是路由设置呢?前面介绍的 MVC 结构执行时,介绍过 beego 存在三种方式的路由:固定路由、正则路由、自动路由,接下来详细的讲解如何使用这三种路由。

基础路由

从 beego 1.2 版本开始支持了基本的 RESTful 函数式路由,应用中的大多数路由都会定义在 routers/router.go 文件中。最简单的 beego 路由由 URI 和闭包函数组成。

基本 GET 路由

web.Get("/",func(ctx *context.Context){
     ctx.Output.Body([]byte("hello world"))
})

基本 POST 路由

web.Post("/alice",func(ctx *context.Context){
     ctx.Output.Body([]byte("bob"))
})

注册一个可以响应任何 HTTP 的路由

web.Any("/foo",func(ctx *context.Context){
     ctx.Output.Body([]byte("bar"))
})

所有的支持的基础函数如下所示

  • web.Get(router, web.HandleFunc)
  • web.Post(router, web.HandleFunc)
  • web.Put(router, web.HandleFunc)
  • web.Patch(router, web.HandleFunc)
  • web.Head(router, web.HandleFunc)
  • web.Options(router, web.HandleFunc)
  • web.Delete(router, web.HandleFunc)
  • web.Any(router, web.HandleFunc)

支持自定义的 handler 实现

有些时候我们已经实现了一些 rpc 的应用,但是想要集成到 beego 中,或者其他的 httpserver 应用,集成到 beego 中来.现在可以很方便的集成:

s := rpc.NewServer()
s.RegisterCodec(json.NewCodec(), "application/json")
s.RegisterService(new(HelloService), "")
web.Handler("/rpc", s)

web.Handler(router, http.Handler) 这个函数是关键,第一个参数表示路由 URI, 第二个就是你自己实现的 http.Handler, 注册之后就会把所有 rpc 作为前缀的请求分发到 http.Handler 中进行处理.

这个函数其实还有第三个参数就是是否是前缀匹配,默认是 false, 如果设置了 true, 那么就会在路由匹配的时候前缀匹配,即 /rpc/user 这样的也会匹配去运行

路由参数

固定路由,正则路由,这些参数一样适用于上面的这些函数

RESTful Controller 路由

在介绍这三种 beego 的路由实现之前先介绍 RESTful,我们知道 RESTful 是一种目前 API 开发中广泛采用的形式,beego 默认就是支持这样的请求方法,也就是用户 Get 请求就执行 Get 方法,Post 请求就执行 Post 方法。因此默认的路由是这样 RESTful 的请求方式

固定路由

固定路由也就是全匹配的路由,如下所示:

web.Router("/", &controllers.MainController{})
web.Router("/admin", &admin.UserController{})
web.Router("/admin/index", &admin.ArticleController{})
web.Router("/admin/addpkg", &admin.AddController{})

如上所示的路由就是我们最常用的路由方式,一个固定的路由,一个控制器,然后根据用户请求方法不同请求控制器中对应的方法,典型的 RESTful 方式。

正则路由

为了用户更加方便的路由设置,beego 参考了 sinatra 的路由实现,支持多种方式的路由:

  • web.Router(“/api/?:id”, &controllers.RController{})

    默认匹配 //例如对于URL”/api/123”可以匹配成功,此时变量”:id”值为”123”,URL”/api/“可正常匹配

  • web.Router(“/api/:id”, &controllers.RController{})

    默认匹配 //例如对于URL”/api/123”可以匹配成功,此时变量”:id”值为”123”,但URL”/api/“匹配失败

  • web.Router(“/api/:id([0-9]+)“, &controllers.RController{})

    自定义正则匹配 //例如对于URL”/api/123”可以匹配成功,此时变量”:id”值为”123”

  • web.Router(“/user/:username([\w]+)“, &controllers.RController{})

    正则字符串匹配 //例如对于URL”/user/astaxie”可以匹配成功,此时变量”:username”值为”astaxie”

  • web.Router(“/download/.”, &controllers.RController{})

    *匹配方式 //例如对于URL”/download/file/api.xml”可以匹配成功,此时变量”:path”值为”file/api”, “:ext”值为”xml”

  • web.Router(“/download/ceshi/*“, &controllers.RController{})

    *全匹配方式 //例如对于URL”/download/ceshi/file/api.json”可以匹配成功,此时变量”:splat”值为”file/api.json”

  • web.Router(“/ 🆔int”, &controllers.RController{})

    int 类型设置方式,匹配 :id为int 类型,框架帮你实现了正则 ([0-9]+)

  • web.Router(“/:hi:string”, &controllers.RController{})

    string 类型设置方式,匹配 :hi 为 string 类型。框架帮你实现了正则 ([\w]+)

  • web.Router(“/cms_:id([0-9]+).html”, &controllers.CmsController{})

    带有前缀的自定义正则 //匹配 :id 为正则类型。匹配 cms_123.html 这样的 url :id = 123

可以在 Controller 中通过如下方式获取上面的变量:

this.Ctx.Input.Param(":id")
this.Ctx.Input.Param(":username")
this.Ctx.Input.Param(":splat")
this.Ctx.Input.Param(":path")
this.Ctx.Input.Param(":ext")

自定义方法及 RESTful 规则

上面列举的是默认的请求方法名(请求的 method 和函数名一致,例如 GET 请求执行 Get 函数,POST 请求执行 Post 函数),如果用户期望自定义函数名,那么可以使用如下方式:

web.Router("/",&IndexController{},"*:Index")

使用第三个参数,第三个参数就是用来设置对应 method 到函数名,定义如下

  • *表示任意的 method 都执行该函数
  • 使用 httpmethod:funcname 格式来展示
  • 多个不同的格式使用 ; 分割
  • 多个 method 对应同一个 funcname,method 之间通过 , 来分割

以下是一个 RESTful 的设计示例:

web.Router("/api/food",&RestController{},"get:ListFood")
web.Router("/api/food",&RestController{},"post:CreateFood")
web.Router("/api/food",&RestController{},"put:UpdateFood")
web.Router("/api/food",&RestController{},"delete:DeleteFood")

以下是多个 HTTP Method 指向同一个函数的示例:

web.Router("/api",&RestController{},"get,post:ApiFunc")

以下是不同的 method 对应不同的函数,通过 ; 进行分割的示例:

web.Router("/api/food",&RestController{},"get:ListFood;post:CreateFood;put:UpdateFood;delete:DeleteFood")

可用的 HTTP Method:

  • *: 包含以下所有的函数
  • get: GET 请求
  • post: POST 请求
  • put: PUT 请求
  • delete: DELETE 请求
  • patch: PATCH 请求
  • options: OPTIONS 请求
  • head: HEAD 请求

如果同时存在 * 和对应的 HTTP Method,那么优先执行 HTTP Method 的方法,例如同时注册了如下所示的路由:

web.Router("/simple",&SimpleController{},"*:AllFunc;post:PostFunc")

那么执行 POST 请求的时候,执行 PostFunc 而不执行 AllFunc

自定义函数的路由默认不支持 RESTful 的方法,也就是如果你设置了 web.Router("/api",&RestController{},"post:ApiFunc") 这样的路由,如果请求的方法是 POST,那么不会默认去执行 Post 函数。

自动匹配

用户首先需要把需要路由的控制器注册到自动路由中:

web.AutoRouter(&controllers.ObjectController{})

那么 beego 就会通过反射获取该结构体中所有的实现方法,你就可以通过如下的方式访问到对应的方法中:

/object/login   调用 ObjectController 中的 Login 方法
/object/logout  调用 ObjectController 中的 Logout 方法

除了前缀两个 /:controller/:method 的匹配之外,剩下的 url beego 会帮你自动化解析为参数,保存在 this.Ctx.Input.Params 当中:

/object/blog/2013/09/12  调用 ObjectController 中的 Blog 方法,参数如下:map[0:2013 1:09 2:12]

方法名在内部是保存了用户设置的,例如 Login,url 匹配的时候都会转化为小写,所以,/object/LOGIN 这样的 url 也一样可以路由到用户定义的 Login 方法中【实际在1.10.1版本中测试,不会转化大小写,即只能匹配/object/login,其他均不能正常识别】。

现在已经可以通过自动识别出来下面类似的所有 url,都会把请求分发到 controllersimple 方法:

/controller/simple
/controller/simple.html
/controller/simple.json
/controller/simple.xml

可以通过 this.Ctx.Input.Param(":ext") 获取后缀名。

注解路由

从2.0开始,我们使用配置CommentRouterPath来配置注解路由的扫描路径。在dev环境下,我们将自动扫描该配置指向的目录及其子目录,生成路由文件。

生成之后,用户需要显示 Include 相应地 controller。注意, controller 的 method 方法上面须有 router 注释(// @router),详细的使用请看下面的例子:

// CMS API
type CMSController struct {
    web.Controller
}

func (c *CMSController) URLMapping() {
    c.Mapping("StaticBlock", c.StaticBlock)
    c.Mapping("AllBlock", c.AllBlock)
}


// @router /staticblock/:key [get]
func (this *CMSController) StaticBlock() {

}

// @router /all/:key [get]
func (this *CMSController) AllBlock() {

}

可以在 router.go 中通过如下方式注册路由:

web.Include(&CMSController{})

web 自动会进行源码分析,注意只会在 dev 模式下进行生成,生成的路由放在 “/routers/commentsRouter.go” 文件中。

这样上面的路由就支持了如下的路由:

  • GET /staticblock/:key
  • GET /all/:key

其实效果和自己通过 Router 函数注册是一样的:

web.Router("/staticblock/:key", &CMSController{}, "get:StaticBlock")
web.Router("/all/:key", &CMSController{}, "get:AllBlock")

同时大家注意到新版本里面增加了 URLMapping 这个函数,这是新增加的函数,用户如果没有进行注册,那么就会通过反射来执行对应的函数,如果注册了就会通过 interface 来进行执行函数,性能上面会提升很多。

方法表达式路由

方法表达式路由与上面的RESTful基本相似,区别是无需在传入http method和controller方法(如:"get:StaticBlock")。
只需要通过golang的method expression进行传入方法表达式。如果方法是receiver是非指针,则直接使用 包名.Controller.Method 方法 传入,
如果receiver是指针,则使用 (*包名.Controller).Method 进行传参。假如在同包下,包名可进行省略。

type BaseController struct {
    web.Controller
}

func (b BaseController) Ping() {
    b.Data["json"] = "pong"
    b.ServeJSON()
}

func (b *BaseController) PingPointer() {
    b.Data["json"] = "pong_pointer"
    b.ServeJSON()
}

func main() {
    web.CtrlGet("/ping", BaseController.Ping)
    web.CtrlGet("/ping_pointer", (*BaseController).PingPointer)
    web.Run()
}

共有以下几种函数:

  • web.CtrlGet(router, pkg.controller.method)
  • web.CtrlPost(router, pkg.controller.method)
  • web.CtrlPut(router, pkg.controller.method)
  • web.CtrlPatch(router, pkg.controller.method)
  • web.CtrlHead(router, pkg.controller.method)
  • web.CtrlOptions(router, pkg.controller.method)
  • web.CtrlDelete(router, pkg.controller.method)
  • web.CtrlAny(router, pkg.controller.method)

同时也支持namespace的使用:

  • web.NSCtrlGet
  • web.NSCtrlPost
  • ……

namespace

//初始化 namespace
ns :=
web.NewNamespace("/v1",
    web.NSCond(func(ctx *context.Context) bool {
        if ctx.Input.Domain() == "api.beego.vip" {
            return true
        }
        return false
    }),
    web.NSBefore(auth),
    web.NSGet("/notallowed", func(ctx *context.Context) {
        ctx.Output.Body([]byte("notAllowed"))
    }),
    web.NSRouter("/version", &AdminController{}, "get:ShowAPIVersion"),
    web.NSRouter("/changepassword", &UserController{}),
    web.NSNamespace("/shop",
        web.NSBefore(sentry),
        web.NSGet("/:id", func(ctx *context.Context) {
            ctx.Output.Body([]byte("notAllowed"))
        }),
    ),
    web.NSNamespace("/cms",
        web.NSInclude(
            &controllers.MainController{},
            &controllers.CMSController{},
            &controllers.BlockController{},
        ),
    ),
)
//注册 namespace
web.AddNamespace(ns)

上面这个代码支持了如下这样的请求 URL

  • GET /v1/notallowed
  • GET /v1/version
  • GET /v1/changepassword
  • POST /v1/changepassword
  • GET /v1/shop/123
  • GET /v1/cms/ 对应 MainController、CMSController、BlockController 中的注解路由

而且还支持前置过滤,条件判断,无限嵌套 namespace

namespace 的接口如下:

  • NewNamespace(prefix string, funcs …interface{})

    初始化 namespace 对象,下面这些函数都是 namespace 对象的方法,但是强烈推荐使用 NS 开头的相应函数注册,因为这样更容易通过 gofmt 工具看的更清楚路由的级别关系

  • NSCond(cond namespaceCond)

    支持满足条件的就执行该 namespace, 不满足就不执行

  • NSBefore(filiterList …FilterFunc)

  • NSAfter(filiterList …FilterFunc)

    上面分别对应 beforeRouter 和 FinishRouter 两个过滤器,可以同时注册多个过滤器

  • NSInclude(cList …ControllerInterface)

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

  • NSGet(rootpath string, f HandleFunc)

  • NSPost(rootpath string, f HandleFunc)

  • NSDelete(rootpath string, f HandleFunc)

  • NSPut(rootpath string, f HandleFunc)

  • NSHead(rootpath string, f HandleFunc)

  • NSOptions(rootpath string, f HandleFunc)

  • NSPatch(rootpath string, f HandleFunc)

  • NSAny(rootpath string, f HandleFunc)

  • NSHandler(rootpath string, h http.Handler)

  • NSAutoRouter(c ControllerInterface)

  • NSAutoPrefix(prefix string, c ControllerInterface)

    上面这些都是设置路由的函数,详细的使用和上面 beego 的对应函数是一样的

  • NSNamespace(prefix string, params …innnerNamespace)

    嵌套其他 namespace

ns :=
web.NewNamespace("/v1",
    web.NSNamespace("/shop",
        web.NSGet("/:id", func(ctx *context.Context) {
            ctx.Output.Body([]byte("shopinfo"))
        }),
    ),
    web.NSNamespace("/order",
        web.NSGet("/:id", func(ctx *context.Context) {
            ctx.Output.Body([]byte("orderinfo"))
        }),
    ),
    web.NSNamespace("/crm",
        web.NSGet("/:id", func(ctx *context.Context) {
            ctx.Output.Body([]byte("crminfo"))
        }),
    ),
)

下面这些函数都是属于 *Namespace 对象的方法:不建议直接使用,当然效果和上面的 NS 开头的函数是一样的,只是上面的方式更优雅,写出来的代码更容易看得懂

  • Cond(cond namespaceCond)

    支持满足条件的就执行该 namespace, 不满足就不执行,例如你可以根据域名来控制 namespace

  • Filter(action string, filter FilterFunc)

    action 表示你需要执行的位置, before 和 after 分别表示执行逻辑之前和执行逻辑之后的 filter

  • Router(rootpath string, c ControllerInterface, mappingMethods …string)

  • AutoRouter(c ControllerInterface)

  • AutoPrefix(prefix string, c ControllerInterface)

  • Get(rootpath string, f HandleFunc)

  • Post(rootpath string, f HandleFunc)

  • Delete(rootpath string, f HandleFunc)

  • Put(rootpath string, f HandleFunc)

  • Head(rootpath string, f HandleFunc)

  • Options(rootpath string, f HandleFunc)

  • Patch(rootpath string, f HandleFunc)

  • Any(rootpath string, f HandleFunc)

  • Handler(rootpath string, h http.Handler)

    上面这些都是设置路由的函数,详细的使用和上面 beego 的对应函数是一样的

  • Namespace(ns …*Namespace)

更多的例子代码:

//APIS
ns :=
    web.NewNamespace("/api",
        //此处正式版时改为验证加密请求
        web.NSCond(func(ctx *context.Context) bool {
            if ua := ctx.Input.Request.UserAgent(); ua != "" {
                return true
            }
            return false
        }),
        web.NSNamespace("/ios",
            //CRUD Create(创建)、Read(读取)、Update(更新)和Delete(删除)
            web.NSNamespace("/create",
                // /api/ios/create/node/
                web.NSRouter("/node", &apis.CreateNodeHandler{}),
                // /api/ios/create/topic/
                web.NSRouter("/topic", &apis.CreateTopicHandler{}),
            ),
            web.NSNamespace("/read",
                web.NSRouter("/node", &apis.ReadNodeHandler{}),
                web.NSRouter("/topic", &apis.ReadTopicHandler{}),
            ),
            web.NSNamespace("/update",
                web.NSRouter("/node", &apis.UpdateNodeHandler{}),
                web.NSRouter("/topic", &apis.UpdateTopicHandler{}),
            ),
            web.NSNamespace("/delete",
                web.NSRouter("/node", &apis.DeleteNodeHandler{}),
                web.NSRouter("/topic", &apis.DeleteTopicHandler{}),
            )
        ),
    )

web.AddNamespace(ns)

控制器介绍

基于 beego 的 Controller 设计,只需要匿名组合 beego.Controller 就可以了,如下所示:

package your_package

import (
    "github.com/beego/beego/v2/server/web"
)

type xxxController struct {
        web.Controller
}

控制器方法

web.Controller 实现了接口 web.ControllerInterfaceweb.ControllerInterface 定义了如下函数:

  • Init(ctx *context.Context, controllerName, actionName string, app interface{})

    这个函数主要初始化了 Context、相应的 Controller 名称,模板名,初始化模板参数的容器 Data,app 即为当前执行的 Controller 的 reflecttype,这个 app 可以用来执行子类的方法。

  • Prepare()

    这个函数主要是为了用户扩展用的,这个函数会在下面定义的这些 Method 方法之前执行,用户可以重写这个函数实现类似用户验证之类。

  • Get()

    如果用户请求的 HTTP Method 是 GET,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Get 请求。

  • Post()

    如果用户请求的 HTTP Method 是 POST,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Post 请求。

  • Delete()

    如果用户请求的 HTTP Method 是 DELETE,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Delete 请求。

  • Put()

    如果用户请求的 HTTP Method 是 PUT,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Put 请求.

  • Head()

    如果用户请求的 HTTP Method 是 HEAD,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Head 请求。

  • Patch()

    如果用户请求的 HTTP Method 是 PATCH,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Patch 请求.

  • Options()

    如果用户请求的HTTP Method是OPTIONS,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Options 请求。

  • Finish()

    这个函数是在执行完相应的 HTTP Method 方法之后执行的,默认是空,用户可以在子 struct 中重写这个函数,执行例如数据库关闭,清理数据之类的工作。

  • Trace() error

    如果用户请求的 HTTP Method 是 Trace,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Head 请求。

  • Render() error

    这个函数主要用来实现渲染模板,如果 beego.AutoRender 为 true 的情况下才会执行。

  • Mapping(method string, fn func())

    注册一个方法。一般而言, method 是合法的 HTTP 方法名。当然,用户注册自己特定的业务逻辑方法,而后手动调用。

  • HandlerFunc(fnname string) bool

    在前面 Mapping 方法里面注册的方法,可以通过该方法来使用。只会返回调用是否成功的信息——一般而言,只有方法不存在才会返回 false

  • RenderBytes() ([]byte, error)

    将模板渲染成字节数组。需要注意的是,该方法并未检测EnableRender设置。并且,和Render方法相比,它并未将结果输出到Response

  • RenderString() (string, error)

    类似于RenderBytes方法。只是将结果转化为了string

  • Redirect(url string, code int)

    重定向。url是目的地址。

  • SetData(data interface{})

    data存储在控制的数据中。一般而言,你不会考虑用到这个方法。

  • Abort(code string)

    中断当前方法的执行,直接返回该状态码,类似于CustomAbort。参考errors

  • CustomAbort(status int, body string)

    中断方法执行,直接返回该状态码和信息。参考errors

  • StopRun()

    直接触发panic

  • ServeXXX(encoding …bool) error

    返回特性类型的响应。目前我们支持 JSON,JSONP,XML,YAML。参考输出格式

  • ServeFormatted(encoding …bool) error

    返回响应。其格式由客户端的Accept选项指定。参考输出格式

  • Input() (url.Values, error)

    返回传入的参数。

  • ParseForm(obj interface{}) error

    将表单反序列化到 obj 对象中。

  • GetXXX(key string, def…) XXX, err

    从传入参数中,读取某个值。如果传入了默认值,那么在读取不到的情况下,返回默认值,否则返回错误。XXX 可以是 golang 所支持的基本类型,或者是 string, File 对象

  • SaveToFile(fromfile, tofile string) error

    将上传的文件保存到文件系统中。其中fromfile是上传的文件的名字。

  • SetSession(name interface{}, value interface{}) error

    Session中设置值。

  • GetSession(name interface{}) interface{}

    Session中读取值。

  • DelSession(name interface{}) error

    Session中删除某项。

  • SessionRegenerateID() error

    重新生成一个SessionId

  • DestroySession() error

    销毁Session

  • IsAjax() bool

    是否是 Ajax 请求

  • GetSecureCookie(Secret, key string) (string, bool)

    Cookie中读取数据。bool返回值,表达是否取到了数据。

  • SetSecureCookie(Secret, name, value string, others …interface{})

    设置Cookie

  • XSRFToken() string

    创建一个CSRF token.

  • CheckXSRFCookie() bool

    检测是否有CSRF token

子类扩展

通过子 struct 的方法重写,用户就可以实现自己的逻辑,接下来我们看一个实际的例子:

type AddController struct {
    web.Controller
}

func (this *AddController) Prepare() {

}

func (this *AddController) Get() {
    this.Data["content"] = "value"
    this.Layout = "admin/layout.html"
    this.TplName = "admin/add.tpl"
}

func (this *AddController) Post() {
    pkgname := this.GetString("pkgname")
    content := this.GetString("content")
    pk := models.GetCruPkg(pkgname)
    if pk.Id == 0 {
        var pp models.PkgEntity
        pp.Pid = 0
        pp.Pathname = pkgname
        pp.Intro = pkgname
        models.InsertPkg(pp)
        pk = models.GetCruPkg(pkgname)
    }
    var at models.Article
    at.Pkgid = pk.Id
    at.Content = content
    models.InsertArticle(at)
    this.Ctx.Redirect(302, "/admin/index")
}

从上面的例子可以看出来,通过重写方法可以实现对应 method 的逻辑,实现 RESTFul 结构的逻辑处理。

下面我们再来看一种比较流行的架构,首先实现一个自己的基类 baseController,实现一些初始化的方法,然后其他所有的逻辑继承自该基类:

type NestPreparer interface {
        NestPrepare()
}

// baseRouter implemented global settings for all other routers.
type baseController struct {
        web.Controller
        i18n.Locale
        user    models.User
        isLogin bool
}
// Prepare implemented Prepare method for baseRouter.
func (this *baseController) Prepare() {

        // page start time
        this.Data["PageStartTime"] = time.Now()

        // Setting properties.
        this.Data["AppDescription"] = utils.AppDescription
        this.Data["AppKeywords"] = utils.AppKeywords
        this.Data["AppName"] = utils.AppName
        this.Data["AppVer"] = utils.AppVer
        this.Data["AppUrl"] = utils.AppUrl
        this.Data["AppLogo"] = utils.AppLogo
        this.Data["AvatarURL"] = utils.AvatarURL
        this.Data["IsProMode"] = utils.IsProMode

        if app, ok := this.AppController.(NestPreparer); ok {
                app.NestPrepare()
        }
}

上面定义了基类,大概是初始化了一些变量,最后有一个 Init 函数中那个 app 的应用,判断当前运行的 Controller 是否是 NestPreparer 实现,如果是的话调用子类的方法,下面我们来看一下 NestPreparer 的实现:

type BaseAdminRouter struct {
    baseController
}

func (this *BaseAdminRouter) NestPrepare() {
    if this.CheckActiveRedirect() {
            return
    }

    // if user isn't admin, then logout user
    if !this.user.IsAdmin {
            models.LogoutUser(&this.Controller)

            // write flash message
            this.FlashWrite("NotPermit", "true")

            this.Redirect("/login", 302)
            return
    }

    // current in admin page
    this.Data["IsAdmin"] = true

    if app, ok := this.AppController.(ModelPreparer); ok {
            app.ModelPrepare()
            return
    }
}

func (this *BaseAdminRouter) Get(){
    this.TplName = "Get.tpl"
}

func (this *BaseAdminRouter) Post(){
    this.TplName = "Post.tpl"
}

这样我们的执行器执行的逻辑是这样的,首先执行 Prepare,这个就是 Go 语言中 struct 中寻找方法的顺序,依次往父类寻找。执行 BaseAdminRouter 时,查找他是否有 Prepare 方法,没有就寻找 baseController,找到了,那么就执行逻辑,然后在 baseController 里面的 this.AppController 即为当前执行的控制器 BaseAdminRouter,因为会执行 BaseAdminRouter.NestPrepare 方法。然后开始执行相应的 Get 方法或者 Post 方法。

提前终止运行

我们应用中经常会遇到这样的情况,在 Prepare 阶段进行判断,如果用户认证不通过,就输出一段信息,然后直接中止进程,之后的 Post、Get 之类的不再执行,那么如何终止呢?可以使用 StopRun 来终止执行逻辑,可以在任意的地方执行。

type RController struct {
    beego.Controller
}

func (this *RController) Prepare() {
    this.Data["json"] = map[string]interface{}{"name": "astaxie"}
    this.ServeJSON()
    this.StopRun()
}

调用 StopRun 之后,如果你还定义了 Finish 函数就不会再执行,如果需要释放资源,那么请自己在调用 StopRun 之前手工调用 Finish 函数。

请求数据的处理

获取参数

我们经常需要获取用户传递的数据,包括 Get、POST 等方式的请求,beego 里面会自动解析这些数据,你可以通过如下方式获取数据:

  • GetString(key string) string
  • GetStrings(key string) []string
  • GetInt(key string) (int64, error)
  • GetBool(key string) (bool, error)
  • GetFloat(key string) (float64, error)

使用例子如下:

func (this *MainController) Post() {
    jsoninfo := this.GetString("jsoninfo")
    if jsoninfo == "" {
        this.Ctx.WriteString("jsoninfo is empty")
        return
    }
}

如果你需要的数据可能是其他类型的,例如是 int 类型而不是 int64,那么你需要这样处理:

func (this *MainController) Post() {
    id := this.Input().Get("id")
    intid, err := strconv.Atoi(id)
}

更多其他的 request 的信息,用户可以通过 this.Ctx.Request 获取信息,关于该对象的属性和方法参考手册 Request

直接解析到 struct

如果要把表单里的内容赋值到一个 struct 里,除了用上面的方法一个一个获取再赋值外,beego 提供了通过另外一个更便捷的方式,就是通过 struct 的字段名或 tag 与表单字段对应直接解析到 struct。

定义 struct:

type user struct {
    Id    int         `form:"-"`
    Name  interface{} `form:"username"`
    Age   int         `form:"age"`
    Email string
}

表单:
```html

名字:<input name="username" type="text" />
年龄:<input name="age" type="text" />
邮箱:<input name="Email" type="text" />
<input type="submit" value="提交" />

```
Controller 里解析:

func (this *MainController) Post() {
    u := user{}
    if err := this.ParseForm(&u); err != nil {
        //handle error
    }
}

注意:

  • StructTag form 的定义和 renderform方法 共用一个标签
  • 定义 struct 时,字段名后如果有 form 这个 tag,则会以把 form 表单里的 name 和 tag 的名称一样的字段赋值给这个字段,否则就会把 form 表单里与字段名一样的表单内容赋值给这个字段。如上面例子中,会把表单中的 username 和 age 分别赋值给 user 里的 Name 和 Age 字段,而 Email 里的内容则会赋给 Email 这个字段。
  • 调用 Controller ParseForm 这个方法的时候,传入的参数必须为一个 struct 的指针,否则对 struct 的赋值不会成功并返回 xx must be a struct pointer 的错误。
  • 如果要忽略一个字段,有两种办法,一是:字段名小写开头,二是:form 标签的值设置为 -

获取 Request Body 里的内容

在 API 的开发中,我们经常会用到 JSONXML 来作为数据交互的格式,如何在 beego 中获取 Request Body 里的 JSON 或 XML 的数据呢?

  1. 在配置文件里设置 copyrequestbody = true
  2. 在 Controller 中
func (this *ObjectController) Post() {
    var ob models.Object
    var err error
    if err = json.Unmarshal(this.Ctx.Input.RequestBody, &ob); err == nil {
        objectid := models.AddOne(ob)
        this.Data["json"] = "{\"ObjectId\":\"" + objectid + "\"}"
    } else {
        this.Data["json"] = err.Error()
    }
    this.ServeJSON()
}

文件上传

在 beego 中你可以很容易的处理文件上传,就是别忘记在你的 form 表单中增加这个属性 enctype="multipart/form-data",否则你的浏览器不会传输你的上传文件。

文件上传之后一般是放在系统的内存里面,如果文件的 size 大于设置的缓存内存大小,那么就放在临时文件中,默认的缓存内存是 64M,你可以通过如下来调整这个缓存内存大小:

web.MaxMemory = 1<<22

或者在配置文件中通过如下设置:

maxmemory = 1<<22

与此同时,beego 提供了另外一个参数,MaxUploadSize来限制最大上传文件大小——如果你一次长传多个文件,那么它限制的就是这些所有文件合并在一起的大小。

默认情况下,MaxMemory应该设置得比MaxUploadSize小,这种情况下两个参数合并在一起的效果则是:

  1. 如果文件大小小于MaxMemory,则直接在内存中处理;
  2. 如果文件大小介于MaxMemoryMaxUploadSize之间,那么比MaxMemory大的部分将会放在临时目录;
  3. 文件大小超出MaxUploadSize,直接拒绝请求,返回响应码 413

Beego 提供了两个很方便的方法来处理文件上传:

  • GetFile(key string) (multipart.File, *multipart.FileHeader, error)

    该方法主要用于用户读取表单中的文件名 the_file,然后返回相应的信息,用户根据这些变量来处理文件上传:过滤、保存文件等。

  • SaveToFile(fromfile, tofile string) error

    该方法是在 GetFile 的基础上实现了快速保存的功能
    fromfile 是提交时候的 html 表单中的 name

<form enctype="multipart/form-data" method="post">
    <input type="file" name="uploadname" />
    <input type="submit">
</form>

保存的代码例子如下:

func (c *FormController) Post() {
    f, h, err := c.GetFile("uploadname")
    if err != nil {
        log.Fatal("getfile err ", err)
    }
    defer f.Close()
    c.SaveToFile("uploadname", "static/upload/" + h.Filename) // 保存位置在 static/upload, 没有文件夹要先创建
    
}

数据绑定

支持从用户请求中直接数据 bind 到指定的对象,例如请求地址如下

?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie
var id int
this.Ctx.Input.Bind(&id, "id")  //id ==123

var isok bool
this.Ctx.Input.Bind(&isok, "isok")  //isok ==true

var ft float64
this.Ctx.Input.Bind(&ft, "ft")  //ft ==1.2

ol := make([]int, 0, 2)
this.Ctx.Input.Bind(&ol, "ol")  //ol ==[1 2]

ul := make([]string, 0, 2)
this.Ctx.Input.Bind(&ul, "ul")  //ul ==[str array]

user struct{Name}
this.Ctx.Input.Bind(&user, "user")  //user =={Name:"astaxie"}

XSRF过滤

跨站请求伪造

跨站请求伪造(Cross-site request forgery), 简称为 XSRF,是 Web 应用中常见的一个安全问题。前面的链接也详细讲述了 XSRF 攻击的实现方式。

当前防范 XSRF 的一种通用的方法,是对每一个用户都记录一个无法预知的 cookie 数据,然后要求所有提交的请求(POST/PUT/DELETE)中都必须带有这个 cookie 数据。如果此数据不匹配 ,那么这个请求就可能是被伪造的。

beego 有内建的 XSRF 的防范机制,要使用此机制,你需要在应用配置文件中加上 enablexsrf 设定:

enablexsrf = true
xsrfkey = 61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o
xsrfexpire = 3600

或者直接在 main 入口处这样设置:

web.EnableXSRF = true
web.XSRFKEY = "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o"
web.XSRFExpire = 3600  //过期时间,默认1小时

如果开启了 XSRF,那么 beego 的 Web 应用将对所有用户设置一个 _xsrf 的 cookie 值(默认过期 1 小时),如果 POST PUT DELET 请求中没有这个 cookie 值,那么这个请求会被直接拒绝。如果你开启了这个机制,那么在所有被提交的表单中,你都需要加上一个域来提供这个值。你可以通过在模板中使用 专门的函数 XSRFFormHTML() 来做到这一点:

过期时间上面我们设置了全局的过期时间 web.XSRFExpire,但是有些时候我们也可以在控制器中修改这个过期时间,专门针对某一类处理逻辑:

func (this *HomeController) Get(){
    this.XSRFExpire = 7200
    this.Data["xsrfdata"]=template.HTML(this.XSRFFormHTML())
}

在 Beego 2.x 里面有一个很大的不同,就是 Beego 2.x 的XSRF只支持 HTTPS 协议。

这是因为,在 2.x 的时候,我们给存储 XSRF token的 cookie 加上了 secure, http-only.
两个设置,所以只能通过 HTTPS 协议运作。

与此同时,你也无法通过 JS 获取到 XSRF token。

这个改进,一个很重要的原因是,在 1.x 的时候,缺乏这两个选项,会导致攻击者可以从 cookie 中拿到 XSRF token,导致 XSRF 失效。

支持controller 级别的屏蔽

XSRF 之前是全局设置的一个参数,如果设置了那么所有的 API 请求都会进行验证,但是有些时候API 逻辑是不需要进行验证的,因此现在支持在controller 级别设置屏蔽:

type AdminController struct{
    web.Controller
}

func (a *AdminController) Prepare() {
    a.EnableXSRF = false
}

过滤器

beego 支持自定义过滤中间件,例如安全验证,强制跳转等。

过滤器函数如下所示:

web.InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt)

InsertFilter 函数的三个必填参数,一个可选参数

  • pattern 路由规则,可以根据一定的规则进行路由,如果你全匹配可以用 *
  • position 执行 Filter 的地方,五个固定参数如下,分别表示不同的执行过程
    • BeforeStatic 静态地址之前
    • BeforeRouter 寻找路由之前
    • BeforeExec 找到路由之后,开始执行相应的 Controller 之前
    • AfterExec 执行完 Controller 逻辑之后执行的过滤器
    • FinishRouter 执行完逻辑之后执行的过滤器
  • filter filter 函数 type FilterFunc func(*context.Context)
  • opts
    1. web.WithReturnOnOutput: 设置 returnOnOutput 的值(默认 true), 如果在进行到此过滤之前已经有输出,是否不再继续执行此过滤器,默认设置为如果前面已有输出(参数为true),则不再执行此过滤器
    2. web.WithResetParams: 是否重置 filters 的参数,默认是 false,因为在 filters 的 pattern 和本身的路由的 pattern 冲突的时候,可以把 filters 的参数重置,这样可以保证在后续的逻辑中获取到正确的参数,例如设置了 /api/* 的 filter,同时又设置了 /api/docs/* 的 router,那么在访问 /api/docs/swagger/abc.js 的时候,在执行 filters 的时候设置 :splat 参数为 docs/swagger/abc.js,但是如果不清楚 filter 的这个路由参数,就会在执行路由逻辑的时候保持 docs/swagger/abc.js,如果设置了 true,就会重置 :splat 参数.
    3. web.WithCaseSensitive: 是否大小写敏感。

如下例子所示,验证用户是否已经登录,应用于全部的请求:

var FilterUser = func(ctx *context.Context) {
    _, ok := ctx.Input.Session("uid").(int)
    if !ok && ctx.Request.RequestURI != "/login" {
        ctx.Redirect(302, "/login")
    }
}

web.InsertFilter("/*", web.BeforeRouter, FilterUser)

这里需要特别注意使用 session 的 Filter 必须在 BeforeStatic 之后才能获取,因为 session 没有在这之前初始化。

还可以通过正则路由进行过滤,如果匹配参数就执行:

var FilterUser = func(ctx *context.Context) {
    _, ok := ctx.Input.Session("uid").(int)
    if !ok {
        ctx.Redirect(302, "/login")
    }
}
web.InsertFilter("/user/:id([0-9]+)", web.BeforeRouter, FilterUser)

过滤器实现路由

beego1.1.2 开始 Context.Input 中增加了 RunController 和 RunMethod, 这样我们就可以在执行路由查找之前,在 filter 中实现自己的路由规则.

如下示例实现了如何实现自己的路由规则:

var UrlManager = func(ctx *context.Context) {
    // 数据库读取全部的 url mapping 数据
    urlMapping := model.GetUrlMapping()
    for baseurl,rule:=range urlMapping {
        if baseurl == ctx.Request.RequestURI {
            ctx.Input.RunController = rule.controller
            ctx.Input.RunMethod = rule.method
            break
        }
    }
}

web.InsertFilter("/*", web.BeforeRouter, web.UrlManager)

Filter和FilterChain

在 v1.x 的设计中,Filter 并不能直接调用下一个 Filter。这导致了我们无法解决一个问题,即我们希望这个 Filter 能够在代码执行前后都执行一段逻辑。

例如,在考虑接入Opentracingprometheus的时候,我们就遇到了这种问题。

考虑到这是一个通用的场景,我们在已有 Filter 的基础上,支持了Filter-Chain设计模式。

type FilterChain func(next FilterFunc) FilterFunc

例如一个非常简单的例子:

package main

import (
    "github.com/beego/beego/v2/core/logs"
    "github.com/beego/beego/v2/server/web"
    "github.com/beego/beego/v2/server/web/context"
)

func main() {
    web.InsertFilterChain("/*", func(next web.FilterFunc) web.FilterFunc {
        return func(ctx *context.Context) {
            // do something
            logs.Info("hello")
            // don't forget this
            next(ctx)

            // do something
        }
    })
}

这个例子里面,我们只是输出了一句”hello”,就调用了下一个 Filter。

在执行完next(ctx)之后,实际上,如果后面的 Filter 没有中断整个流程,那么这时候OutPut对象已经被赋值了,意味着能够拿到响应码等数据。

Prometheus例子

package main

import (
    "time"

    "github.com/beego/beego/v2/server/web"
    "github.com/beego/beego/v2/server/web/filter/prometheus"
)

func main() {
    // we start admin service
    // Prometheus will fetch metrics data from admin service's port
    web.BConfig.Listen.EnableAdmin = true

    web.BConfig.AppName = "my app"

    ctrl := &MainController{}
    web.Router("/hello", ctrl, "get:Hello")
    fb := &prometheus.FilterChainBuilder{}
    web.InsertFilterChain("/*", fb.FilterChain)
    web.Run(":8080")
    // after you start the server
    // and GET http://localhost:8080/hello
    // access http://localhost:8088/metrics
    // you can see something looks like:
    // http_request_web_sum{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1002
    // http_request_web_count{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1
    // http_request_web_sum{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1004
    // http_request_web_count{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1
}

type MainController struct {
    web.Controller
}

func (ctrl *MainController) Hello() {
    time.Sleep(time.Second)
    ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world"))
}

别忘记了开启prometheus的端口。在你没有启动admin服务的时候,需要自己手动开启。

Opentracing例子

package main

import (
    "time"

    "github.com/beego/beego/v2/server/web"
    "github.com/beego/beego/v2/server/web/filter/opentracing"
)

func main() {
    // don't forget this to inject the opentracing API's implementation
    // opentracing2.SetGlobalTracer()

    web.BConfig.AppName = "my app"

    ctrl := &MainController{}
    web.Router("/hello", ctrl, "get:Hello")
    fb := &opentracing.FilterChainBuilder{}
    web.InsertFilterChain("/*", fb.FilterChain)
    web.Run(":8080")
    // after you start the server
}

type MainController struct {
    web.Controller
}

func (ctrl *MainController) Hello() {
    time.Sleep(time.Second)
    ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world"))
}

别忘了调用opentracing库的SetGlobalTracer方法,注入真正的opentracing API的实现。

session 控制

beego 内置了 session 模块,目前 session 模块支持的后端引擎包括 memory、cookie、file、mysql、redis、couchbase、memcache、postgres,用户也可以根据相应的 interface 实现自己的引擎。

beego 中使用 session 相当方便,只要在 main 入口函数中设置如下:

web.BConfig.WebConfig.Session.SessionOn = true

通过这种方式就可以开启 session,如何使用 session,请看下面的例子:

func (this *MainController) Get() {
    v := this.GetSession("asta")
    if v == nil {
        this.SetSession("asta", int(1))
        this.Data["num"] = 0
    } else {
        this.SetSession("asta", v.(int)+1)
        this.Data["num"] = v.(int)
    }
    this.TplName = "index.tpl"
}

session 有几个方便的方法:

  • SetSession(name string, value interface{})
  • GetSession(name string) interface{}
  • DelSession(name string)
  • SessionRegenerateID()
  • DestroySession()

session 操作主要有设置 session、获取 session、删除 session。

当然你可以通过下面的方式自己控制这些逻辑:

sess:=this.StartSession()
defer sess.SessionRelease()

sess 对象具有如下方法:

  • sess.Set()
  • sess.Get()
  • sess.Delete()
  • sess.SessionID()
  • sess.Flush()

但是我还是建议大家采用 SetSession、GetSession、DelSession 三个方法来操作,避免自己在操作的过程中资源没释放的问题。

关于 Session 模块使用中的一些参数设置:

  • web.BConfig.WebConfig.Session.SessionOn

    设置是否开启 Session,默认是 false,配置文件对应的参数名:sessionon。

  • web.BConfig.WebConfig.Session.SessionProvider

    设置 Session 的引擎,默认是 memory,目前支持还有 file、mysql、redis 等,配置文件对应的参数名:sessionprovider。

  • web.BConfig.WebConfig.Session.SessionName

    设置 cookies 的名字,Session 默认是保存在用户的浏览器 cookies 里面的,默认名是 beegosessionID,配置文件对应的参数名是:sessionname。

  • web.BConfig.WebConfig.Session.SessionGCMaxLifetime

    设置 Session 过期的时间,默认值是 3600 秒,配置文件对应的参数:sessiongcmaxlifetime。

  • web.BConfig.WebConfig.Session.SessionProviderConfig

    设置对应 file、mysql、redis 引擎的保存路径或者链接地址,默认值是空,配置文件对应的参数:sessionproviderconfig。

  • web.BConfig.WebConfig.Session.SessionHashFunc

    默认值为 sha1,采用 sha1 加密算法生产 sessionid

  • web.BConfig.WebConfig.Session.SessionCookieLifeTime

    设置 cookie 的过期时间,cookie 是用来存储保存在客户端的数据。

从 beego1.1.3 版本开始移除了第三方依赖库,也就是如果你想使用 mysql、redis、couchbase、memcache、postgres 这些引擎,那么你首先需要安装

go get -u github.com/beego/beego/v2/server/web/session/mysql

然后在你的 main 函数中引入该库, 和数据库的驱动引入是一样的:

import _ "github.com/beego/beego/v2/server/web/session/mysql"

当 SessionProvider 为 file SessionProviderConfig 是指保存文件的目录,如下所示:

web.BConfig.WebConfig.Session.SessionProvider="file"
web.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"

当 SessionProvider 为 mysql 时,SessionProviderConfig 是链接地址,采用 go-sql-driver,如下所示:

web.BConfig.WebConfig.Session.SessionProvider = "mysql"
web.BConfig.WebConfig.Session.SessionProviderConfig = "username:password@protocol(address)/dbname?param=value"

需要特别注意的是,在使用 mysql 存储 session 信息的时候,需要事先在 mysql 创建表,建表语句如下

CREATE TABLE `session` (
    `session_key` char(64) NOT NULL,
    `session_data` blob,
    `session_expiry` int(11) unsigned NOT NULL,
    PRIMARY KEY (`session_key`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

当 SessionProvider 为 redis 时,SessionProviderConfig 是 redis 的链接地址,采用了 redigo,如下所示:

web.BConfig.WebConfig.Session.SessionProvider = "redis"
web.BConfig.WebConfig.Session.SessionProviderConfig = "127.0.0.1:6379"

当 SessionProvider 为 memcache 时,SessionProviderConfig 是 memcache 的链接地址,采用了 memcache,如下所示:

web.BConfig.WebConfig.Session.SessionProvider = "memcache"
web.BConfig.WebConfig.Session.SessionProviderConfig = "127.0.0.1:7080"

当 SessionProvider 为 postgres 时,SessionProviderConfig 是 postgres 的链接地址,采用了 postgres,如下所示:

web.BConfig.WebConfig.Session.SessionProvider = "postgresql"
web.BConfig.WebConfig.Session.SessionProviderConfig = "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"

当 SessionProvider 为 couchbase 时,SessionProviderConfig 是 couchbase 的链接地址,采用了 couchbase,如下所示:

web.BConfig.WebConfig.Session.SessionProvider = "couchbase"
web.BConfig.WebConfig.Session.SessionProviderConfig = "http://bucketname:bucketpass@myserver:8091"

特别注意点

因为 session 内部采用了 gob 来注册存储的对象,例如 struct,所以如果你采用了非 memory 的引擎,请自己在 main.go 的 init 里面注册需要保存的这些结构体,不然会引起应用重启之后出现无法解析的错误

flash 数据

这个 flash 与 Adobe/Macromedia Flash 没有任何关系。它主要用于在两个逻辑间传递临时数据,flash 中存放的所有数据会在紧接着的下一个逻辑中调用后清除。一般用于传递提示和错误消息。它适合 Post/Redirect/Get 模式。下面看使用的例子:

// 显示设置信息
func (c *MainController) Get() {
    flash:=web.ReadFromRequest(&c.Controller)
    if n,ok:=flash.Data["notice"];ok{
        // 显示设置成功
        c.TplName = "set_success.html"
    }else if n,ok=flash.Data["error"];ok{
        // 显示错误
        c.TplName = "set_error.html"
    }else{
        // 不然默认显示设置页面
        c.Data["list"]=GetInfo()
        c.TplName = "setting_list.html"
    }
}

// 处理设置信息
func (c *MainController) Post() {
    flash:=web.NewFlash()
    setting:=Settings{}
    valid := Validation{}
    c.ParseForm(&setting)
    if b, err := valid.Valid(setting);err!=nil {
        flash.Error("Settings invalid!")
        flash.Store(&c.Controller)
        c.Redirect("/setting",302)
        return
    }else if b!=nil{
        flash.Error("validation err!")
        flash.Store(&c.Controller)
        c.Redirect("/setting",302)
        return
    }
    saveSetting(setting)
    flash.Notice("Settings saved!")
    flash.Store(&c.Controller)
    c.Redirect("/setting",302)
}

上面的代码执行的大概逻辑是这样的:

  1. Get 方法执行,因为没有 flash 数据,所以显示设置页面。
  2. 用户设置信息之后点击递交,执行 Post,然后初始化一个 flash,通过验证,验证出错或者验证不通过设置 flash 的错误,如果通过了就保存设置,然后设置 flash 成功设置的信息。
  3. 设置完成后跳转到 Get 请求。
  4. Get 请求获取到了 Flash 信息,然后执行相应的逻辑,如果出错显示出错的页面,如果成功显示成功的页面。

默认情况下 ReadFromRequest 函数已经实现了读取的数据赋值给 flash,所以在你的模板里面你可以这样读取数据:

{{.flash.error}}
{{.flash.warning}}
{{.flash.notice}}

flash 对象有三个级别的设置:

  • Notice 提示信息
  • Warning 警告信息
  • Error 错误信息

URL 构建

如果可以匹配 URL ,那么 beego 也可以生成 URL 吗?当然可以。 UrlFor() 函数就是用于构建指定函数的 URL 的。它把对应控制器和函数名结合的字符串作为第一个参数,其余参数对应 URL 中的变量。未知变量将添加到 URL 中作为查询参数

下面定义了一个相应的控制器

type TestController struct {
    web.Controller
}

func (this *TestController) Get() {
    this.Data["Username"] = "astaxie"
    this.Ctx.Output.Body([]byte("ok"))
}

func (this *TestController) List() {
    this.Ctx.Output.Body([]byte("i am list"))
}

func (this *TestController) Params() {
    this.Ctx.Output.Body([]byte(this.Ctx.Input.Params()["0"] + this.Ctx.Input.Params()["1"] + this.Ctx.Input.Params()["2"]))
}

func (this *TestController) Myext() {
    this.Ctx.Output.Body([]byte(this.Ctx.Input.Param(":ext")))
}

func (this *TestController) GetUrl() {
    this.Ctx.Output.Body([]byte(this.UrlFor(".Myext")))
}

下面是我们注册的路由:

web.Router("/api/list", &TestController{}, "*:List")
web.Router("/person/:last/:first", &TestController{})
web.AutoRouter(&TestController{})

那么通过方式可以获取相应的URL地址:

web.URLFor("TestController.List")
// 输出 /api/list

web.URLFor("TestController.Get", ":last", "xie", ":first", "asta")
// 输出 /person/xie/asta

web.URLFor("TestController.Myext")
// 输出 /Test/Myext

web.URLFor("TestController.GetUrl")
// 输出 /Test/GetUrl

模板中如何使用

默认情况下,beego 已经注册了 urlfor 函数,用户可以通过如下的代码进行调用

{{urlfor "TestController.List"}}

为什么不在把 URL 写死在模板中,反而要动态构建?有两个很好的理由:

  1. 反向解析通常比硬编码 URL 更直观。同时,更重要的是你可以只在一个地方改变 URL ,而不用到处乱找。
  2. URL 创建会为你处理特殊字符的转义和 Unicode 数据,不用你操心。

多种格式数据输出

JSON、XML、JSONP、YAML

beego 当初设计的时候就考虑了 API 功能的设计,而我们在设计 API 的时候经常是输出 JSON 或者 XML 数据,那么 beego 提供了这样的方式直接输出:

注意 struct 属性应该 为 exported Identifier
首字母应该大写

  • JSON 数据直接输出:

    func (this *AddController) Get() {
        mystruct := { ... }
        this.Data["json"] = &mystruct
        this.ServeJSON()
    }
    

    调用 ServeJSON 之后,会设置 content-typeapplication/json,然后同时把数据进行 JSON 序列化输出。

  • XML 数据直接输出:

    func (this *AddController) Get() {
        mystruct := { ... }
        this.Data["xml"]=&mystruct
        this.ServeXML()
    }
    

    调用 ServeXML 之后,会设置 content-typeapplication/xml,同时数据会进行 XML 序列化输出。

  • jsonp 调用

    func (this *AddController) Get() {
        mystruct := { ... }
        this.Data["jsonp"] = &mystruct
        this.ServeJSONP()
    }
    

    调用 ServeJSONP 之后,会设置 content-typeapplication/javascript,然后同时把数据进行 JSON 序列化,然后根据请求的 callback 参数设置 jsonp 输出。

开发模式下序列化后输出的是格式化易阅读的 JSON 或 XML 字符串;在生产模式下序列化后输出的是压缩的字符串。

表单数据认证

安装及测试

安装:

go get github.com/beego/beego/v2/core/validation

测试:

go test github.com/beego/beego/v2/core/validation

示例

直接使用示例:

import (
    "github.com/beego/beego/v2/core/validation"
    "log"
)

type User struct {
    Name string
    Age int
}

func main() {
    u := User{"man", 40}
    valid := validation.Validation{}
    valid.Required(u.Name, "name")
    valid.MaxSize(u.Name, 15, "nameMax")
    valid.Range(u.Age, 0, 18, "age")

    if valid.HasErrors() {
        // 如果有错误信息,证明验证没通过
        // 打印错误信息
        for _, err := range valid.Errors {
            log.Println(err.Key, err.Message)
        }
    }
    // or use like this
    if v := valid.Max(u.Age, 140, "age"); !v.Ok {
        log.Println(v.Error.Key, v.Error.Message)
    }
    // 定制错误信息
    minAge := 18
    valid.Min(u.Age, minAge, "age").Message("少儿不宜!")
    // 错误信息格式化
    valid.Min(u.Age, minAge, "age").Message("%d不禁", minAge)
}

通过 StructTag 使用示例:

import (
    "log"
    "strings"

    "github.com/beego/beego/v2/core/validation"
)

// 验证函数写在 "valid" tag 的标签里
// 各个函数之间用分号 ";" 分隔,分号后面可以有空格
// 参数用括号 "()" 括起来,多个参数之间用逗号 "," 分开,逗号后面可以有空格
// 正则函数(Match)的匹配模式用两斜杠 "/" 括起来
// 各个函数的结果的 key 值为字段名.验证函数名
type user struct {
    Id     int
    Name   string `valid:"Required;Match(/^Bee.*/)"` // Name 不能为空并且以 Bee 开头
    Age    int    `valid:"Range(1, 140)"` // 1 <= Age <= 140,超出此范围即为不合法
    Email  string `valid:"Email; MaxSize(100)"` // Email 字段需要符合邮箱格式,并且最大长度不能大于 100 个字符
    Mobile string `valid:"Mobile"` // Mobile 必须为正确的手机号
    IP     string `valid:"IP"` // IP 必须为一个正确的 IPv4 地址
}

// 如果你的 struct 实现了接口 validation.ValidFormer
// 当 StructTag 中的测试都成功时,将会执行 Valid 函数进行自定义验证
func (u *user) Valid(v *validation.Validation) {
    if strings.Index(u.Name, "admin") != -1 {
        // 通过 SetError 设置 Name 的错误信息,HasErrors 将会返回 true
        v.SetError("Name", "名称里不能含有 admin")
    }
}

func main() {
    valid := validation.Validation{}
    u := user{Name: "Beego", Age: 2, Email: "dev@web.me"}
    b, err := valid.Valid(&u)
    if err != nil {
        // handle error
    }
    if !b {
        // validation does not pass
        // blabla...
        for _, err := range valid.Errors {
            log.Println(err.Key, err.Message)
        }
    }
}

StructTag 可用的验证函数:

  • Required 不为空,即各个类型要求不为其零值
  • Min(min int) 最小值,有效类型:int,其他类型都将不能通过验证
  • Max(max int) 最大值,有效类型:int,其他类型都将不能通过验证
  • Range(min, max int) 数值的范围,有效类型:int,他类型都将不能通过验证
  • MinSize(min int) 最小长度,有效类型:string slice,其他类型都将不能通过验证
  • MaxSize(max int) 最大长度,有效类型:string slice,其他类型都将不能通过验证
  • Length(length int) 指定长度,有效类型:string slice,其他类型都将不能通过验证
  • Alpha alpha字符,有效类型:string,其他类型都将不能通过验证
  • Numeric 数字,有效类型:string,其他类型都将不能通过验证
  • AlphaNumeric alpha 字符或数字,有效类型:string,其他类型都将不能通过验证
  • Match(pattern string) 正则匹配,有效类型:string,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match)
  • AlphaDash alpha 字符或数字或横杠 -_,有效类型:string,其他类型都将不能通过验证
  • Email 邮箱格式,有效类型:string,其他类型都将不能通过验证
  • IP IP 格式,目前只支持 IPv4 格式验证,有效类型:string,其他类型都将不能通过验证
  • Base64 base64 编码,有效类型:string,其他类型都将不能通过验证
  • Mobile 手机号,有效类型:string,其他类型都将不能通过验证
  • Tel 固定电话号,有效类型:string,其他类型都将不能通过验证
  • Phone 手机号或固定电话号,有效类型:string,其他类型都将不能通过验证
  • ZipCode 邮政编码,有效类型:string,其他类型都将不能通过验证

自定义验证

我们允许自己注册验证逻辑。使用方法:

AddCustomFunc(name string, f CustomFunc) error

注意的是,该方法并不是线程安全的。注册这种自定义的方法,应该在系统初始化阶段完成。在该阶段,应当不存在竞争问题。

错误处理

我们在做 Web 开发的时候,经常需要页面跳转和错误处理,beego 这方面也进行了考虑,通过 Redirect 方法来进行跳转:

func (this *AddController) Get() {
    this.Redirect("/", 302)
}

如何中止此次请求并抛出异常,beego 可以在控制器中这样操作:

func (this *MainController) Get() {
    this.Abort("401")
    v := this.GetSession("asta")
    if v == nil {
        this.SetSession("asta", int(1))
        this.Data["Email"] = 0
    } else {
        this.SetSession("asta", v.(int)+1)
        this.Data["Email"] = v.(int)
    }
    this.TplName = "index.tpl"
}

这样 this.Abort("401") 之后的代码不会再执行,而且会默认显示给用户如下页面:

img

web 框架默认支持 401、403、404、500、503 这几种错误的处理。用户可以自定义相应的错误处理,例如下面重新定义 404 页面:

func page_not_found(rw http.ResponseWriter, r *http.Request){
    t,_:= template.New("404.html").ParseFiles(web.BConfig.WebConfig.ViewsPath+"/404.html")
    data :=make(map[string]interface{})
    data["content"] = "page not found"
    t.Execute(rw, data)
}

func main() {
    web.ErrorHandler("404",page_not_found)
    web.Router("/", &controllers.MainController{})
    web.Run()
}

我们可以通过自定义错误页面 404.html 来处理 404 错误。

beego 更加人性化的还有一个设计就是支持用户自定义字符串错误类型处理函数,例如下面的代码,用户注册了一个数据库出错的处理页面:

func dbError(rw http.ResponseWriter, r *http.Request){
    t,_:= template.New("dberror.html").ParseFiles(web.BConfig.WebConfig.ViewsPath+"/dberror.html")
    data :=make(map[string]interface{})
    data["content"] = "database is now down"
    t.Execute(rw, data)
}

func main() {
    web.ErrorHandler("dbError",dbError)
    web.Router("/", &controllers.MainController{})
    web.Run()
}

一旦在入口注册该错误处理代码,那么你可以在任何你的逻辑中遇到数据库错误调用 this.Abort("dbError") 来进行异常页面处理。

Controller 定义 Error

从 1.4.3 版本开始,支持 Controller 方式定义 Error 错误处理函数,这样就可以充分利用系统自带的模板处理,以及 context 等方法。

package controllers

import (
    "github.com/beego/beego/v2/server/web"
)

type ErrorController struct {
    web.Controller
}

func (c *ErrorController) Error404() {
    c.Data["content"] = "page not found"
    c.TplName = "404.tpl"
}

func (c *ErrorController) Error501() {
    c.Data["content"] = "server error"
    c.TplName = "501.tpl"
}


func (c *ErrorController) ErrorDb() {
    c.Data["content"] = "database is now down"
    c.TplName = "dberror.tpl"
}

通过上面的例子我们可以看到,所有的函数都是有一定规律的,都是 Error 开头,后面的名字就是我们调用 Abort 的名字,例如 Error404 函数其实调用对应的就是 Abort("404")

我们就只要在 web.Run 之前采用 web.ErrorController 注册这个错误处理函数就可以了

package main

import (
    _ "btest/routers"
    "btest/controllers"

    "github.com/beego/beego/v2/server/web"
)

func main() {
    web.ErrorController(&controllers.ErrorController{})
    web.Run()
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
控制器(Controller):Controller是应用程序的一部分,负责接受并处理来自用户的请求,并将其转发给适当的服务。它通常是与用户界面交互的组件,例如网页或移动应用程序中的按钮、表单等。Controller负责处理用户的输入,并决定如何响应这些输入,可以从数据库、外部API等获取数据,并将结果返回给用户。 服务(Service):服务是应用程序的核心,负责处理各种业务逻辑。它通常是Controller和数据访问之间的桥梁。服务负责对用户请求进行处理、封装和组织,协调业务逻辑的执行。它可以调用其他的服务、库或外部API,以完成复杂的业务操作,并返回处理结果给Controller。 数据映射器(Mapper):数据映射器是将数据从一种形式映射到另一种形式的组件。它位于服务和数据访问之间。数据映射器负责将服务的数据对象转换为数据访问的实体对象,或将数据访问的实体对象转换为服务的数据对象。它的目的是将不同之间的数据表示进行解耦,使它们可以独立地进行演变和修改。 在应用程序中使用这架构可以带来很多好处。Controller负责用户界面和用户交互的处理,使代码逻辑更加清晰,易于理解和维护。Service负责处理业务逻辑,使得业务逻辑更加集中和可复用。Mapper则负责数据的转换和解耦,使得不同之间的数据操作更加灵活和可扩展。 总之,Controller、Service和Mapper是应用程序的个重要组成部分,它们各自承担着不同的责任和功能,通过合理地组织和划分任务,可以提高代码的可读性、可维护性和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值