beego 和 gin 框架的对比:
https://www.jianshu.com/p/bb93fdaf30c7
Beego在业务方面较Gin支持更多
- 在业务更加复杂的项目,适用beego
- 在需要快速开发的项目,适用beego
- 在1.0的项目中,适用beego,因为项目初期对性能没太大要求
Gin在性能方面较beego更好
- 当某个接口性能遭到较大的挑战,考虑用Gin重写
- 如果项目的规模不大,业务相对简单,适用Gin
beego 和 bee的安装:
安装bee 的时候会有坑
一.go版本 确保go版本在1.11及以上。 二.创建项目文件夹,并初始化 cd /project mkdir test cd test go mod init projectName(自定义的项目名) 三.替换bee的源 1. 首先我在 github.com/beego/bee 项目上 fork到我自己的项目下 git地址为: git@github.com:****/bee.git 2. 在执行完 go mod init 命令后,会在当前目录生成一个 go.mod 文件 编辑文件如下:(注 要把git地址 git@和.git部分去掉,并把:号替换成/) module beego replace github.com/beego/bee v1.10.0 => github.com/***/bee v1.6.2 go 1.12 四. 正式安装beego和bee 1. export GOPROXY=https://goproxy.io 2. go get -u github.com/astaxie/beego 3. go get -u github.com/beego/bee 如果中间没报错,即安装成功 五. 将bee命令放到GOROOT/bin目录下,这步很关键 cp bee /usr/local/go/bin/ 注:或者可以将GOPATH/bin设置为环境变量 echo ’export PATH="$GOPATH/bin:$PATH"' >> ~/.bashrc source ~/.bashrc 六. 测试 cd /project/test bee new hello cd src/hello go mod init hello(不执行) bee run 即可看到程序已启动,访问8080端口即可看到beego的页面
打开本地8080 端口,
Beego 的基础:
官方文档十分详细: https://beego.me/docs/intro/
Beego HelloWorld :
package main import "github.com/astaxie/beego" type MainController struct { beego.Controller } func (this *MainController) Get() { this.Ctx.WriteString("Hello world") } func main() { beego.Router("/",&MainController{}) beego.Run() }
Beego NameSpace :
package routers import ( "github.com/astaxie/beego" "quickstart/controllers" ) func init() { ns := beego.NewNamespace("/v1", beego.NSNamespace("/user", beego.NSRouter("/login",&controllers.MainController{}))) beego.AddNamespace(ns) }
MVC :
M : 数据库
V:模板
C:路由控制
Beego Orm :
安装:go get github.com/astaxie/beego/orm
增:
package main import ( "fmt" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" "main.go/models" _ "main.go/models" ) func init() { fmt.Println("main.go init") orm.RegisterDataBase("default","mysql", "root:123456@/go_mysql?charset=utf8") orm.RunSyncdb("default",false,true) } // 增 func insert(o orm.Ormer) { //=================insert==================== // 创建 profile //var profile models.Profile //profile.Age = 38 //profileId,err := o.Insert(&profile) //if err != nil { // fmt.Println("insert profile failed,err:",err) //} //fmt.Println(profileId) // //// 创建 user //var user models.User //user.Name = "xxx" //user.Profile = &profile //userId,err := o.Insert(&user) //if err != nil { // fmt.Println("insert user failed,err:",err) //} //fmt.Println(userId) //=================insert multi==================== //profiles := []models.Profile{ // {Age: 10}, // {Age: 11}, // {Age: 12}, // {Age: 13}, //} //successNums, err := o.InsertMulti(1, profiles) // bulk 为1 将会顺序插入 slice 中的数据 //if err != nil { // fmt.Println("failed,err",err) //} //fmt.Println("success num :",successNums) //=================PrepareInsert==================== //用于一次 prepare 多次 insert 插入,以提高批量插入的速度。 //profiles := []models.Profile{{Age: 17},{Age: 16},{Age: 15},} //qs := o.QueryTable("profile") //i,_ := qs.PrepareInsert() //for _,profile := range profiles{ // id,_ := i.Insert(&profile) // fmt.Println("inserted success",id) //} //i.Close() // 别忘记关闭 statement } func main() { o := orm.NewOrm() // 默认使用的是 default 数据库 // 增 insert(o) }
package models import ( "github.com/astaxie/beego/orm" "fmt" ) // 用户 type User struct { Id int Name string Profile *Profile `orm:"rel(one)"` // OneToOne relation 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)) fmt.Println("models.go init") }
查:
models/model.go 同上,
package main import ( "fmt" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" "main.go/models" _ "main.go/models" ) func init() { fmt.Println("main.go init") orm.RegisterDataBase("default","mysql", "root:123456@/go_mysql?charset=utf8") orm.RunSyncdb("default",false,true) } // 查 func query(o orm.Ormer) { //=================Read==================== //user := models.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) //} //user := models.User{Name: "jack"} //err := o.Read(&user,"Name") // Read 默认通过查询主键赋值,可以使用指定的字段进行查询 //if err != nil { // fmt.Println("failed",err) //} //fmt.Println(user) //=================ReadOrCreate==================== //默认必须传入一个参数作为条件字段 //profile := models.Profile{Age: 20} //// 三个返回参数依次为:是否新创建的,对象 Id 值,错误 //created, id, err := o.ReadOrCreate(&profile, "Age") //if err != nil { // fmt.Println("failed,",err) //}else{ // if created { // fmt.Println("New Insert an object. Id:", id) // } else { // fmt.Println("Get an object. Id:", id) // } //} //=================高级查询==================== //=================高级查询==================== //=================高级查询==================== //ORM 以 QuerySeter 来组织查询,每个返回 QuerySeter 的方法都会获得一个新的 QuerySeter 对象。 //================= 获取 QuerySeter==================== // 获取 QuerySeter 对象,user 为表名 //qs := o.QueryTable("user") //// 也可以直接使用对象作为表名 //user := new(models.User) //qs = o.QueryTable(user) // 返回 QuerySeter //=================expr==================== //QuerySeter 中用于描述字段和 sql 操作符,使用简单的 expr 查询方法 //字段组合的前后顺序依照表的关系,比如 User 表拥有 Profile 的外键,那么对 User 表查询对应的 Profile.Age 为条件,则使用 Profile__Age 注意,字段的分隔符号使用双下划线 __,除了描述字段, expr 的尾部可以增加操作符以执行对应的 sql 操作。比如 Profile__Age__gt 代表 Profile.Age > 18 的条件查询。 //注释后面将描述对应的 sql 语句,仅仅是描述 expr 的类似结果,并不代表实际生成的语句。 qs := o.QueryTable("user") //=================Filter==================== //用来过滤查询结果,起到 包含条件 的作用 //多个 Filter 之间使用的是 AND 连接 不是 OR的关系 //var users []models.User //qs.Filter("id",1).All(&users) //qs.Filter("profile__lt",3).All(&users) // WHERE profile_id < 3 // profile_id 数据库中存的字段 //qs.Filter("profile__age",38).All(&users) // WHERE profile.age = 38 //qs.Filter("profile__age__gt",18).All(&users) // WHERE profile.age > 18 //qs.Filter("profile__age__gte",18).All(&users) // WHERE profile.age >= 18 //qs.Filter("profile__age__in",18,28).All(&users) //WHERE profile.age IN (18, 28) //fmt.Println(users) //=================Exclude==================== //用来过滤查询结果,起到 排除条件 的作用 //使用 NOT 排除条件 //多个 Exclude 之间使用 AND 连接 //var users []models.User //qs.Exclude("profile__lt",3).All(&users) //fmt.Println(users) //=================Limit==================== //限制最大返回数据行数,第二个参数可以设置 Offset // ORM 默认的 limit 值为 1000 //var users []models.User //qs.Filter("id__gte",1).Limit(2).All(&users) //fmt.Println(users) //var users []models.User //qs.Filter("id__gte",1).Limit(2,1).All(&users) // offset 为1 //fmt.Println(users) //=================Offset==================== //var users []models.User //qs.Filter("id__gte",1).Offset(1).All(&users) // offset 为1 //fmt.Println(users) //=================GroupBy(没有不知道如何用,还是原生sql吧)==================== //var users []models.User //qs.GroupBy("name").All(&users) //fmt.Println(users) //var maps []orm.Params //o.Raw("select name,avg(profile_id) as avg from user group by name").Values(&maps) //fmt.Println(maps) //for _,m := range maps{ // fmt.Println(m) //} //=================OrderBy==================== //在 expr 前使用减号 - 表示 DESC 的排列 //var users []models.User //qs.OrderBy("profile_id").All(&users) //fmt.Println(users) // // //qs.OrderBy("-profile_id").All(&users) //fmt.Println(users) //=================Distinct==================== //var users []models.User //qs.Distinct().All(&users,"name") //fmt.Println(users) //=================Count==================== //依据当前的查询条件,返回结果行数 //cnt, _ := qs.Count() //fmt.Println(cnt) //cnt,_ := qs.Filter("name","xxx").Count() //fmt.Println(cnt) //=================Exist==================== //ret1 := qs.Filter("name","yyy").Exist() //ret2 := qs.Filter("name","zzz").Exist() //ret3 := qs.Filter("name","xxx").Exist() //fmt.Println(ret1,ret2,ret3) //=================All==================== //All / Values / ValuesList / ValuesFlat 受到 Limit 的限制,默认最大行数为 1000 //可以指定返回的字段: //var users []models.User //qs.All(&users,"id","name") // 指定返回id 和 name 对象的其他字段值将会是对应类型的默认值 //fmt.Println(users) //=================One==================== //尝试返回单条记录 //var users []models.User //qs.Filter("name","xxx").One(&users) // 如果有多条也是返回一个 One 后面也可以指定返回字段 //fmt.Println(users) //=================Values==================== // 返回结果以 map 存储 //var maps []orm.Params // 其实是 [] map[string]interface{} //cnt,err := qs.Values(&maps) //if err != nil { // fmt.Println("failed,err",err) //} //fmt.Println(cnt) //fmt.Println(maps) //for _,m :=range maps{ // fmt.Println(m) //} //暂不支持级联查询 RelatedSel 直接返回 Values //但可以直接指定 expr 级联返回需要的数据 //var maps []orm.Params // 其实是 [] map[string]interface{} //cnt,err := qs.Values(&maps,"id", "name", "profile", "profile__age") //if err != nil { // fmt.Println("failed,err",err) //} //for _,m :=range maps{ // fmt.Println(m["Id"],m["Name"],m["Profile"],m["Profile__Age"]) //} //=================ValuesList==================== //返回的结果集以slice存储 //结果的排列与 Model 中定义的 Field 顺序一致 //返回的每个元素值以 string 保存 //var lists []orm.ParamsList //cnt ,err := qs.ValuesList(&lists) //if err != nil { // fmt.Println("failed",err) //} //fmt.Println(cnt) //fmt.Println(lists) //for _,l := range lists{ // fmt.Println(l[0],l[1],l[2]) //} //可以指定 expr 返回指定的 Field //var lists []orm.ParamsList //cnt ,err := qs.ValuesList(&lists,"name", "profile__age") //if err != nil { // fmt.Println("failed",err) //} //fmt.Println(cnt) //for _,l := range lists{ // fmt.Println(l[0],l[1]) //} //=================ValuesFlat==================== //只返回特定的 Field 值,将结果集展开到单个 slice 里 //var l orm.ParamsList //cnt,err := qs.ValuesFlat(&l,"name") //if err != nil { // fmt.Println("failed",err) //} //fmt.Println(cnt) //fmt.Println(l) //=================Operators==================== //=================当前支持的操作符号==================== //当前支持的操作符号: //exact / iexact 等于 //contains / icontains 包含 //gt / gte 大于 / 大于等于 //lt / lte 小于 / 小于等于 //startswith / istartswith 以…起始 //endswith / iendswith 以…结束 //in //isnull //后面以 i 开头的表示:大小写不敏感 /*exact*/ // 它是 Filter / Exclude / Condition 的默认值 // 就是等于 //使用 = 匹配,大小写是否敏感取决于数据表使用的 collation //qs.Filter("name", "tom") // WHERE name = 'tom' //qs.Filter("name__exact", "tom") // WHERE name = 'tom' //qs.Filter("profile_id", nil) // WHERE profile_id IS NULL /*iexact*/ // 大小写不敏感,匹配任意 'tom' 'Tom' //qs.Filter("name__iexact", "tom") // WHERE name LIKE 'slene' /*contains*/ // 大小写敏感, 匹配包含 o 的字符 //qs.Filter("name__contains", "o") // WHERE name LIKE BINARY '%o%' /*icontains*/ // 大小写不敏感, 匹配包含 o 的字符 //qs.Filter("name__icontains", "o") /*in*/ //var users []models.User //qs.Filter("name__in", "tom","xxx").All(&users) // WHERE age IN ( "tom","xxx") //fmt.Println(users) // 同上效果 //var users []models.User //names :=[]string{"tom","xxx"} //qs.Filter("name__in", names).All(&users) // WHERE age IN ( "tom","xxx") //fmt.Println(users) /*gt / gte*/ // 大于 大于等于 /*lt / lte*/ // 小于 小于等于 /*startswith*/ // 大小写敏感, /*istartswith*/ // 大小写不敏感, /*endswith*/ // 大小写敏感, /*iendswith*/ // 大小写不敏感, /* isnull */ //qs.Filter("profile__isnull", true) //qs.Filter("profile_id__isnull", true) //// WHERE profile_id IS NULL // //qs.Filter("profile__isnull", false) //// WHERE profile_id IS NOT NULL //=================SetCond==================== //自定义条件表达式 //var users []models.User //cond := orm.NewCondition() // // age >= 28 and profile_id <= 3 or age = 38 //cond1 := cond.And("profile__age__gte",28).AndNot("profile__gt",3).Or("profile__age",38) //qs.SetCond(cond1).All(&users) // WHERE ... AND ... AND NOT ... OR ... //fmt.Println(users) // //cond2 := cond.AndCond(cond1).OrCond(cond.And("name", "jack")) // WHERE (... AND ... AND NOT ... OR ...) OR ( ... ) //qs.SetCond(cond2).All(&users) //fmt.Println(users) } func main() { o := orm.NewOrm() // 默认使用的是 default 数据库 query(o) }
改:
models/model.go 同上,
package main import ( "fmt" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" "main.go/models" _ "main.go/models" ) func init() { fmt.Println("main.go init") orm.RegisterDataBase("default","mysql", "root:123456@/go_mysql?charset=utf8") orm.RunSyncdb("default",false,true) } func update(o orm.Ormer) { //=================Update==================== //第一个返回值为影响的行数 //user := models.User{Id: 1} //err := o.Read(&user) //if err != nil { // fmt.Println("failed",err) // return //} //user.Name="tomman" //cnt,_ := o.Update(&user) //fmt.Println(cnt) //Update 默认更新所有的字段,可以更新指定的字段: //user := models.User{Id: 1} //err := o.Read(&user) //if err != nil { // fmt.Println("failed",err) // return //} //user.Name="tomman" //cnt,_ := o.Update(&user,"Name") //fmt.Println(cnt) //=================批量更新==================== //依据当前查询条件,进行批量更新操作 //num, err := o.QueryTable("user").Filter("id__in", 1,2,3).Update(orm.Params{ // // "name": "tom", //}) //fmt.Printf("Affected Num: %d, %s", num, err) //=================原子操作增加字段值==================== // 假设 user struct 里有一个 nums int 字段 //num, err := o.QueryTable("user").Update(orm.Params{ // "nums": orm.ColValue(orm.ColAdd, 100), //}) // SET nums = nums + 100 //orm.ColValue 支持以下操作 //ColAdd // 加 //ColMinus // 减 //ColMultiply // 乘 //ColExcept // 除 } func main() { o := orm.NewOrm() // 默认使用的是 default 数据库 // 改 update(o) }
删:
models/model.go 同上,
package main import ( "fmt" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" "main.go/models" _ "main.go/models" ) func init() { fmt.Println("main.go init") orm.RegisterDataBase("default","mysql", "root:123456@/go_mysql?charset=utf8") orm.RunSyncdb("default",false,true) } func del(o orm.Ormer) { //=================Delete==================== //第一个返回值为影响的行数 //profile := models.Profile{Id: 5} //cnt,_ := o.Delete(&profile) //fmt.Println(cnt) //Delete 操作会对反向关系进行操作, //此例中 Post 拥有一个到 User 的外键。删除 User 的时候。如果 on_delete 设置为默认的级联操作,将删除对应的 Post //Changed in 1.0.3 删除以后不会删除 auto field 的值 //=================批量删除==================== //num, err := o.QueryTable("profile").Filter("id__in", 7,8,9).Delete() //fmt.Printf("Affected Num: %d, %s", num, err) } func main() { o := orm.NewOrm() // 默认使用的是 default 数据库 // 删 del(o) }
一对一,一对多,多对多,以及 载入关系字段 以及 QueryM2Mer 对象 :
models/model.go 同上,
package main import ( "fmt" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" "main.go/models" _ "main.go/models" ) func init() { fmt.Println("main.go init") orm.RegisterDataBase("default","mysql", "root:123456@/go_mysql?charset=utf8") orm.RunSyncdb("default",false,true) } func main() { o := orm.NewOrm() // 默认使用的是 default 数据库 // 关系操作 //=================一对一==================== //=================一对一==================== //=================一对一==================== // User 和 Profile // 新增一对一 //profile := models.Profile{Age: 18} //o.Insert(&profile) //user := models.User{Name: "jaxxx",Profile: &profile} //o.Insert(&user) // 一对一查询 // 任务 查询 用户id为1 的 profile //=================正向查询==================== // (已经取得了 User 对象,查询 Profile) //user := models.User{Id: 1} //o.Read(&user) //fmt.Println(user) ////fmt.Println(user.Profile) // 此时是user 表中的profile_id //o.Read(user.Profile) // 查询 ////fmt.Println(user.Profile) // 此时是profile 表中的 数据了 // 直接关联 查询 //user := models.User{} //o.QueryTable("user").Filter("Id", 1).RelatedSel().One(&user) //fmt.Println(user.Profile) // 此时已经是profile 表中的 数据了 // //// 因为在 Profile 里定义了反向关系的 User,所以 Profile 里的 User 也是自动赋值过的,可以直接取用。 //fmt.Println(user.Profile.User) //fmt.Println(user) //=================反向查询==================== // 通过profile 反向查询 User //var profile models.Profile //o.QueryTable("profile").Filter("User__Id",1).One(&profile) //fmt.Println(profile) //=================一对多==================== //=================一对多==================== //=================一对多==================== // User 和 Post // 新增一对多 //user := models.User{Id: 1} //post := models.Post{Title: "Golang从入门到放弃",User: &user} //o.Insert(&post) // 一对多查询 // 任务 查询 用户id为1 的 post //=================正向查询==================== //user := models.User{Id: 1} //o.Read(&user) //o.LoadRelated(&user,"Post") // 载入关系字段 //fmt.Println(user.Post) //for _,post := range user.Post{ // fmt.Println(post) //} //=================反向查询==================== //var posts []models.Post //o.QueryTable("post").Filter("user_id", 1).RelatedSel().All(&posts) //fmt.Println(posts) //=================多对多==================== //=================多对多==================== //=================多对多==================== // Post 和 Tag // 新增多对多 //tag1 := models.Tag{Name: "Python"} //tag2 := models.Tag{Name: "编程语言"} //o.Insert(&tag1) //o.Insert(&tag2) // //var post models.Post //o.QueryTable("post").Filter("id",1).One(&post) //m2m := o.QueryM2M(&post,"Tags") // 需要通过m2m 这个对象 才能生成第三张表 //m2m.Add(&tag1) //cnt,_:= m2m.Add(&tag2) // 此时第三张表 中就会有值了 //fmt.Println(cnt) // 多对多查询 // 任务 查询 post id为1 的 tag //=================正向查询==================== //post := models.Post{Id: 1} //o.Read(&post) //o.LoadRelated(&post,"Tags") // 载入关系字段 //fmt.Println(post) //for _,tag := range post.Tags{ // fmt.Println(tag) //} //=================反向查询==================== //var tags []models.Tag //o.QueryTable("tag").Filter("posts__post__id",1).All(&tags) //fmt.Println(tags) //=================QueryM2Mer 多对多操作 ==================== //=================QueryM2Mer 多对多操作 ==================== //=================QueryM2Mer 多对多操作 ==================== //=================创建 QueryM2Mer 对象==================== //post := models.Post{Id: 1} //m2m := o.QueryM2M(&post,"Tags") //// 第一个参数的对象,主键必须有值 //// 第二个参数为对象需要操作的 M2M 字段 //// QueryM2Mer 的 api 将作用于 Id 为 1 的 Post //=================Add==================== //向M2M关系 tag // 给id =2 的post 增加一个 名字为 Golang 的tag //post := models.Post{Id: 2} //m2m := o.QueryM2M(&post,"Tags") //tag := models.Tag{Name: "Golang"} //o.Insert(&tag) // //m2m.Add(&tag) // 也可以多个作为参数传入 // m2m.Add(tag1, tag2, tag3) //=================Remove==================== //从M2M关系中删除 tag // 从id 为1 的Post 上去掉 "编程语言" 的tag //post := models.Post{Id: 1} //m2m := o.QueryM2M(&post,"Tags") // //var tag models.Tag //o.QueryTable("tag").Filter("name","编程语言").One(&tag) // //m2m.Remove(tag) // 只是从第三张表中 删去记录 // 也可以多个作为参数传入 // m2m.Remove(tag1, tag2, tag3) //=================Exist==================== //判断 "编程语言" 这个Tag 是否存在于 post id为1 的 M2M 关系中 //post := models.Post{Id: 1} //m2m := o.QueryM2M(&post,"Tags") // //var tag models.Tag //o.QueryTable("tag").Filter("name","编程语言").One(&tag) //ret := m2m.Exist(&tag) //fmt.Println(ret) //=================Count==================== // 计算Post id 为1 的tag 的数量 //post := models.Post{Id: 1} //m2m := o.QueryM2M(&post,"Tags") //nums,_ := m2m.Count() //fmt.Println(nums) //=================Clear==================== // 清除 post id 为1 的 所有的M2M 关系 //post := models.Post{Id: 1} //m2m := o.QueryM2M(&post,"Tags") // //m2m.Clear() }
原生SQL查询:
https://beego.me/docs/mvc/model/rawsql.md
Beego 模板渲染:
beego 中使用的模板语法,与 go 模板语法基本相同
with end:
with 重定向pipeline :
<div>{{.person.name}}</div> <div>{{.person.age}}</div> <div>{{.person.hobby}}</div> <hr> 以下方式 和上面的结果是相同的, {{with .person}} <div>{{.name}}</div> <div>{{.age}}</div> <div>{{.hobby}}</div> {{end}}
define :
https://beego.me/docs/mvc/view/tutorial.md#define
define 可以用来定义自模板,可用于模块定义和模板嵌套
注:使用template 调用的时候,后面有个 点 , 因为要把当前位置的上下文传入,
Beego 中支持直接载入文件模板
{{template "path/to/head.html" .}}
Beego 会依据你设置的模板路径读取 head.html
在模板中可以接着载入其他模板,对于模板的分模块处理很有用处
注:也是需要传入 点的 ,
注释:
允许多行文本注释,不允许嵌套
{{/* comment content
support new line */}}
模板处理:
Layout 设计:
Beego 路由控制:
Beego 控制器函数:
参考: https://beego.me/docs/mvc/controller/controller.md
package controllers import ( "fmt" "github.com/astaxie/beego" ) type MainController struct { beego.Controller } func (c *MainController) Get() { c.Data["Website"] = "beego.me" c.Data["Email"] = "astaxie@gmail.com" c.TplName = "index.tpl" } //===================================== type NestPreparer interface { NestPrepare() } // baseRouter implemented global settings for all other routers. type baseController struct { beego.Controller isLogin bool } // Prepare implemented Prepare method for baseRouter. func (this *baseController) Prepare() { fmt.Println("baseController prepare") fmt.Println("hello world") this.Data["json"]= map[string]interface{}{"name":"tom"} this.Finish() // 只会 调 当前的 Finish() this.ServeJSON() this.StopRun() // page start time //this.Data["PageStartTime"] = time.Now() if app, ok := this.AppController.(NestPreparer); ok { app.NestPrepare() } } func (this *baseController) Finish(){ fmt.Println("baseController 收尾了 ...") } //===================================== type BaseAdminRouter struct { baseController } func (this *BaseAdminRouter) Finish(){ fmt.Println("baseAdminRouter 收尾了 ...") } func (this *BaseAdminRouter) NestPrepare() { fmt.Println("baseAdminRouter nestPrepare") } func (this *BaseAdminRouter) Get(){ this.Ctx.WriteString("baseAdminRouter get") } func (this *BaseAdminRouter) Post(){ this.Ctx.WriteString("baseAdminRouter post") }
Beego 请求数据处理:
1,获取数据的方式:
- GetString(key string) string
- GetStrings(key string) []string
- GetInt(key string) (int64, error)
- GetBool(key string) (bool, error)
- GetFloat(key string) (float64, error)
更多其他的 request 的信息,用户可以通过 this.Ctx.Request
获取信息
2,直接解析到 struct :
如果要把表单里的内容赋值到一个 struct 里,除了用上面的方法一个一个获取再赋值外,beego 提供了通过另外一个更便捷的方式,就是通过 struct 的字段名或 tag 与表单字段对应直接解析到 struct。
详情:https://beego.me/docs/mvc/controller/params.md#%E7%9B%B4%E6%8E%A5%E8%A7%A3%E6%9E%90%E5%88%B0-struct
关于 struct 的tag :
注意:
- 定义 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
标签的值设置为-
3,post 请求时: 获取 Request Body 里的 JSON 或 XML 的数据:
首先:
在配置文件里设置 copyrequestbody = true
type MainController struct { beego.Controller } func (c *MainController) Post() { ret := c.Ctx.Input.RequestBody var u User json.Unmarshal(ret,&u) c.Ctx.WriteString("post ok") }
<script src="static/js/jquery.js"></script> <script> $(function () { $.ajax({ url:"http://localhost:8080/", type:"post", contentType:"application/json", data:JSON.stringify({ "name": "tom", "age": 18 }), success:function (res) { console.log(res); } }) }) </script>
4,post 文件上传:
https://beego.me/docs/mvc/controller/params.md#%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0
func (c *MainController) Post() {
_,h,_ := c.GetFile("upload_file")
//fmt.Println(f)
//fmt.Println(h)
//fmt.Println("===========")
//fmt.Println(h.Filename)
//fmt.Println(h.Size) // 字节
//fmt.Println(h.Header)
//fmt.Println("===========")
//fmt.Println(err)
err := c.SaveToFile("upload_file","static/upload/" + h.Filename)
fmt.Println(err)
c.Ctx.WriteString("post ok")
}
<form action="" method="post" enctype="multipart/form-data"> <input name="upload_file" type="file"> <input type="submit"> </form>
5, 数据绑定:
https://beego.me/docs/mvc/controller/params.md#%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A
Beego 在表单中使用 PUT 方法:
跨站请求伪造:
以下内容参考: https://www.cnblogs.com/xiaxiaoxu/p/10428483.html
跨站请求伪造(Cross-site request forgery),简称CSRF 或 XSRF,是Web 应用中常见的一个安全问题。
过程主要是:某用户登录了A网站,认证信息保存在cookie中。当用户访问攻击者创建的B网站时,攻击者通过在B网站发送一个伪造的请求提交到A网站服务器上,让A网站服务器误以为请求来自于自己的网站,于是执行响应的操作,该用户的信息边遭到了篡改。总结起来就是,攻击者利用用户在浏览器中保存的认证信息,向对应的站点发送伪造请求。用户的认证是通过保存在cookie中的数据实现,在发送请求是,只要浏览器中保存了对应的cookie,服务器端就会认为用户已经处于登录状态,而攻击者正是利用了这一机制。
当前防范 XSRF 的一种通用的方法,是对每一个用户都记录一个无法预知的 token 数据,然后要求所有提交的请求(POST/PUT/DELETE)中都必须带有这个 token 数据。如果此数据不匹配 ,那么这个请求就可能是被伪造的。
当处理非GET请求时,要想避免CSRF攻击,关键在判断请求是否来自自己的网站。理论上讲,通过HTTP referrer可以判断原站点从而避免CSRF攻击,但是referer很容易被修改和伪造,所以不能作为主要的防御措施。
除了在表单中加入校验码外,一般的做法是通过在客户端页面中加入伪随机数来防御CSRF攻击,这个伪随机数通过被称为CSRF令牌(token)。
在计算机语境中,令牌(token)指用于标记、验证和传递信息的字符,通常是通过一定算法生成的随机数。
在HTML中,POST方法的请求通过表单创建。我们把在服务器端创建的伪随机数(CSRF令牌)添加到表单中的隐藏字段里和session变量(即签名cookie)中,当用户提交表单时,这个令牌会和表单数据一起提交。在服务器端处理POST请求时,会对表单中的令牌值进行验证,如果表单中的令牌值和seesion中的令牌值相同,就说明请求来自自己的网站。因为CSRF令牌在用户向包含表单的页面发起GET请求时创建,并且在一定时间内过期,一般情况下攻击者无法获取到这个令牌值,所以我们可以有效地区分出请求的来源是否安全。
对于AJAX请求,我们可以在XMLHttpRequest请求首部添加一个自定义字段X-CSRFToken来保存CSRF令牌。
如果程序存在XSS漏洞(跨站脚本攻击),那么攻击者可以使用javaScript窃取cookie内容,进而获取CSRF令牌。
beego 中的 XSRF 设置:
https://beego.me/docs/mvc/controller/xsrf.md#%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0
1,在 表单中使用:
https://beego.me/docs/mvc/controller/xsrf.md#%E5%9C%A8%E8%A1%A8%E5%8D%95%E4%B8%AD%E4%BD%BF%E7%94%A8
2,在 ajax 中使用:
https://beego.me/docs/mvc/controller/xsrf.md#%E5%9C%A8-javascript-%E4%B8%AD%E4%BD%BF%E7%94%A8
<div id="xsrf_token" style="display: none">{{.xsrf_token}}</div> <script src="static/js/jquery.js"></script> <script> $(function () { $.ajax({ url:"http://localhost:8080/", type:"post", data:{ userName :"tom", age:18, _xsrf:$("#xsrf_token").text(), }, success:res=>{ console.log(res); } }) }) </script>
func (c *MainController) Get() {
c.Data["xsrf_token"] = c.XSRFToken() // 给index.html 页面传入token
c.TplName = "index.html"
}
方法二: 不用隐藏标签:
<script src="static/js/jquery.js"></script> <script> $(function () { $.ajaxSetup({ data: {_xsrf: '{{ .xsrf_token }}' }, }); $.ajax({ url:"http://localhost:8080/", type:"post", data:{ userName :"tom", age:18, }, success:res=>{ console.log(res); } }) }) </script>
controllers/default.go 同上,
方法三: 通过放入 请求头中发送,
注意:需要引入一个jquery.cookie.js插件,没用过。
支持controller 级别的屏蔽:
XSRF 之前是全局设置的一个参数,如果设置了那么所有的 API 请求都会进行验证,但是有些时候API 逻辑是不需要进行验证的,因此现在支持在controller 级别设置屏蔽
还是很有用的,这个!!!
beego 中的 session 的控制:
https://beego.me/docs/mvc/controller/session.md
特别注意:
因为 session 内部采用了 gob 来注册存储的对象,例如 struct,所以如果你采用了非 memory 的引擎,请自己在 main.go 的 init 里面注册需要保存的这些结构体,不然会引起应用重启之后出现无法解析的错误,需要用gob.Register(&models.User{}) 注册下!!!
beego 中的过滤器:
package routers import ( "test03/controllers" "github.com/astaxie/beego" ) func init() { beego.Router("/", &controllers.MainController{}) // 过滤器 beego.InsertFilter("/*", beego.BeforeRouter, controllers.BeforeRouter) }
package controllers import ( "fmt" "github.com/astaxie/beego/context" "reflect" ) //=================是否登录过滤器==================== //寻找路由之前 func BeforeRouter(ctx *context.Context) { fmt.Println(ctx.Input.RunController ) fmt.Println(ctx.Input.RunMethod) ctx.Input.RunController = reflect.TypeOf(TestController{}) ctx.Input.RunMethod = "Test" fmt.Println(ctx.Input.RunController ) fmt.Println(ctx.Input.RunMethod) }
beego 中的flash 闪存:
beego 中的url 构建:
beego 中的多种格式输出 json xml jsonp:
什么是 jsonp :
https://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="/static/js/jquery.js"></script> <script> let script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'http://127.0.0.1:8080/?callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { // alert(JSON.stringify(res)); console.log(res); } </script> </body> </html>
常见的跨域解决方案:
beego 表单验证:
https://beego.me/docs/mvc/controller/validation.md
表单验证是用于数据验证和错误收集的模块。
go get github.com/astaxie/beego/validation
package main import ( "fmt" "github.com/astaxie/beego/validation" ) type User struct { Name string Age int } func main() { u := User{"tomxxxooo", 8} valid := validation.Validation{} valid.Required(u.Name, "name") valid.MaxSize(u.Name, 6, "nameMax") valid.Range(u.Age, 0, 18, "age") if valid.HasErrors() { // 如果有错误信息,证明验证没通过 // 打印错误信息 for _, err := range valid.Errors { fmt.Println(err.Key,err.Message) } } fmt.Println("========================") // or use like this v := valid.Max(u.Age,16,"xx_age") if !v.Ok{ fmt.Println(v.Error.Key,v.Error.Message) } fmt.Println("==================") // 定制错误信息 minAge := 10 v = valid.Min(u.Age, minAge, "age").Message("少儿不宜o!") if !v.Ok{ fmt.Println(v.Error.Key,v.Error.Message) } fmt.Println("====================") //错误信息格式化 v = valid.Min(u.Age, minAge, "age").Message("%d不禁", minAge) if !v.Ok{ fmt.Println(v.Error.Key,v.Error.Message) } }
StructTag 使用:
package main import ( "fmt" "strings" "github.com/astaxie/beego/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: "Beegoadmin", Age: 2, Email: "dev@beego.me",Mobile: "17778882661",IP: "120.53.110.1"} ok, err := valid.Valid(&u) if err != nil { fmt.Println(err) // handle error } if !ok { for _, err := range valid.Errors { fmt.Println(err.Key, err.Message) } } }
beego 错误处理:
主要是404,501 处理,
beego 日志处理:
https://beego.me/docs/mvc/controller/logs.md
https://beego.me/docs/module/logs.md
我们的程序往往期望把信息输出到 log 中,现在设置输出到文件很方便,如下所示:beego.SetLogger("file", `{"filename":"logs/test.log"}`)
这个默认情况就会同时输出到两个地方,一个 console,一个 file,如果只想输出到文件,就需要调用删除操作:beego.BeeLogger.DelLogger("console")