1、介绍
beego是一个使用Go语言来开发WEB引用的GoWeb框架,该框架起始于2012年,由一位中国的程序员编写并进行公开,其目的就是为大家提供一个高效率的web应用开发框架。
1)特性
(1)简单化:RESTful支持,MVC模型;可以使用bee工具来提高开发效率,比如监控代码修改进行热编译,自动化测试代码,以及自动化打包部署等丰富的开发调试功能。
(2)智能化:beego框架封装了路由模块,支持智能路由,智能监控,并可以监控内存消耗,CPU使用以及goroutine的运行状况,方便开发者对线上应用进行监控分析。
(3)模块化:beego根据功能对代码进行节耦封装,形成了Session,Cache,Log,配置解析,性能监控,上下文操作,ORM等独立的模块,方便开发者进行使用。
(4)高性能:beego采用Go原生的http请求,goroutine的并发效率应付大流量的Web应用和API应用。
2)安装
go get github.com/astaxie/beego (下载安装)
git config --global http.sslVerify false (提示https不支持时使用)
go get -u github.com/astaxie/beego (升级)
创建一个beego项目
package main
import "github.com/astaxie/beego"
func main() {
beego.Info("第一个beego案例")
beego.Run("localhost:8080")
}go build -o hello hello.go
./hello
访问浏览器http://localhost:8080
3)bee工具
协助Beego框架开发项目时进行创建项目,运行项目,热部署等相关的项目管理的工具。安装完之后,bee 可执行文件默认存放在 $GOPATH/bin 里面,所以您需要把 $GOPATH/bin 添加到您的环境变量中
go get github.com/beego/bee (bee工具安装)
bee new <项目名> 创建一个新的项目
bee api <项目名> 开发 API 应用
bee run 命令是监控 beego 的项目
bee pack 用来打包
beego项目结构:

2、beego程序流程分析
Go语言执行的时候是main包下面的init函数、main函数依次执行。因此,先找到main.go文件。
首先,import导入了两个包,一个是routers,一个是beego。而在routers包前面,可以看到有一个“_”,这表明是引入routers包,并执行init方法。
然后到routers包下看到init方法:

router函数的功能是映射 URL 到 controller,第一个参数是 URL (用户请求的地址),这里注册的是 /,也就是访问的不带任何参数的 URL,第二个参数是对应的 Controller,即将把请求分发到那个控制器来执行相应的逻辑。
现在去这里设置的MainController中去看一下有什么方法:

