01 . Go框架之Beego简介部署及程序流程分析
Beego简介
beego是一个使用Go语言来开发WEB引用的GoWeb框架,该框架起始于2012年,由一位中国的程序员编写并进行公开,其目的就是为大家提供一个高效率的web应用开发框架。该框架采用模块封装,使用简单,容易学习。方便技术开发者快速学习并进行实际开发。对程序员来说,beego掌握起来非常简单,只需要关注业务逻辑实现即可,框架自动为项目需求提供不同的模块功能。
在对beego框架有了一个简单基本的了解之后,我们给大家介绍一下beego的一些特性。
Beego框架的主要特性
1)简单化:RESTful支持,MVC模型;可以使用bee工具来提高开发效率,比如监控代码修改进行热编译,自动化测试代码,以及自动化打包部署等丰富的开发调试功能。
2)智能化:beego框架封装了路由模块,支持智能路由,智能监控,并可以监控内存消耗,CPU使用以及goroutine的运行状况,方便开发者对线上应用进行监控分析。
3)模块化:beego根据功能对代码进行节耦封装,形成了Session,Cache,Log,配置解析,性能监控,上下文操作,ORM等独立的模块,方便开发者进行使用。
4)高性能:beego采用Go原生的http请求,goroutine的并发效率应付大流量的Web应用和API应用。
beego的几个特性
一方面在面试过程中,如果有面试官问起大家关于goweb开发的相关知识的时候,有可能问beego框架有优势或者有什么特点,这个时候需要大家能够对beego框架这些特点做一个介绍;
另一方面,咱们在后面实际的学习和编码过程中,大家感受一下这些特性在实际项目中为我们带来的便利和高效。
beego安装
我们使用go get命令来进行beego的安装。
注意
在安装的时候,有一点大家要注意,beego框架要求Go 语言版本1.1+以上,这个我们大家的版本应该至少也是1.9的吧,应该都没有。或者大家现在在命令行中,执行go version查看一下自己的go语言版本,如下所示:
添加环境变量
cat .bash_profile
export GOROOT=/usr/local/go
export GOARCH=amd64
export GOOS=darwin
export GOPATH=/Users/youmen/Go_wunai
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOPATH/bin
安装beego
export GO111MODULE=on
export GOPROXY=https://goproxy.cn
ulimit -n 5000
go get -u github.com/astaxie/beego
go get -u github.com/beego/bee
bee new beego
cd ${GOPATH}/src
// cd进入项目目录
// 输入go mod init
bee run
// 如果想编译成linux可以直接执行的二进制文件
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build hello.go
命令行工具Bee
现在,尝试来进行对代码进行修改,就简单的添加一行打印日志:beego.Info("代码修改")。这个时候,如果我们要想让代码生效,必须把原来正在执行的程序停止,然后重新执行编码和运行命令,我们来实际尝试一下。
经过我们的尝试,我们可以发现,确实第二次的重新启动的程序打印出了两条后台日志,这说明我们修改的代码生效了。
bee简介
bee是一个开发工具,是协助Beego框架开发项目时进行创建项目,运行项目,热部署等相关的项目管理的工具。beego是源码,负责开发,bee是工具,负责构建和管理项目。介绍beego的特性的时候说过其中一点beego支持代码热部署吧。所谓热部署就是,当修改代码的时候,可以不用停止服务重新启动,内置的beego就能够实时感知源代码程序编码,并进行实时生效,这就是热部署,这样就大大方便了开发人员和维护人员要频繁的进行服务停止,服务重启的过程。
Bee安装
如同beego一样,如果要使用bee,首先要进行安装,同样的道理,先使用go get命令安装bee工具。bee安装命令:
go get github.com/beego/bee
Bee功能命令
安装结束以后,进入到GOPATH对应的目录中,然后进入到bin目录下,可以看到有bee这个可执行文件,这就是安装的bee工具的可执行文件,在该目录中,我们在终端中执行以下bee命令,然后回车,会发现列出很多关于bee的用法,如下图:
new命令
// bee new ProjectName
该命令表示新建一个全新的web项目,有一点需要注意:该命令必须在src目录下执行,才能生效,自动生成web项目的目录结构。如果在其他目录下面执行bee new命令,也同样会是在src目录下面生成对应新项目的目录结构,这是bee工具在构建项目的时候默认寻找创建的目录。
api命令
// bee api ProjectNames
该命令表示用来创建开发API应用。很多用户写前端的后台接口也是通过go来写,因此bee专门提供了一个写api接口应用的命令。通过目录结构可以看到,和Web项目相比,API项目少了static和views目录,多了一个test目录,test是用来进行写测试用例代码的。
run命令
// bee run
命令用来运行项目,并且能够通过监控文件系统,实时进行代码的热部署更新。也就是,我们代码逻辑进行了修改,不用停止应用,在前端页面就能看到改变。
pack命令
// bee pack
pack命令用来发布应用的时候的打包操作,该命令会把项目大包成zip包,然后我们就可以在部署的时候直接把大包后的项目上传到服务器,然后解压进行部署。
version命令
// bee version
Version命令来查看当前bee,beego,go的版本。
使用Bee工具
常见的几个bee工具命令就是上面的几个,立即来练习使用一下。打开命令行终端,进入到GOPATH所对应的目录,然后进入到src目录中。
创建项目
使用bee run命令来新建一个案例项目,比如我们新建一个BeegoDemo2的项目,我们执行命令:bee new BeegoDemo2,命令执行效果如下:
bee new test
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.11.0
2020/07/25 16:35:17 WARN ▶ 0001 You current workdir is not inside $GOPATH/src.
2020/07/25 16:35:17 INFO ▶ 0002 Creating application...
create /Users/youmen/Go_wunai/src/test/
create /Users/youmen/Go_wunai/src/test/conf/
create /Users/youmen/Go_wunai/src/test/controllers/
create /Users/youmen/Go_wunai/src/test/models/
create /Users/youmen/Go_wunai/src/test/routers/
create /Users/youmen/Go_wunai/src/test/tests/
create /Users/youmen/Go_wunai/src/test/static/
create /Users/youmen/Go_wunai/src/test/static/js/
create /Users/youmen/Go_wunai/src/test/static/css/
create /Users/youmen/Go_wunai/src/test/static/img/
create /Users/youmen/Go_wunai/src/test/views/
create /Users/youmen/Go_wunai/src/test/conf/app.conf
create /Users/youmen/Go_wunai/src/test/controllers/default.go
create /Users/youmen/Go_wunai/src/test/views/index.tpl
create /Users/youmen/Go_wunai/src/test/routers/router.go
create /Users/youmen/Go_wunai/src/test/tests/default_test.go
create /Users/youmen/Go_wunai/src/test/main.go
2020/07/25 16:35:17 SUCCESS ▶ 0003 New application successfully created!
项目结构
cd ${GOPATH}/src
tree test
go mod init
test
├── conf
│ └── app.conf
├── controllers
│ └── default.go
├── main.go
├── models
├── routers
│ └── router.go
├── static
│ ├── css
│ ├── img
│ └── js
│ └── reload.min.js
├── tests
│ └── default_test.go
└── views
└── index.tpl
运行项目
bee run
______
| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.11.0
2020/07/25 16:37:40 INFO ▶ 0001 Using 'test' as 'appname'
2020/07/25 16:37:40 INFO ▶ 0002 Initializing watcher...
go: finding module for package github.com/astaxie/beego
go: found github.com/astaxie/beego in github.com/astaxie/beego v1.12.2
test/controllers
test/routers
2020/07/25 16:37:43 SUCCESS ▶ 0003 Built Successfully!
2020/07/25 16:37:43 INFO ▶ 0004 Restarting 'test'...
2020/07/25 16:37:43 SUCCESS ▶ 0005 './test' is running...
2020/07/25 16:37:44.611 [I] [asm_amd64.s:1373] http server Running on http://:8080
手动创建项目
创建main.go
package main
import (
"fmt"
// 导入beego包
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
)
func main() {
// 路由 url => controller
// 处理器函数 处理器 函数
// 函数, 结构体
// beego/context/Context
// 绑定函数
// 以GET方式请求/通过绑定函数处理
// 固定路由
beego.Get("/",func(ctx *context.Context){
// 用户数据的获取
name := ctx.Input.Query("name")
// 给用户响应数据
ctx.Output.Context.WriteString(fmt.Sprintf("您输入的名字是: %s",name))
})
// Post
beego.Post("/", func(ctx *context.Context) {
name := ctx.Input.Query("name")
ctx.Output.Context.WriteString(fmt.Sprintf("(Post)您输入的名字是: %s",name))
})
// 任意请求
beego.Any("/any", func(ctx *context.Context) {
name := ctx.Input.Query("name")
ctx.Output.Context.WriteString(fmt.Sprintf("(%s)您输入的名字是: %s",ctx.Input.Method(),name))
})
// 启动Beego程序
beego.Run("127.0.0.1")
}
youmen@youmendeMacBook-Pro elk % curl http://127.0.0.1:8080/
您输入的名字是: %
// 可以使用Postman
执行流程分析
Go语言执行的时候是执行main包下面的init函数,main函数依次执行。因此,先找到main.go文件。
app.conf
appname = cmdb
httpaddr = "127.0.0.1"
httpport = 8080
runmode = dev
main.go
package main
import (
_ "beego_demo1/routers"
"github.com/astaxie/beego"
)
func main() {
beego.Info("Beego_Demo1")
beego.Run("127.0.0.1:8080")
}
上就是main.go文件内容,那么代码如何执行呢?首先,import导入了两个包,一个是routers,一个是beego。而在routers包前面,可以看到有一个“_”,这表明是引入routers包,并执行init方法。这里涉及一个知识点,就是Go语言的执行过程,这里给大家一张程序执行流程图
根据上图,可以得知程序首先到routers包下执行init方法。到router.go里面看一下具体的代码:
可以看到在router.go文件中有一个init方法,看到beego.Router()这句代码。router表示的是路由的意思,这个函数的功能是映射 URL 到 controller,第一个参数是 URL (用户请求的地址),这里注册的是 /,也就是访问的不带任何参数的 URL,第二个参数是对应的 Controller,即将把请求分发到那个控制器来执行相应的逻辑,现在去这里设置的MainController中去看一下有什么方法:
package controllers
import (
"github.com/astaxie/beego"
)
type MainController struct {
beego.Controller
}
func (c *MainController) Get() {
c.Data["Website"] = "youmen.me"
c.Data["Email"] = "163@.com"
c.TplName = "index.tpl"
}
MainController结构体及函数声明在default.go文件中。而这里就看到一个Get方法,方法中有三行代码。
上文在浏览器中访问的是:http://localhost:8080,这是一个get请求,请求到了后台以后,什么请求参数都没有,因此,就会被“/”拦截,执行到MainController中的代码,因为是get请求,所以这里自动找到Get函数并进行执行。
在get函数里面,有三句代码,前两句c.Data[]= ""表示设置返回的数据字段及内容,最后一句c.TplName表示设置处理该请求指向某个模板文件,这里指向了index.tpl,那么index.tpl文件在哪里呢?可以查看项目目录结构,在views下面,views下面存放一些模板文件。
模板文件
简单解释一下,通常的页面都是使用静态的html+css+js等这些静态代码来进行页面的布局,页面效果控制等,而把页面的数据使用变量表示,这样,在进行页面展示的时候,就能够自动的填充页面里面的变量的值;这些静态的代码文件统称为模板文件。每个模板文件就是像一个模板一样,样式效果都固定,只是根据数据不一样进行渲染和展示。
服务器处理客户端过程
// 客户端请求: http协议 => url,method,params
// 服务器处理客户端过程: url => handler => params => db => templates => html
// 定义处理器
// params
// 处理器调用数据库对数据进行处理(增/删/改/查)
// 处理器调用模板基础去渲染页面
// 定义url处理器管理
// 客户端渲染: http渲染页面
Beego.Run()逻辑
init方法分析完毕后,程序会继续往下执行,就到了main函数,在main函数中执行:beego.Run()代码。分析一下Run代码的逻辑,在Run方法内部,主要做了几件事:
// 1)解析配置文件,也就是我们的app.conf文件,比如端口,应用名称等信息。
// 2)检查是否开启session,如果开启session,就会初始化一个session对象。
// 3)是否编译模板,beego框架会在项目启动的时候根据配置把views目录下的所有模板进行预编译,
// 然后存放在map中,这样可以有效的提高模板运行的效率,不需要进行多次编译。
// 4)监听服务端口。根据app.conf文件中的端口配置,启动监听。
官网Beego请求流程
通过文字来描述如下:
- 在监听的端口接收数据,默认监听在 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,对应的链接访问的执行时间,请求链接等。