安装
|
|
bee 工具是一个为了协助快速开发 beego 项目而创建的项目,通过 bee 您可以很容易的进行 beego 项目的创建、热编译、开发、测试、和部署。
如果在下载过程中发现很慢,那应该是GOPROXY使用的默认值,下载速度会很慢。可以设置使用以下加速的代理:
国内七牛云代理
|
|
全球CDN加速
|
|
选择一个就行,下载速度的问题就解决了。当下载完之后,bee的bee.exe便会生成在go的bin目录下和beego框架的源码就被下载到设置了的POPATH执行的目录。如果GOPATH和go的src目录不一致,就把下载好的复制到go的src目录,编写程序时就可以引用了。
bee工具的命令
|
|
bee new创建项目
使用bee工具创建一个web项目,bee new是新建一个web项目。后面接的是想要命名的项目名称。E:\Go\go_project是我自己电脑的GOPATH路径。可见bee新建的项目是存放在GOPATH目录下。
|
|
bee新建的web项目的结构
|
|
bee run运行项目
|
|
然后再去访问http://localhost:8080。就可以看到beego的欢迎页了。
如果报错:missing dot in first path element。在项目下执行:go mod init。再去运行项目
beego快速入门
项目路由设置
main.go是整个项目的入口,那么就从main.go开始看
|
|
main函数是入口函数,go的执行流程又如下图所示:
在通过bee生成的main.go
文件中,导入了routers
包,在routers
包下有一个router.go
文件
|
|
init()
函数中注册了一个路由:/
。路由包里面我们看到执行了路由注册 beego.Router
, 这个函数的功能是映射 URL 到 controller,第一个参数是 URL (用户请求的地址),这里我们注册的是 /
,也就是我们访问的不带任何参数的 URL,第二个参数是对应的 Controller,也就是我们即将把请求分发到那个控制器来执行相应的逻辑,我们可以执行类似的方式注册如下路由:
|
|
这样用户就可以通过访问 /index
去执行 IndexController
的逻辑。这就是我们所谓的路由,后面会介绍更多更复杂的路由规则。
再回来看看 main 函数里面的 beego.Run
, beego.Run
执行之后,我们看到的效果好像只是监听服务端口这个过程,但是它内部做了很多事情:
-
解析配置文件
beego 会自动解析在 conf 目录下面的配置文件
app.conf
,通过修改配置文件相关的属性,我们可以定义:开启的端口,是否开启 session,应用名称等信息。 -
执行用户的 hookfunc
beego 会执行用户注册的 hookfunc,默认的已经存在了注册 mime,用户可以通过函数
AddAPPStartHook
注册自己的启动函数。 -
是否开启 session
会根据上面配置文件的分析之后判断是否开启 session,如果开启的话就初始化全局的 session。
-
是否编译模板
beego 会在启动的时候根据配置把 views 目录下的所有模板进行预编译,然后存在 map 里面,这样可以有效的提高模板运行的效率,无需进行多次编译。
-
是否开启文档功能
根据 EnableDocs 配置判断是否开启内置的文档路由功能
-
是否启动管理模块
beego 目前做了一个很酷的模块,应用内监控模块,会在 8088 端口做一个内部监听,我们可以通过这个端口查询到 QPS、CPU、内存、GC、goroutine、thread 等统计信息。
-
监听服务端口
这是最后一步也就是我们看到的访问 8080 看到的网页端口,内部其实调用了
ListenAndServe
,充分利用了 goroutine 的优势
一旦 run 起来之后,我们的服务就监听在两个端口了,一个服务端口 8080 作为对外服务,另一个 8088 端口实行对内监控。
通过这个代码的分析我们了解了 beego 运行起来的过程,以及内部的一些机制。接下来让我们去剥离 Controller 如何来处理逻辑的。
controller控制器执行逻辑
在router.go
中又引入了controllers
包,下面有一个default.go
文件
|
|
上面的代码显示首先声明了一个控制器 MainController
,这个控制器里面内嵌了 beego.Controller
,这就是 Go 的嵌入方式,也就是 MainController
自动拥有了所有 beego.Controller
的方法。
而 beego.Controller
拥有很多方法,其中包括 Init
、Prepare
、Post
、Get
、Delete
、Head
等方法。也可以通过重写的方式来实现这些方法,上面的代码就是重写了 Get
方法。
beego 是一个 RESTful 的框架,所以的请求默认是执行对应 req.Method
的方法。例如浏览器的是 GET
请求,那么默认就会执行 MainController
下的 Get
方法。这样上面的 Get 方法就会被执行到,这样就进入了逻辑处理。(后续将介绍自定义路由规则)里面的代码是需要执行的逻辑,这里只是简单的输出数据,可以通过各种方式获取数据,然后赋值到 this.Data
中,这是一个用来存储输出数据的 map,可以赋值任意类型的值,这里只是简单举例输出两个字符串。
最后一个就是需要去渲染的模板,this.TplName
就是需要渲染的模板,这里指定了 index.tpl
,如果用户不设置该参数,那么默认会去到模板目录的 Controller/<方法名>.tpl
查找,例如上面的方法会去 maincontroller/get.tpl
(文件、文件夹必须小写。beego框架规定的)。
用户设置了模板之后系统会自动的调用 Render
函数(这个函数是在 beego.Controller 中实现的),所以无需用户自己来调用渲染。
当然也可以不使用模版,直接用 this.Ctx.WriteString
输出字符串,如:
|
|
至此控制器分析基本完成了,接下来看一看如何来编写 model。
model分析
Web 应用中用的最多的就是数据库操作,而 model 层一般用来做这些操作, bee new
例子不存在 Model 的演示,但是 bee api
应用中存在 model 的应用。其实如果一个应用足够简单,那么 Controller 可以处理一切的逻辑,如果逻辑里面存在着可以复用的东西,那么就抽取出来变成一个模块。因此 Model 就是逐步抽象的过程,一般会在 Model 里面处理一些数据读取,如下是一个日志分析应用中的代码片段:
|
|
所以应用足够简单,那么就不需要 Model 了;如果模块开始多了,需要复用,需要逻辑分离了,那么 Model 是必不可少的。接下来分析如何编写 View 层的东西。
View层的编写
在前面编写 Controller 的时候,在MainController中的 Get方法 里面写过这样的语句 this.TplName = "index.tpl"
,设置显示的模板文件,默认支持 tpl
和 html
的后缀名,如果想设置其他后缀可以调用 beego.AddTemplateExt
接口设置,那么模板如何来显示相应的数据呢?beego 采用了 Go 语言默认的模板引擎,所以和 Go 的模板语法一样,Go 模板的详细使用方法请参考《Go Web 编程》模板使用指南
在快速入门中beegodemo中创建的index.tpl里面的代码(去掉了 css 样式):
|
|
在 Controller 里面把数据赋值给了 data(map 类型),然后我们在模板中就直接通过 key 访问 .Website
和 .Email
。这样就做到了数据的输出。接下来讲解如何让静态文件输出。
静态文件处理
前面介绍了如何输出静态页面,但是一个网页往往包含了很多的静态文件,包括图片、JS、CSS 等,bee工具创建的应用里面就创建了如下目录:
|
|
beego 默认注册了 static 目录为静态处理的目录,注册样式:URL 前缀和映射的目录(在/main.go文件中beego.Run()之前加入):
|
|
用户可以设置多个静态文件处理目录,例如你有多个文件下载目录 download1、download2,你可以这样映射(在 /main.go 文件中 beego.Run() 之前加入)。
例如:在static文件夹下新建download1和download2文件夹,并分别在这两个文件夹下创建文件123.txt和345.txt。里面的内容分别是"123"和345,在代码中注册静态文件夹和匹配的路径
|
|
这样访问 URL http://localhost:8080/down1/123.txt
则会请求 download1 目录下的 123.txt 文件。在浏览器上就会显示文件里面的内容。
beego的MVC架构设计
执行逻辑图
执行流程分析
- 在监听的端口接收数据,默认监听在 8080 端口。
- 用户请求到达 8080 端口之后进入 beego 的处理逻辑。
- 初始化 Context 对象,根据请求判断是否为 WebSocket 请求,如果是的话设置 Input,同时判断请求的方法是否在标准请求方法中(GET、POST、PUT、DELETE、PATCH、OPTIONS、HEAD),防止用户的恶意伪造请求攻击造成不必要的影响。
- 执行 BeforeRouter 过滤器,当然在 beego 里面有开关设置。如果用户设置了过滤器,那么该开关打开,这样可以提高在没有开启过滤器的情况下提高执行效率。如果在执行过滤器过程中,responseWriter已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
- 开始执行静态文件的处理,查看用户的请求 URL 是否和注册在静态文件处理 StaticDir 中的 prefix 是否匹配。如果匹配的话,采用
http
包中默认的 ServeFile 来处理静态文件。 - 如果不是静态文件开始初始化 session 模块(如果开启 session 的话),这个里面大家需要注意,如果你的 BeforeRouter 过滤器用到了 session 就会报错,你应该把它加入到 AfterStatic 过滤器中。
- 开始执行 AfterStatic 过滤器,如果在执行过滤器过程中,responseWriter 已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
- 执行过过滤器之后,开始从固定的路由规则中查找和请求 URL 相匹配的对象。这个匹配是全匹配规则,即如果用户请求的 URL 是
/hello/world
,那么固定规则中/hello
是不会匹配的,只有完全匹配才算匹配。如果匹配的话就进入逻辑执行,如果不匹配进入下一环节的正则匹配。 - 正则匹配是进行正则的全匹配,这个正则是按照用户添加 beego 路由顺序来进行匹配的,也就是说,如果你在添加路由的时候你的顺序影响你的匹配。和固定匹配一样,如果匹配的话就进行逻辑执行,如果不匹配进入 Auto 匹配。
- 如果用户注册了 AutoRouter,那么会通过
controller/method
这样的方式去查找对应的 Controller 和他内置的方法,如果找到就开始执行逻辑,如果找不到就跳转到监控判断。 - 如果找到 Controller 的话,那么就开始执行逻辑,首先执行 BeforeExec 过滤器,如果在执行过滤器过程中,responseWriter 已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
- Controller 开始执行 Init 函数,初始化基本的一些信息,这个函数一般都是 beego.Controller 的初始化,不建议用户继承的时候修改该函数。
- 是否开启了 XSRF,开启的话就调用 Controller 的 XsrfToken,然后如果是 POST 请求就调用 CheckXsrfCookie 方法。
- 继续执行 Controller 的 Prepare 函数,这个函数一般是预留给用户的,用来做 Controller 里面的一些参数初始化之类的工作。如果在初始化中 responseWriter 有输出,那么就直接进入 Finish 函数逻辑。
- 如果没有输出的话,那么根据用户注册的方法执行相应的逻辑,如果用户没有注册,那么就调用 http.Method 对应的方法(Get/Post 等)。执行相应的逻辑,例如数据读取,数据赋值,模板显示之类的,或者直接输出 JSON 或者 XML。
- 如果 responseWriter 没有输出,那么就调用 Render 函数进行模板输出。
- 执行 Controller 的 Finish 函数,这个函数是预留给用户用来重写的,用于释放一些资源。释放在 Init 中初始化的信息数据。
- 执行 AfterExec 过滤器,如果有输出的话就跳转到监控判断逻辑。
- 执行 Controller 的 Destructor,用于释放 Init 中初始化的一些数据。
- 如果这一路执行下来都没有找到路由,那么会调用 404 显示找不到该页面。
- 最后所有的逻辑都汇聚到了监控判断,如果用户开启了监控模块(默认是开启一个 8088 端口用于进程内监控),这样就会把访问的请求链接扔给监控程序去记录当前访问的 QPS,对应的链接访问的执行时间,请求链接等。
接下来就让我们开始进入 beego 的 MVC 核心第一步,路由设置:
路由设置
beego 存在三种方式的路由:固定路由、正则路由、自动路由,接下来详细的讲解如何使用这三种路由。
基础路由
从 beego 1.2 版本开始支持了基本的 RESTful 函数式路由,应用中的大多数路由都会定义在 routers/router.go
文件中。最简单的 beego 路由由 URI 和闭包函数组成。
-
基础路由Get,Post。通过postman用对应的HTTP请求去访问:http://localhost:8080/basic/get,http://localhost:8080/basic/get
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package routers import ( "github.com/astaxie/beego" "github.com/astaxie/beego/context" ) func init() { // 1.基础路由 beego.Get("/basic/get",func(ctx *context.Context){ ctx.Output.Body([]byte("basic router get")) }) beego.Post("/basic/post",func(ctx *context.Context){ ctx.Output.Body([]byte("basic router post")) }) }
-
注册一个可以响应任何 HTTP 的路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package routers import ( "github.com/astaxie/beego" "github.com/astaxie/beego/context" ) func init() { // 1.基础路由 beego.Get("/basic/get",func(ctx *context.Context){ ctx.Output.Body([]byte("basic router get")) }) beego.Post("/basic/post",func(ctx *context.Context){ ctx.Output.Body([]byte("basic router post")) }) // 2.注册一个可以响应任意HTTP请求的路由 beego.Any("/basic/any",func(ctx *context.Context){ ctx.Output.Body([]byte("basic router any")) }) }
-
beego所支持的基础函数如下
- beego.Get(router, beego.FilterFunc)
- beego.Post(router, beego.FilterFunc)
- beego.Put(router, beego.FilterFunc)
- beego.Patch(router, beego.FilterFunc)
- beego.Head(router, beego.FilterFunc)
- beego.Options(router, beego.FilterFunc)
- beego.Delete(router, beego.FilterFunc)
- beego.Any(router, beego.FilterFunc)
-
自定义handler
有些时候项目中已经实现了一些 rpc 的应用,但是想要集成到 beego 中,或者其他的 httpserver 应用,集成到 beego 中来.现在可以很方便的集成
1 2 3 4
s := rpc.NewServer() s.RegisterCodec(json.NewCodec(), "application/json") s.RegisterService(new(HelloService), "") beego.Handler("/rpc", s)
beego.Handler(router, http.Handler)
这个函数是关键,第一个参数表示路由 URI, 第二个就是你自己实现的http.Handler
, 注册之后就会把所有 rpc 作为前缀的请求分发到http.Handler
中进行处理.这个函数其实还有第三个参数就是是否是前缀匹配,默认是 false, 如果设置了 true, 那么就会在路由匹配的时候前缀匹配,即
/rpc/user
这样的也会匹配去运行 -
路由参数
在项目开发中通常是需要在请求中获取对应的参数,后面的固定路由,正则路由,这些参数一样适用于上面的这些函数
RESTful Controller 路由
RESTful 是一种目前 API 开发中广泛采用的形式,beego 默认就是支持这样的请求方法,也就是用户 Get 请求就执行 Get 方法,Post 请求就执行 Post 方法。因此默认的路由是这样 RESTful 的请求方式。
RESTful风格的固定路由
固定路由也就是全匹配的路由.
在controllers
包下创建UserController、ArticleController和AddController.
UserController.go
|
|
ArticleController.go
|
|
AddController.go
|
|
编写完之后去路由进行注册,如下面所示的路由就是我们最常用的路由方式,一个固定的路由,一个控制器,然后根据用户请求方法不同请求控制器中对应的方法,典型的 RESTful 方式。
|
|
正则路由
为了用户更加方便的路由设置,beego 参考了 sinatra 的路由实现,支持多种方式的路由:
-
beego.Router("/api/?:id", &controllers.RController{})
默认匹配 //例如对于URL"/api/123"可以匹配成功,此时变量":id"值为"123"
-
beego.Router("/api/:id", &controllers.RController{})
默认匹配 //例如对于URL"/api/123"可以匹配成功,此时变量":id"值为"123",但URL"/api/“匹配失败
-
beego.Router("/api/:id([0-9]+)" &controllers.RController{})
自定义正则匹配 //例如对于URL"/api/123"可以匹配成功,此时变量":id"值为"123"
-
beego.Router("/user/:username([\w]+)", &controllers.RController{})
正则字符串匹配 //例如对于URL"/user/astaxie"可以匹配成功,此时变量":username"值为"astaxie"
-
beego.Router("/download/.", &controllers.RController{})
*匹配方式 //例如对于URL"/download/file/api.xml"可以匹配成功,此时变量":path"值为"file/api", “:ext"值为"xml"
-
beego.Router("/download/ceshi/*", &controllers.RController{})
*全匹配方式 //例如对于URL"/download/ceshi/file/api.json"可以匹配成功,此时变量":splat"值为"file/api.json"
-
beego.Router("/:id :int", &controllers.RController{})
int 类型设置方式,匹配 :id为int 类型,框架帮你实现了正则 ([0-9]+)
-
beego.Router("/:hi:string", &controllers.RController{})
string 类型设置方式,匹配 :hi 为 string 类型。框架帮你实现了正则 ([\w]+)
-
beego.Router("/cms_:id([0-9]+).html", &controllers.CmsController{})
带有前缀的自定义正则 //匹配 :id 为正则类型。匹配 cms_123.html 这样的 url :id = 123
现在在controllers
包下创建RController
|
|
再router.go
中注册正则路由
|
|
postman访问http://localhost:8080/api/123,会返回"path parma123"。说明路由匹配成功。当访问http://localhost:8080/api的时候路由也会匹配成功,页面返回了"path param",就是参数为空。
现在把路由的匹配换成一下进行注册,然后再分别请求访问http://localhost:8080/api/123和http://localhost:8080/api。前者会匹配成功,页面返回了"path param123"。后者会出现404页面,路由匹配失败。
|
|
“/api/:id([0-9]+)“和”/user/:username([\w]+)",其实从正则表达式就可以看得出来,前者是只匹配数字,后者只匹配字符。修改UserController
|
|
注册路由
|
|
访问测试http://localhost:8080/api/123和http://localhost:8080/api/kkk。前者会匹配成功,页面返回"path param id:123” ,后者访问会404。http://localhost:8080/user/123和http://localhost:8080/user/kkk。这两个路径都会成功匹配并路由到UserController的Get方法。