MainController结构体及函数声明在default.go文件中。而这里就看到一个Get方法,方法中有三行代码。
一个get请求到了后台以后,什么请求参数都没有,就会被“/”拦截,执行到MainController中的代码,因为是get请求,所以这里自动找到Get函数并进行执行。
在get函数里面,有三句代码,前两句c.Data[]= ""表示设置返回的数据字段及内容,最后一句c.TplName表示设置处理该请求指向某个模板文件,这里指向了index.tpl。
解释:模板文件在views下面,通常的页面都是使用静态的html+css+js等这些静态代码来进行页面的布局,页面效果控制等,而把页面的数据使用变量表示,这样,在进行页面展示的时候,就能够自动的填充页面里面的变量的值;这些静态的代码文件统称为模板文件。每个模板文件就是像一个模板一样,样式效果都固定,只是根据数据不一样进行渲染和展示。
init方法分析完毕后,程序会继续往下执行,就到了main函数,在main函数中执行:beego.Run()代码。分析一下Run代码的逻辑,在Run方法内部,主要做了几件事:
- 解析配置文件,也就是我们的app.conf文件,比如端口,应用名称等信息。
- 检查是否开启session,如果开启session,就会初始化一个session对象。
- 是否编译模板,beego框架会在项目启动的时候根据配置把views目录下的所有模板进行预编译,然后存放在map中,这样可以有效的提高模板运行的效率,不需要进行多次编译。
- 监听服务端口。根据app.conf文件中的端口配置,启动监听。
3、MVC架构
1)控制层
(1)控制器:controllers
该目录是存放控制器文件的目录,所谓控制器就是控制应用调用哪些业务逻辑,由controllers处理完http请求以后,并负责返回给前端调用者。
beego.Controller 实现了接口 beego.ControllerInterface,该接口存在Init(ct *context.Context, childName string, app interface{})、Prepare()、Get()、Post()等等函数
(2)路由器:routers
所谓路由就是分发的意思,当前端浏览器进行一个http请求达到后台web项目时,必须要让程序能够根据浏览器的请求url进行不同的业务处理,从接收到前端请求到判断执行具体的业务逻辑的过程的工作,就由routers来实现。
在beego框架中,支持四种路由设置,他们分别是:**基础路由**,**固定路由**,**正则路由**和**自动路由**。
基础路由
直接通过beego.Get,beego.POST,beego.Head,beego.Delete等方法来进行路由的映射,常见的http请求方法操作有:GET,HEAD,PUT,POST,DELETE,OPTIONS等。
GET路由
beego.GET("",func)
beego.Get("/",func(ctx *context.Context){
ctx.Output.Body([]byte("hello world")) })
POST路由:
beego.POST("",func)固定路由
固定路由也就是全匹配的路由
beego.Router("/", &controllers.MainController{})
beego.Router("/admin", &admin.UserController{})
beego.Router("/admin/addpkg", &admin.AddController{})正则路由
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
自定义路由
beego.Router("/api/list",&RestController{},"*:ListFood")
beego.Router("/api/create",&RestController{},"post:CreateFood")
beego.Router("/api/update",&RestController{},"put:UpdateFood")
beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
(3)参数配置
beego 默认会解析当前应用下的 conf/app.conf 文件。 通过这个文件你可以初始化很多 beego 的默认参数:
appname = beepkg
httpaddr = "127.0.0.1"
httpport = 9090
runmode ="dev"
autorender = false
recoverpanic = false
viewspath = "myview"你也可以在配置文件中配置应用需要用的一些配置信息,例如下面所示的数据库信息:
mysqluser = "root"
mysqlpass = "rootpass"
mysqlurls = "127.0.0.1"
mysqldb = "beego"那么你就可以通过如下的方式获取设置的配置信息:
beego.AppConfig.String("mysqluser")
beego.AppConfig.String("mysqlpass")
beego.AppConfig.String("mysqlurls")
beego.AppConfig.String("mysqldb")runmode 参数
runmode ="dev"
[dev]
httpport = 8080
[prod]
httpport = 8088
[test]
httpport = 8888include参数
include "app2.conf"(4)XSRF
防御CSRF安全问题
在应用配置文件中加上 enablexsrf 设定:
enablexsrf = true
xsrfkey = 61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o
xsrfexpire = 3600或者直接在 main 入口处这样设置:
beego.EnableXSRF = true
beego.XSRFKEY = "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o"
beego.XSRFExpire = 3600 //过期时间,默认1小时如果开启了 XSRF,那么 beego 的 Web 应用将对所有用户设置一个 _xsrf 的 cookie 值(默认过期 1 小时),如果 POST PUT DELET 请求中没有这个 cookie 值,那么这个请求会被直接拒绝。
在 Controller 中这样设置数据:
func (this *HomeController) Get(){
this.Data["xsrfdata"]=template.HTML(this.XSRFFormHTML()
) }然后在模板中这样设置:
<form action="/new_message" method="post">
{{ .xsrfdata }}
<input type="text" name="message"/> <input type="submit" value="Post"/>
</form>(5)数据处理
通过如下方式获取数据:
- GetString(key string) string
- GetStrings(key string) []string
- GetInt(key string) (int64, error)
- GetBool(key string) (bool, error)
- GetFloat(key string) (float64, error)
解析struct,把表单里的内容赋值到一个 struct 里:
定义 struct:
type user struct {
Id int `form:"-"`
Name interface{} `form:"username"`
Age int `form:"age"`
Email string
}表单:
<form id="user">
名字:<input name="username" type="text" />
年龄:<input name="age" type="text" />
邮箱:<input name="Email" type="text" />
<input type="submit" value="提交" />
</form>Controller 里解析:
func (this *MainController) Post() {
u := user{}
if err := this.ParseForm(&u);
err != nil { //handle error }
}获取 Request Body 里的 JSON 或 XML 的数据
在配置文件里设置 copyrequestbody = true
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()
}(6)session控制
beego 中使用 session 相当方便,只要在 main 入口函数中设置如下:
beego.BConfig.WebConfig.Session.SessionOn = true
或者通过配置文件配置如下:
sessionon = true
session 有几个方便的方法:
- SetSession(name string, value interface{})
- GetSession(name string) interface{}
- DelSession(name string)
- SessionRegenerateID()
- DestroySession()
(7)过滤器
beego.InsertFilter(pattern string, position int, filter FilterFunc, params ...bool)
pattern 路由规则,可以根据一定的规则进行路由,如果你全匹配可以用 *
position 执行 Filter 的地方,五个固定参数如下,分别表示不同的执行过程
- BeforeStatic 静态地址之前
- BeforeRouter 寻找路由之前
- BeforeExec 找到路由之后,开始执行相应的 Controller 之前
- AfterExec 执行完 Controller 逻辑之后执行的过滤器
- FinishRouter 执行完逻辑之后执行的过滤器
filter filter 函数 type FilterFunc func(*context.Context)
2)model层
数据库查询操作,同时提供了 ORM 框架
(1)models.go
package main
import (
"github.com/astaxie/beego/orm"
)
type User struct {
Id int
Name string
Profile *Profile `orm:"rel(one)"` // 设置一对一的关系
Post []*Post `orm:"reverse(many)"` // 设置一对多的反向关系
}
type Profile struct {
Id int
Age int16
User *User `orm:"reverse(one)"` // 设置一对一反向关系(可选)
}
type Post struct {
Id int
Title string
User *User `orm:"rel(fk)"` //设置一对多关系
Tags []*Tag `orm:"rel(m2m)"`
}
type Tag struct {
Id int
Name string
Posts []*Post `orm:"reverse(many)"` //设置多对多反向关系
}
func init() {
// 需要在init中注册定义的model
orm.RegisterModel(new(User), new(Post), new(Profile), new(Tag))
}打印数据库语句
func main() {
orm.Debug = true
var w io.Writer
...
// 设置为你的 io.Writer
...
orm.DebugLog = orm.NewLog(w)日志格式:
[ORM] - 时间 - [Queries/数据库名] - [执行操作/执行时间] - [SQL语句] - 使用标点 `,` 分隔的参数列表 - 打印遇到的错误(2)数据库配置
import _ "github.com/go-sql-driver/mysql"
func init() {
driverName := beego.AppConfig.String("driverName")
//注册数据库驱动
orm.RegisterDriver(driverName, orm.DRMySQL)
//数据库连接
user := beego.AppConfig.String("mysqluser")
pwd := beego.AppConfig.String("mysqlpwd")
host := beego.AppConfig.String("host")
port := beego.AppConfig.String("port")
dbname := beego.AppConfig.String("dbname")
//dbConn := "root:yu271400@tcp(127.0.0.1:3306)/cmsproject?charset=utf8"
dbConn := user + ":" + pwd + "@tcp(" + host + ":" + port + ")/" + dbname + "?charset=utf8"
err := orm.RegisterDataBase("default", driverName, dbConn)
if err != nil {
util.LogError("连接数据库出错")
return
}
util.LogInfo("连接数据库成功")
}(3)注册模型
import "github.com/astaxie/beego/orm"
type User struct { Id int Name string } func init(){
orm.RegisterModel(new(User))
}RegisterModel 也可以同时注册多个 model
orm.RegisterModel(new(User), new(Profile), new(Post))
用表名前缀
orm.RegisterModelWithPrefix("prefix_", new(User))
(4)ORM接口
QueryTable
传入表名,或者 Model 对象,返回一个 QuerySeter
o := orm.NewOrm()
var qs orm.QuerySeter
qs = o.QueryTable("user") // 如果表没有定义过,会立刻 panicUsing
切换为其他数据库
orm.RegisterDataBase("db1", "mysql", "root:root@/orm_db2?charset=utf8") orm.RegisterDataBase("db2", "sqlite3", "data.db")
o1 := orm.NewOrm()
o1.Using("db1")
o2 := orm.NewOrm()
o2.Using("db2") // 切换为其他数据库以后 这个 Ormer 对象的其下的 api 调用都将使用这个数据库Raw
Raw 函数,原生 SQL查询
o := orm.NewOrm()
var r orm.RawSeter
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")
ids := []int{1, 2, 3}
p.Raw("SELECT name FROM user WHERE id IN (?, ?, ?)", ids)返回result对象
res, err := o.Raw("UPDATE user SET name = ?", "your").Exec()
if err == nil {
num, _ := res.RowsAffected()
fmt.Println("mysql row affected nums: ", num)
}Prepare
用于一次 prepare 多次 exec,以提高批量执行的速度。
p, err := o.Raw("UPDATE user SET name = ? WHERE name = ?").Prepare()
res, err := p.Exec("testing", "slene")
res, err = p.Exec("testing", "astaxie")
p.Close() // 别忘记关闭 statementRowsToStruct
查询结果匹配到 struct 里
type Options struct { Total int Found int }
res := new(Options)
nums, err := o.Raw("SELECT name, value FROM options_table").RowsToStruct(res, "name", "value")
fmt.Println(res.Total)
fmt.Println(res.Found) RowToMap
查询结果匹配到 map 里
res := make(orm.Params)
nums, err := o.Raw("SELECT name, value FROM options_table").RowsToMap(&res, "name", "value")Values
返回结果集的 key => value 值
var maps []orm.Params
num, err := o.Raw("SELECT user_name FROM user WHERE status = ?", 1).Values(&maps)
if err == nil && num > 0 {
fmt.Println(maps[0]["user_name"])
}(5)GURD
Read
o := orm.NewOrm()
user := User{Id: 1}
err := o.Read(&user)
if err == orm.ErrNoRows {
fmt.Println("查询不到")
} else if err == orm.ErrMissPK {
fmt.Println("找不到主键")
} else {
fmt.Println(user.Id, user.Name)
}
Read 默认通过查询主键赋值,可以使用指定的字段进行查询:
user := User{Name: "slene"}
err = o.Read(&user, "Name")Insert
o := orm.NewOrm()
var user User
user.Name = "slene"
user.IsActive = true
id, err := o.Insert(&user)
if err == nil {
fmt.Println(id)
}InsertMulti
users := []User{
{Name: "slene"},
{Name: "astaxie"},
{Name: "unknown"},
...
}
successNums, err := o.InsertMulti(100, users)Update
o := orm.NewOrm()
user := User{Id: 1}
if o.Read(&user) == nil {
user.Name = "MyName"
if num, err := o.Update(&user); err == nil {
fmt.Println(num)
}
}
Update 默认更新所有的字段,可以更新指定的字段:
// 只更新 Name
o.Update(&user, "Name")Delete
o := orm.NewOrm()
if num, err := o.Delete(&User{Id: 1}); err == nil {
fmt.Println(num)
}3)VIEW层
模板语法
go 统一使用了 {{ 和 }} 作为左右标签
使用 . 来访问当前位置的上下文
使用 $ 来引用当前模板根级的上下文
使用 $var 来访问创建的变量
学习链接:简介 · Go语言中文文档