Golang 语言 beego v

Golang 语言 beego v2 之安装和快速入门

01

介绍

beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。

beego 架构:

beego 是基于八大独立的模块构建的,是一个高度解耦的框架。

当初设计 beego 的时候就是考虑功能模块化,用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块,例如:你可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。

所以 beego 不仅可以用于 HTTP 类的应用开发,在你的 socket 游戏开发中也是很有用的模块,这也是 beego 为什么受欢迎的一个原因。

大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计 beego 的时候,这些模块就是积木,高级机器人就是 beego。

beego 执行逻辑:

02

安装 beego v2

因为截至本文发稿前,beego v2 正式版还没有发布,所以我们通过安装 beego 的 develop 分支,使用 beego v2。

我们使用 bee 工具生成 web 项目,bee 工具是一个为了协助快速开发 beego 项目而创建的项目,通过 bee 您可以很容易的进行 beego 项目的创建、热编译、开发、测试、和部署。关于 bee 工具的更多用法,请参阅官方文档。

截至本文发稿前,bee v2 正式版也还没有发布,所以我们通过安装 bee 的 develop 分支,使用 bee v2。

安装 bee v2.0.0

查看 bee 版本

如果可以输出版本信息,证明 bee 工具安装成功。(bug:使用 bee 工具查询版本,显示的 beego 版本信息不对。)

新建 web 项目(bug:bee 工具生成 web 项目,使用的 beego v1.12.1)

升级到 beego v2.0.0

运行

cURL 访问

03

快速入门

web 项目的目录结构:

web
├── config
│   └── app.conf
├── controllers
│   └── default.go
├── go.mod
├── go.sum
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
│       └── reload.min.js
├── tests
│   └── default_test.go
└── views
    └── index.tpl

从上面的目录结构我们可以看出来 M(models 目录)、V(views 目录)和 C(controllers 目录)的结构, main.go 是入口文件。

阅读 bee 工具生成的 web 项目代码,main.go 文件导入 routers 包,main 函数调用 beego.Run() 方法,监听默认端口 8080。

package main

import (
  _ "web/routers"
  beego "github.com/astaxie/beego/server/web"
)

func main() {
  beego.Run()
}

阅读 routers/router.go 文件,init 函数调用beego.Router() 方法,注册路由,第一个参数传入 URI,第二个参数传入Controller。

package routers

import (
  "web/controllers"
  beego "github.com/astaxie/beego/server/web"
)

func init() {
    beego.Router("/", &controllers.MainController{})
}

阅读 controllers/default.go 文件,声明一个名为 MainController 的结构体,并且实现 Get 方法,Get 方法指定模板文件。

package controllers

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

type MainController struct {
  beego.Controller
}

func (c *MainController) Get() {
  c.Data["Website"] = "beego.me"
  c.Data["Email"] = "astaxie@gmail.com"
  c.TplName = "index.tpl"
}

阅读 views/index.tpl 文件,使用模板标签语法,渲染数据。

<footer>
    <div class="author">
      Official website:
      <a href="http://{{.Website}}">{{.Website}}</a> /
      Contact me:
      <a class="email" href="mailto:{{.Email}}">{{.Email}}</a>
    </div>
</footer>

04

总结

本文开篇介绍了 beego 是什么,beego 的架构和 beego 的执行逻辑,然后介绍了安装 beego v2 的步骤,最后介绍了 bee 工具生成的 web 项目目录结构和代码解读。

bug list:

  1. 使用 bee 工具查询版本,显示的 beego 版本信息不对。
  2. 使用 bee v2.0.0 工具生成 web 项目,安装的 beego 版本 beego v1.12.1。
  3. 使用 bee v2.0.0 工具生成 web 项目,配置文件目录名是 conf/,而 bee run 查找的配置文件目录名是 config/,如果手动修改目录名,bee run 时,将无法自动生成注解路由的文件。

以上 bug 已反馈给 beego 官方。

Golang 语言 beego v2 之路由设置

01

介绍

beego 的路由功能非常强大,支持多种路由方式,包括固定路由,正则路由,自动路由等。

02

RESTful Controller 路由

beego 路由功能的执行顺序从前往后的优先级是:固定路由,正则路由和自动路由。beego 默认支持 RESTful 请求方式,即 Get HTTP 请求执行 Get 方法,Post HTTP 请求执行 Post 方法,Update HTTP 请求执行Update 方法,Delete HTTP 请求执行 Delete 方法。下面分别介绍这三种路由功能。

固定路由:

beego.Router("/", &controllers.MainController{})
beego.Router("/user", &controllers.UserController{})

如上所示,给 beego.Router() 方法传入两个参数,第一个参数是 URI,第二个参数是对应的 Controller。

固定路由规则中查找和请求 URL 相匹配的对象。这个匹配是全匹配规则,即如果用户请求的 URL 是 /hello/world,那么固定规则中 /hello 是不会匹配的,只有完全匹配才算匹配。如果匹配的话就进入逻辑执行,如果不匹配进入下一环节的正则匹配。

正则路由:

beego.Router("/user/:id([0-9]+)", &controllers.UserController{})
beego.Router("/user/:name([\\w]+)", &controllers.UserController{})
beego.Router("/user/:?id([0-9]+)", &controllers.UserController{})
beego.Router("/user/:id:int", &controllers.UserController{})
beego.Router("/user/:name:string", &controllers.UserController{})
beego.Router("/user/*.*", &controllers.UserController{})
beego.Router("/user/*", &controllers.UserController{})

正则匹配是进行正则的全匹配,这个正则是按照用户添加 beego 路由顺序来进行匹配的,也就是说,如果你在添加路由的时候你的顺序影响你的匹配。和固定匹配一样,如果匹配的话就进行逻辑执行,如果不匹配进入 Auto 匹配。

自动路由:

beego.AutoRouter(&controllers.UserController{})

首先把路由的控制器注册到自动路由中,然后 beego 就会通过反射获取该结构体中所有的实现方法,我们就可以通过访问 controller/method 这样的方式访问到对应的方法,除了前缀两个 /:controller/:method 的匹配之外,剩下的 url beego 会帮你自动化解析为参数,保存在 this.Ctx.Input.Params 当中。

如果用户注册了 AutoRouter,那么会通过 controller/method 这样的方式去查找对应的 Controller 和他内置的方法,如果找到就开始执行逻辑,如果找不到就跳转到监控判断。

自定义路由:

细心的读者可能已经发现,除了自动路由可以通过 method 名字访问对应的方法,固定路由和正则路由都是通过 HTTP Method 访问对应的方法,也就是说请求的 HTTP Method 和函数名一致,例如 GET 请求执行 Get 函数,POST 请求执行 Post 函数)。

如果用户期望自定义函数名,那么可以使用自定义路由,自定义路由默认不支持 RESTful 请求方式,需要指定对应 Http Method 的函数。

beego.Router("/user/list", &controllers.UserController, "*:List")
beego.Router("/user/list", &controllers.UserController, "get:List")
beego.Router("/user/list", &controllers.UserController, "post:List")
beego.Router("/user/list", &controllers.UserController, "put:List")
beego.Router("/user/list", &controllers.UserController, "delete:List")
beego.Router("/user/list", &controllers.UserController, "get,post:List")
beego.Router("/user/list", &controllers.UserController, "get:GetList;post:PostList")
beego.Router("/user/list", &controllers.UserController, "*:List;post:PostList")

自定义路由就是给 beego.Router() 方法传入第三个参数,用来设置对应 HTTP Method 的自定义函数名,规则如下:

  • 「*」星号代表所有 HTTP Method 都执行该函数。
  • 使用 httpmethod:funcname 格式设置。
  • 多个不同的格式之间使用「;」英文分号分隔。
  • 多个 HTTP Method 对应同一个函数,HTTP Method 之间使用「,」英文逗号分隔。
  • 如果同时存在「*」和 HTTP Method,优先执行 HTTP Method 对应的函数。

03

注解路由

除了上边讲的几种路由功能,beego 还支持注解路由,注解路由就是用户无需在 beego.router() 方法中注册路由,只需要在 beego.Include() 方法中注册相应的 controller,然后在 controller 的方法上写上 router 注释(// @router)。

注册 controller:

beego.Include(&controller@UserController)

controller 的方法上写上 router 注释:

type UserController struct {
  beego.Controller
}

// @router /user/list/:id([0-9]+) [get]
func (u *UserController) List() {
  u.Ctx.WriteString("UserController@List func\n")
  id := u.Ctx.Input.Param(":id")
  u.Ctx.WriteString(id)
}

只需上面简单两个步骤,beego 在 dev 模式下,就可以自动进行源码分析,生成的注释路由文件存放在 routers/ 目录中。

提醒:beego v2 不需要使用 beego.Include()注册 controller ,也可以自动生成注解路由文件,但是必须使用 beego.Include() 注册 controller,才可以 URL 访问到对应的方法。

04

命名空间

我们在接口开发时,经常要对接口的版本不断迭代升级,这时我们可以使用 beego 的 namespace,它非常强大,我们通过区分接口版本号演示它的使用方法。

ns := beego.NewNamespace("/v1",
    beego.NSRouter("/user/list", &controller.UserController{}, "get:List"),
)
beego.AddNamespace(ns)

通过使用 namespace 封装,上面的接口可以这样访问:

GET /v1/user/list

而且 namespace 还支持前置过滤,条件判断和无限嵌套,如果想要了解更多关于 namespace 的功能,可以参阅官方文档。

05

RESTful 基础路由

上面讲的路由功能,全部都是访问 controller/method 的方式,有时我们可能只是写一些简单的业务逻辑,不需要使用 controller,我们可以这么做,beego 还支持由 URI 和闭包函数组成的路由。

beego.Get("/get", func(ctx *context.Context) {
    ctx.WriteString("get method")
})

beego.Post("/post", func(ctx *context.Context) {
    ctx.WriteString("post method")
})

beego.Any("/any", func(ctx *context.Context) {
    ctx.WriteString("all method")
})

06

路由参数

路由参数:

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

this.Ctx.Input.Params() 是一个 map 类型。

07

总结

本文介绍了 beego 的所有路由方式和 beego 的 namespace,我们可以根据自己的业务需求选用最合适的路由方式,beego 的 namespace 在开发中非常有用,限于篇幅,我们只是简单介绍了一下 namespace,建议读者一定要通过阅读官方文档,了解更多关于 namespace 的用法。

bug list:

  1. 注解路由,不使用 beego.Include() 注册 controller,也可以生成注解路由文件,但是必须使用 beego.Include() 注册 controller,才可以通过 URL 访问到对应的方法。

以上 bug,已反馈给 beego 官方。

本文分享自微信公众号 – Go语言开发栈(golanghubcn)

GORM V2 写操作

01 概念

在项目开发中,数据库写操作包含新增、删除和修改,使用 GORM V2 可以更加安全和便捷进行写操作。

02 新增

普通创建 使用 GORM V2 创建记录,可以定义一个自定义结构体类型的变量,调用 Create 方法,通过入参结构体类型变量的指针来创建记录。

stu := Student{
  Name:  "rose",
  Age:   28,
  Email: "rose@88.com",
}
result := gormDB.Create(&stu)

通过获取变量的 ID,可以得到插入记录的主键 ID。

insertID := stu.ID
fmt.Printf("主键 ID:%d\n", insertID)

通过调用 Error 和 RowsAffected,可以分别获取插入错误和插入行数。

insertErr := result.Error
fmt.Printf("插入错误:%v\n", insertErr)
num := result.RowsAffected
fmt.Printf("影响行数:%d\n", num)

 

GORM V2 新增了选定字段和排除字段创建

选定字段创建 Select

gormDB.Select("Name", "Age").Create(&stu)

排除字段创建 Omit

gormDB.Omit("Age", "Email").Create(&stu)

 

批量创建 定义一个切片变量,通过调用 Create 方法,入参切片类型的变量,GORM 会生成一个单一的 sql 语句来插入所有数据,并回填主键的值。

stus := []Student{
  {    
    Name: "coco",    
    Age: 19,    
    Email: "coco@88.com",  
   },  
   {    
    Name: "bear",    
    Age: 12,    
    Email: "bear@88.com",  
   },
 }
 gormDB.Create(&stus)
 for _, stu := range stus {  
   fmt.Printf("ID:%d\n", stu.ID)
 }

 

GORM 支持根据 map 创建记录 根据 map 单条创建

stuMap := map[string]interface{}{
  "Name": "name1", 
   "Age": 22,  
   "Email": "name1@8.com",
 }
 gormDB.Model(&Student{}).Create(stuMap)

 

根据 map 批量创建

stusMap := []map[string]interface{}{
  {    
    "Name": "apple",    
    "Age": 20,    
    "Email": "apple@88.com",
  },  
  {    
    "Name": "pear", 
    "Age": 21,   
    "Email": "pear@88.com",  
  },
}
gormDB.Model(&Student{}).Create(stusMap)

需要注意的是,根据 map 创建,不会自动填充 gorm.Model 结构体定义的字段。

 

默认值

可以使用 GORM 标签 default 设置默认值,插入数据时,设置的默认值会被用于填充值为零值的字段。

需要注意的是,如果默认值本身是数据类型的零值,将不会被保存到数据库。对于数据库表的设置默认值的字段,需要预先在声明模型的 struct 字段上使用标签 default 设置默认值,否则会插入该字段数据类型的零值。

03 删除

单条删除 删除单条记录,删除对象需要指定主键,否则会触发批量删除并且会被阻止全局删除功能拦截。

stu := Student{}
stu.ID = 16
gormDB.Delete(&stu)

 

根据主键删除

GORM 支持根据内联条件指定删除对象的主键,但是只支持数据类型为整型主键。

gormDB.Delete(&Student{}, 2)
gormDB.Delete(&Student{}, "2")
gormDB.Delete(&Student{},[]int{8,9})

 

批量删除

GORM 可以根据指定的删除条件,批量删除全部匹配的记录。

gormDB.Where("email LIKE ?", "%88%").Delete(Student{})
gormDB.Delete(Student{}, "email LIKE ?", "%88%")

 

阻止全局删除 如果没有指定 WHERE 条件,GORM 不会执行删除操作,并返回 ErrMissingWhereClause 错误。

如果想要全局删除,必须指定 WHERE 条件,或者执行原生 SQL,或者启用 AllowGlobalUpdate 模式。

 

软删除

如果模型包含 gorm.DeletedAt 字段,将会启用软删除,软删除是指不会真的删除记录,而是会将 DeletedAt 字段设置为当前时间,并且被软删除的记录,不可以通过正常查询操作获取。

如果需要查询被软删除的记录,需要使用 Unscoped 方法。

 

永久删除 永久删除记录,也需要使用 Unscoped 方法。

gormDB.Unscoped().Where("id = ?", 5).Delete(&Student{})
gormDB.Unscoped().Where("id IN ?", []int{8,9}).Delete(&Student{})

 

04 修改

保存所有字段

调用 Save 方法更新数据,会保存所有字段,即使字段的值为字段类型的零值。

student := Student{}
gormDB.First(&student)
student.Name="cat1"
student.Age=0
student.Email="cat1@88.com"
gormDB.Save(&student)

 

更新单个列

使用 Update 方法更新单个列时,需要指定条件,否则会返回 ErrMissingWhereClause 的错误。

student := Student{}
student.ID = 15
gormDB.Model(&student).Update("email", "cat@gmail.com")
gormDB.Model(&Student{}).Where("age = ?", 19).Update("age", 21)

当使用 Model 方法时,并且该对象的主键有值,该值会被用于更新条件。

student := Student{}
student.ID = 15
gormDB.Model(&student).Where("email = ?", "cat@gmail.com").Update("name", "bigFace")

 

更新多个列

使用 Updates 方法更新多个列,GORM 支持 struct 和 map[string]interface{} 参数,需要注意的是,当使用 struct 作为参数时,GORM 只会更新字段的值不是字段类型的零值的字段。

struct

student := Student{}
student.ID = 1
gormDB.Model(&student).Updates(Student{Name: "book", Age: 20})

 

map

student := Student{}
student.ID = 15
gormDB.Model(&student).Updates(map[string]interface{}{"name" : "panda", "age": 30})

 

更新选定或排除字段

更新操作,也支持根据选定或排除字段进行更新。

选定字段 Select

student := Student{}
student.ID = 2
gormDB.Model(&student).Select("name").Updates(map[string]interface{}{"name":"lucy", "email":"lucy@88.com"})

 

排除字段 Omit

student := Student{}
student.ID = 3
gormDB.Model(&student).Omit("name").Updates(map[string]interface{}{"name": "dog", "age": 29, "email":"dog@gmail.com"})

 

批量更新

如果未通过 Model 方法指定记录的主键,GORM 则会执行批量更新。

struct

gormDB.Model(&Student{}).Where("name=?", "milk").Updates(Student{Name: "tom", Age: 18})

map

gormDB.Model(&Student{}).Where("name = ?", "frank").Updates(map[string]interface{}{"name":"milk", "email": "milk@88.com"})
gormDB.Table("students").Where("name = ?", "tom").Updates(map[string]interface{}{"name":"honey", "email" :"honey@88.com"})

 

阻止全局更新

如果未指定任何条件执行批量更新,默认情况下,GORM 不会执行该操作,并且会返回 ErrMissingWhereClause 错误。

如果希望执行全局更新,需要指定条件,或使用原生 SQL,或启用 AllowGlobalUpdate 模式。

 

更新的记录数和更新操作的错误

获取受影响的行数和更新操作的错误。

result := gormDB.Table("students").Where("name = ?", "honey").Updates(map[string]interface{}{"name":"life", "email" :"life@88.com"})
rows := result.RowsAffected
fmt.Printf("更新的记录数:%d\n", rows)
fmt.Println(result.Error)

本文分享自微信公众号 – Go语言开发栈(golanghubcn),作者:frankphper

GORM V2 读操作

01 概念

在项目开发中,大多数项目都是读多写少,关于使用 GORM 读取 MySQL,我们单独一篇文章讲述 GORM 是如何使读操作更加便捷的。

02 单条查询

关于单条查询,GORM 提供了三个方法,分别是 First、Last 和 Take,其中 First 和 Last 是根据主键升序和降序,使用 Limit 1 获取 1 条记录。Take 方法未指定排序字段,使用 Limit 1获取 1 条记录,排序规则根据数据库的实现。

其中 First 和 Last 仅在通过 struct 和给定 Model 值进行查询时才有效,但是 Take 方法不受此限制。

示例代码:

First 方法:

student := Student{}
gormDB.First(&student)
fmt.Println(student)

Take 方法:

student := Student{}
gormDB.Take(&student)
fmt.Println(student)

Last 方法:

student := Student{}
result := gormDB.Last(&student)
fmt.Println(student)

 

03查询所有

关于查询所有记录,GORM 提供了 Find 方法,获取全部记录。

示例代码:

var students  []Student
result := gormDB.Find(&students)
fmt.Println(students)

 

04 条件查询

关于条件查询,GORM 提供了 Where、Not 和 Or 方法。其中 Where 方法分别可以通过使用 String 条件、Struct 条件、Map 条件和主键 Slice 条件。

需要特别注意的是,当使用 Struct 条件时,GORM 只会查询非零值字段,也就是说如果你的字段是该字段类型的零值,将不会被用于构建查询条件。如果需要使用零值构建查询条件,可以使用 Map 构建查询条件。

示例代码:

Where 方法

String 条件:

student := Student{}
gormDB.Where("name = ?", "bear").First(&student)
fmt.Println(student)
var students []Student
gormDB.Where("name <> ?", "bear").Find(&students)
fmt.Println(students)
var students []Student
gormDB.Where("name IN ?", []string{"bear", "panda"}).Find(&students)
fmt.Println(students)
var students []Student
gormDB.Where("name LIKE ?", "%a%").Find(&students)
fmt.Println(students)
var students []Student
gormDB.Where("name = ? AND age > ?", "coco", 18).Find(&students)
fmt.Println(students)
var students []Student
lastWeek := time.Now().Add(time.Hour*24*-7)
gormDB.Where("updated_at > ?", lastWeek).Find(&students)
fmt.Println(students)
var students []Student
lastWeek := time.Now().Add(time.Hour*24*-7)
today := time.Now()
gormDB.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&students)
fmt.Println(students)

 

Struct 条件:

var student Student
gormDB.Where(&Student{Name: "coco", Age: 19}).Find(&student)
fmt.Println(student)

 

Map 条件:

var student Student
gormDB.Where(map[string]interface{}{"name": "coco", "age": 19}).Find(&student)
fmt.Println(student)

 

主键 Slice 条件:

var students []Student
gormDB.Where([]int{15,16,17}).Find(&students)
fmt.Println(students)

 

Not 方法: 普通 Not 条件:

var student Student
gormDB.Not("name = ?", "cat").First(&student)
fmt.Println(student)

 

Not In:

var students []Student
gormDB.Not(map[string]interface{}{"id":[]int{15,17}}).Find(&students)
fmt.Println(students)

 

Struct:

var student Student
gormDB.Not(Student{Name: "cat", Age: 18}).Find(&student)
fmt.Println(student)

 

主键 Slice:

var students []Student
gormDB.Not([]int{1,2,3,15,16}).Find(&students)
fmt.Println(students)

 

Or 方法:

普通 Or:

var students []Student
gormDB.Where("name = ?", "cat").Or("name = ?", "cat2").Find(&students)
fmt.Println(students)

 

Struct:

var students []Student
gormDB.Where("name = ?", "cat2").Or(Student{Name: "cat", Age: 19}).Find(&students)
fmt.Println(students)

 

Map:

var students []Student
gormDB.Where("name = ?", "cat2").Or(map[string]interface{}{"name":"cat", "age":19}).Find(&students)
fmt.Println(students)

 

05 指定字段查询

仔细的读者可能已经发现,上述的查询,全部都是查询数据库表的所有字段,这在 MySQL 查询优化上是大忌。使用 GORM 怎么指定字段查询呢?GORM 提供了一个 Select 方法,可以方便我们指定字段查询。

示例代码:

普通方式:

var students []Student
gormDB.Select("name", "age").Find(&students)
fmt.Println(students)

 

Slice 方式:

var students []Student
gormDB.Select([]string{"name", "age"}).Find(&students)
fmt.Println(students)

 

06 order 排序

在许多业务场景中,查询都需要用到排序,GORM 提供了 Order 方法,实现对结果集的排序功能。

示例代码:

普通方式:

var students []Student
gormDB.Order("id desc, age").Find(&students)
fmt.Println(students)

 

多个排序字段:

var students []Student
gormDB.Order("id desc").Order("age").Find(&students)
fmt.Println(students)

 

07 Limit & Offset

查询操作中,在某些业务场景中,需要限制查询的条数,和偏移查询记录的开始位置。针对此类业务场景,GORM 提供了 Limit 和 Offset 方法。

示例代码:

var students []Student
gormDB.Limit(5).Offset(1).Find(&students)
fmt.Println(students)

关于 GORM 的查询操作,还有一些其它高级用法,限于篇幅原因,不再一一列举,如有兴趣,可以通过查询 GORM 官方文档了解。

本文分享自微信公众号 – Go语言开发栈(golanghubcn),作者:frankphper

GORM V2 模型定义、约定、标签

01

概念

使用 GORM 操作数据库,需要了解 GORM 的约定和字段标签提供的约束。尽量遵循 GORM 已有的约定,但是如果约定不符合需求,也可以自定义配置,从而改变已有约定,达到满足需求的目的。

02

模型定义

与使用 Go 标准库 sql 包操作数据库表相同,使用 GORM 操作数据库,也需要先声明模型,模型一般是基于 Go 语言的基础数据类型、实现了 Scanner 和 Valuer 接口的自定义类型,以及它们的指针或别名。

示例代码:

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivedAt    sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

03

约定

GORM 已有约定

默认情况下,GORM 约定使用 ID 作为主键,使用结构体名的复数作为表名,字段名作为列名,使用 CreatedAt、UpdatedAt、DeletedAt时间追踪。如果约定与需求不符,可以自定义配置。

gorm.Model

GORM 定义了一个 gorm.Model 结构体,字段包括 ID、CreatedAt、UpdatedAt、DeletedAt,我们可以将它嵌入到我们自定义的结构体中,详情见 04 章节。

示例代码:

// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

04

嵌入结构体

对于匿名字段,GORM 会将其字段包含在父结构体中。

type User struct {
  gorm.Model
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
}

对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入。

type Author struct {
    Name  string
    Email string
}

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded"`
  Upvotes int32
}
// 等效于
type Blog struct {
  ID    int64
    Name  string
    Email string
  Upvotes  int32
}

并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀。

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded;embeddedPrefix:author_"`
  Upvotes int32
}
// 等效于
type Blog struct {
  ID          int64
    AuthorName  string
    AuthorEmail string
  Upvotes     int32
}

05

标签

字段级别权限控制

在 GO 语言中,根据名称的首字母大小写来定义是否可被导出,GORM 使用可导出的字段进行 CRUD 时拥有全部权限,另外,GORM 可以使用标签控制字段级别的权限,可以让一个字段的权限是只读、只写、只创建、只更新和忽略该字段。

示例代码:

type User struct {
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 读写操作均会忽略该字段
}

创建/更新时间追踪(纳秒、毫秒、秒、Time)

GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果您定义了他们,GORM 在创建/更新时会自动填充当前时间至这些字段,要使用不同名称的字段,您可以配置 autoCreateTim、autoUpdateTim 标签。

当然,你可以按照自己的需求自定义约定项。比如时间追踪默认是将为零值的创建时间CreatedAt 字段以当前时间填充,以当前时间戳秒数填充 UpdatedAt 字段,如果您想要保存 UNIX(毫/纳)秒时间戳,而不是 time,您只需简单地将 time.Time 修改为 int 即可,默认时间戳是 Unix 秒,你还可以使用标签将时间戳的单位改为纳秒或毫秒。

示例代码:

type User struct {
  CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
  UpdatedAt int       // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
  Created   int64 `gorm:"autoCreateTime"`      // 使用时间戳秒数填充创建时间
}

字段标签

标签是模型定义时的可选项,GORM 的标签不区分大小写,推荐使用驼峰式命名。我们会在后续章节中,陆续使用到 GORM 提供的字段标签。

查阅 GORM 字段标签:

https://gorm.io/zh_CN/docs/models.html#%E5%AD%97%E6%AE%B5%E6%A0%87%E7%AD%BE

Golang 语言 Web 框架 beego v2 之模板

01

介绍

beego 的模板处理引擎采用的是 Go 内置的 html/template 包进行处理,而且 beego 的模板处理逻辑是采用了缓存编译方式,也就是所有的模板会在 beego 应用启动的时候全部编译然后缓存在 map 里面。

02

模板处理

模板目录

beego 的默认模板目录是 views,也可以通过 beego.ViewPath = “viewPath” 指定模板目录。beego 会自动解析并缓存模板目录中的所有模板文件。但是在开发模式下,每次修改模板文件都会重新解析,并且不做缓存。

自动渲染

beego 会在调用完相应 method 方法之后自动调用 Render 函数,不需要用户手动调用渲染输出模板。如果不需要模板输出,可以在配置文件中配置:

autorender=false

或在 main.go 文件中设置配置变量:

web.AutoRender = false

模板标签

beego 默认使用{{}}双大括号作为模板标签,但是可能会和某些模板引擎使用的标签冲突,beego 可以通过配置文件或者设置配置变量:

web.TemplateLeft="<<"
web.TemplateRight=">>"

模板数据

模板数据是通过在 controller中 this.Data 获取的,Data 字段的类型是:

Data map[interface{}]interface{}

key 和 value 都是空接口类型的 map

模板名称

自动渲染模式中,如果没有在 controller 中没有设置任何的模板名称TplName,beego 会自动将 controller 名字,method 名字,和模板后缀拼接成一个字符串,作为模板名称。

c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt

beego 默认支持两种模板文件的后缀名,分别是 tpl 和 html。如果你的模板文件的后缀名不是这两种,需要设置配置变量:

web.AddTemplateExt(“模板文件的后缀名”)

用户还可以在 controller 中指定模板名称,beego 会自动在 viewPath 目录中查找该模板文件,设置方式如下:

this.TplName = "admin/add.tpl"

Layout 设计

beego 支持 Layout 设计,可以通过如下设置:

this.Layout = "admin/layout.html"
this.TplName = "admin/add.tpl"

在 layout.html 文件中,设置如下变量:

{{.LayoutContent}}

beego 就会首先解析 TplName 指定的文件,获取到内容后,赋值给 LayoutContent,最后渲染 layout.html 文件。

另外,还可以使用如下方式:

{{template "header.html" .}}
Logic code
{{template "footer.html" .}}

LayoutSection

根据惯例,js 文件放在 body 的末尾,css 文件放到 head 中,所以 layout.html 文件只有一个 LayoutContent 是不够用的。

所以,在 controller 中还有一个 LayoutSection 属性,可以给 Layout.html 设置多个 section,每个 section 都可以包含各自的子模板页面。

type BlogsController struct {
  web.Controller
}

func (this *BlogsController) Get() {
  this.Layout = "layout_blog.tpl"
  this.TplName = "blogs/index.tpl"
  this.LayoutSections = make(map[string]string)
  this.LayoutSections["HtmlHead"] = "blogs/html_head.tpl"
  this.LayoutSections["Scripts"] = "blogs/scripts.tpl"
  this.LayoutSections["Sidebar"] = "blogs/side_bar.tpl"
}

renderform 使用

struct:

type User struct {
  Id    int         `form:"-"`
  Name  interface{} `form:"username"`
  Age   int         `form:"age,text,年龄:"`
  Sex   string
  Intro string `form:",textarea"`
}

StructTag 的定义用的标签为 form,和 ParseForm 方法共用一个标签,标签后面有三个可选参数,用 , 分割。

第一个参数为表单中类型的 name 的值,如果为空,则以 struct field name 为值。

第二个参数为表单组件的类型,如果为空,则为 text。表单组件的标签默认为 struct field name 的值,否则为第三个值。

如果要忽略一个字段,有两种办法,一是:字段名小写开头,二是:form 标签的值设置为 –

如果 form 标签只有一个值,则为表单中类型 name 的值,除了最后一个值可以忽略外,其他位置的必须要有 ,号分割,如:form:”,,姓名:”

现在的代码版本只能实现固定的格式,用 br 标签实现换行,无法实现 css 和 class 等代码的插入。所以,要实现 form 的高级排版,不能使用 renderform 的方法,而需要手动处理每一个字段。

controller:

func (this *AddController) Get() {
  this.Data["Form"] = &User{}
  this.TplName = "index.tpl"
}

Form 的参数必须是一个 struct 的指针。

template:

<form action="" method="post">
{{.Form | renderform}}
</form>

上面的代码生成的表单为:

Name: <input name="username" type="text" value="test"></br>
年龄:<input name="age" type="text" value="0"></br>
Sex: <input name="Sex" type="text" value=""></br>
Intro: <input name="Intro" type="textarea" value="">

03

模板语法

模板中支持的 go 语言符号

{{"string"}} // 一般 string
{{`raw string`}} // 原始 string
{{'c'}} // byte
{{print nil}} // nil 也被支持

模板中的 pipeline

可以是上下文的变量输出,也可以是函数通过管道传递的返回值

{{. | FuncA | FuncB | FuncC}}

当 pipeline 的值 false 或 0,nil 的指针或 interface,长度为 0 的 array、slice、map、string,那么这个 pipeline 被认为是空。

if … else … end

{{if pipeline}}{{end}}

if 判断时,pipeline 为空时,相当于判断为 false

this.Data["IsLogin"] = true
this.Data["IsHome"] = true
this.Data["IsAbout"] = true

支持嵌套的循环

{{if .IsHome}}
{{else}}
  {{if .IsAbout}}{{end}}
{{end}}

也可以使用 else if

{{if .IsHome}}
{{else if .IsAbout}}
{{else}}
{{end}}

range … end

{{range pipeline}}{{.}}{{end}}

pipeline 支持的类型为 array,slice,map,channel

range 循环内部的「.」改变为以上类型的子元素

对应的值长度为 0 时,range 不会执行,「.」不会改变

pages := []struct {
  Num int
}{{10}, {20}, {30}}

this.Data["Total"] = 100
this.Data["Pages"] = pages

使用「.Num」输出子元素的 Num 属性,使用「$.」引用模板中的根级上下文

{{range .Pages}}
  {{.Num}} of {{$.Total}}
{{end}}

使用创建的变量,在这里和 go 中的 range 用法相同

{{range $index, $elem := .Pages}}
  {{$index}} - {{$elem.Num}} - {{.Num}} of {{$.Total}}
{{end}}

range 也支持 else

{{range .Pages}}
{{else}}
  {{/* 当 .Pages 为空 或者 长度为 0 时会执行这里 */}}
{{end}}

with … end

{{with pipeline}}{{end}}

with 用于重定向 pipeline

{{with .Field.NestField.SubField}}
  {{.Var}}
{{end}}

也可以对变量赋值操作

{{with $value := "My name is %s"}}
  {{printf . "slene"}}
{{end}}

with 也支持 else

{{with pipeline}}
{{else}}
  {{/* 当 pipeline 为空时会执行这里 */}}
{{end}}

define

define 可以用来定义自模板,可用于模块定义和模板嵌套

{{define "loop"}}
  <li>{{.Name}}</li>
{{end}}

使用 template 调用模板

<ul>
  {{range .Items}}
    {{template "loop" .}}
  {{end}}
</ul>

template

{{template "模板名" pipeline}}

将对应的上下文 pipeline 传给模板,才可以在模板中调用

beego 中支持直接载入文件模板

{{template "path/to/head.html" .}}

beego 会依据你设置的模板路径读取 head.html

在模板中可以接着载入其他模板,对于模板的分模块处理很有用处

注释

允许多行文本注释,不允许嵌套

{{/* comment content
support new line */}}

04

模板函数

基本函数

变量可以使用符号 | 在函数间传递

{{.Con | markdown | addlinks}}
{{.Name | printf "%s"}}

使用括号

{{printf "nums is %s %d" (printf "%d %d" 1 2) 3}}

and

{{and .X .Y .Z}}

and 会逐一判断每个参数,将返回第一个为空的参数,否则就返回最后一个非空参数

or

{{or .X .Y .Z}}

or 会逐一判断每个参数,将返回第一个非空的参数,否则就返回最后一个参数

call

{{call .Field.Func .Arg1 .Arg2}}

call 可以调用函数,并传入参数

调用的函数需要返回 1 个值或者 2 个值,返回两个值时,第二个值用于返回 error 类型的错误。返回的错误不等于 nil 时,执行将终止。

Index

index 支持 map, slice, array, string,读取指定类型对应下标的值

this.Data["Maps"] = map[string]string{"name": "Beego"}
{{index .Maps "name"}}

len

{{printf "The content length is %d" (.Content|len)}}

返回对应类型的长度,支持类型:map, slice, array, string, chan

not

not 返回输入参数的否定值,if true then false else true

print

对应 fmt.Sprint

printf

对应 fmt.Sprintf

println

对应 fmt.Sprintln

urlquery

{{urlquery "http://beego.me"}}

将返回

http%3A%2F%2Fbeego.me

eq / ne / lt / le / gt/ ge

这类函数一般配合在 if 中使用

eq 和其他函数不一样的地方是,支持多个参数,和下面的逻辑判断相同

arg1==arg2 || arg1==arg3 || arg1==arg4 ...

与 if 一起使用

{{if eq true .Var1 .Var2 .Var3}}{{end}}
{{if lt 100 200}}{{end}}

内置模板函数

dateformat

date

compare

substr

html2str

str2html

htmlquote

htmlunquote

renderform

assets_js

assets_css

config

map_get

// In controller
Data["m"] = map[string]interface{} {
    "a": 1,
    "1": map[string]float64{
        "c": 4,
    },
}

// In view
{{ map_get .m "a" }} // return 1
{{ map_get .m 1 "c" }} // return 4

urlfor

{{urlfor "TestController.List"}}

自定义模板函数

beego 支持用户定义模板函数,但是必须在 web.Run() 调用之前,设置如下:

func hello(in string)(out string){
    out = in + "world"
    return
}

web.AddFuncMap("hi",hello)

定义之后你就可以在模板中这样使用了:

{{.Content | hi}}

05

静态文件

Go 语言内部其实已经提供了 http.ServeFile,通过这个函数可以实现静态文件的服务。beego 针对这个功能进行了一层封装,通过下面的方式进行静态文件注册:

web.SetStaticPath("/static","public")
  • 第一个参数是路径,url 路径信息
  • 第二个参数是静态文件目录(相对应用所在的目录)

beego 支持多个目录的静态文件注册,用户可以注册如下的静态文件目录:

web.SetStaticPath("/images","images")
web.SetStaticPath("/css","css")
web.SetStaticPath("/js","js")

设置了如上的静态目录之后,用户访问 /images/login/login.png,那么就会访问应用对应的目录下面的 images/login/login.png 文件。如果是访问 /static/img/logo.png,那么就访问 public/img/logo.png文件。

默认情况下 beego 会判断目录下文件是否存在,不存在直接返回 404 页面,如果请求的是 index.html,那么由于 http.ServeFile 默认是会跳转的,不提供该页面的显示。

因此 beego 可以设置

web.BConfig.WebConfig.DirectoryIndex=true 这样来使得显示 index.html 页面。而且开启该功能之后,用户访问目录就会显示该目录下所有的文件列表。

06

分页

这里所说的分页,指的是大量数据显示时,每页显示固定的数量的数据,同时显示多个分页链接,用户点击翻页链接或页码时进入到对应的网页。分页算法中需要处理的问题:

  1. 当前数据一共有多少条。
  2. 每页多少条,算出总页数。
  3. 根据总页数情况,处理翻页链接。
  4. 对页面上传入的 Get 或 Post 数据,需要从翻页链接中继续向后传。
  5. 在页面显示时,根据每页数量和当前传入的页码,设置查询的 Limit 和 Skip,选择需要的数据。
  6. 其他的操作,就是在 View 中显示翻页链接和数据列表的问题了。

模板处理过程中经常需要分页,那么如何进行有效的开发和操作呢?我们开发组针对这个需求开发了如下的例子,希望对大家有用

  • 工具类 https://github.com/beego/wetalk/blob/master/modules/utils/paginator.go
  • 模板 https://github.com/beego/wetalk/blob/master/views/base/paginator.html
  • 使用方法 https://github.com/beego/wetalk/blob/master/routers/base/base.go#L458

07

总结

本文整理了 beego 关于模板的使用方法,文章内容来自 beego 官方手册,作者提供的仅是 beego 模板的学习路径,读者也可以直接阅读官方手册。

参考资料:

https://beego.me/docs/intro/

Golang 语言 Web 框架 beego v2 之写操作

01

介绍

beego ORM 是一个强大的 Go 语言 ORM 框架。她的灵感主要来自

Django ORM 和 SQLAlchemy。

已支持的数据库驱动有MySQL、PostgreSQL 和 Sqlite3。

beego v2.x 和 beego v1.x 在 ORM 上的区别是,beego v2.x 的 ORM 对象被设计为无状态的,它是线程安全的,建议大家在使用时,一个数据库只对应一个 ORM 对象。

本文全篇都是以 MySQL 为例。

02

安装

如果您使用的是 beego 框架,beego ORM 已默认安装,如果您没有使用 beego 框架,可使用 go get 安装 beego ORM:

go get github.com/astaxie/beego/client/orm

03

注册

注册驱动

参数 1 是驱动名称,参数 2 是驱动类型,需要注意的是,不要忘记导入 MySQL 驱动。

注册数据库

beego ORM 必须注册一个别名为 default 的数据库,作为默认使用。参数 1 是数据库的别名,用来在 ORM 中切换数据库使用,参数 2 是驱动名称,参数 3 是对应的 dataSource。

设置数据库最大空闲连接数

根据数据库的别名,设置数据库的最大空闲连接数。

设置数据库最大连接数

orm.SetMaxOpenConns("default", 10)

注册模型

注册模型,同时注册多个模型,使用英文逗号分隔。

使用表前缀注册模型

使用表前缀注册模型,同时注册多个模型,使用英文逗号分隔。

注册模型和使用表前缀注册模型,不可同时使用,重复注册模型,会导致成程序 panic。

04

自动建表

默认表名

AuthUser -> auth_user
Auth_User -> auth__user
DB_AuthUser -> d_b__auth_user

自定义表名

如果设置了表前缀,自定义表名会在开头拼接上前缀。

自定义存储引擎

默认使用 MySQL 数据库设置的存储引擎,如果您需要在程序中指定存储引擎,可以使用以上方式设置。

设置参数

在结构体中,可以使用 orm 标签设置数据库的属性,限于篇幅,具体使用方法请参考官方手册。

自动建表

参数 1 是数据库别名,参数 2 是布尔类型的 force,参数 3 是布尔类型的 verbose。force 为 true 代表 drop table 后再建表;verbose 为 true 代表打印建表的执行过程。

自动建表功能在 force 为 false 时,会自动创建新增的字段和索引。删除或修改操作,需要用户自行手动处理。

05

新增

单条插入:

func (u *UserController) Create() {
  o := orm.NewOrm() // 创建一个 Ormer
  name := u.GetString("name", "unknow")
  age, _ := u.GetUint8("age", 0)
  user := &models.User{
    Name: name,
    Age: age,
  }
  id, err := o.Insert(user)
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Println("id:", id)
}

Insert 方法插入单条数据,接收参数必须是指针类型,返回值有两个,分别是自增id 和 err。

批量插入:

func (u *UserController) Create() {
  o := orm.NewOrm() // 创建一个 Ormer
  users := []models.User{}
  _ = json.Unmarshal(u.Ctx.Input.RequestBody, &users)
  rows, err := o.InsertMulti(2, users)
  if err != nil {
    log.Fatalln(err)
    return
  }
  fmt.Println("rows:", rows)
}

InsertMulti 方法批量插入数据,参数 1 是批量插入的数量,参数 2 是 slice,参数 1 的值为 1 时,将会按照顺序插入 slice 中的数据。返回值有两个,分别是插入行数和 err。

06

修改

func (u *UserController) Update() {
  web.BConfig.WebConfig.AutoRender = false
  o := orm.NewOrm()
  user := models.User{}
  _ = json.Unmarshal(u.Ctx.Input.RequestBody, &user)
  rows, err := o.Update(&user, "Name")
  if err != nil {
    log.Println(err.Error())
    return
  }
  fmt.Println("rows:", rows)
}

Update 方法更新数据,参数 1 是更新对象,参数 2 是更新的字段,多个字段用英文逗号分隔,默认更新多有字段。返回值有两个,分别是更新行数和err。

07

删除

func (u *UserController) Delete() {
  web.BConfig.WebConfig.AutoRender = false
  o := orm.NewOrm()
  id, _ := u.GetUint64("id", 0)
  user := models.User{
    Id: id,
  }
  rows, err := o.Delete(&user)
  if err != nil {
    log.Println(err.Error())
    return
  }
  fmt.Println("rows:", rows)
}

Delete 方法删除数据,参数是删除对象,返回值有两个,分别是更新行数和 err。

08

总结

本文开篇介绍了 beego ORM 的设计思路和已支持的数据库驱动,并且介绍了 v2 和 v1 的区别,然后详细介绍了 beego ORM 的安装和使用方法。关于更多细节,请参阅官方手册。

参考资料:

https://beego.me/docs

Golang 语言 Web 框架 beego v2 之读操作

01

介绍

beego ORM 是一个强大的 Go 语言 ORM 框架。她的灵感主要来自

Django ORM 和 SQLAlchemy。

已支持的数据库驱动有MySQLPostgreSQL 和 Sqlite3。

beego v2.x 和 beego v1.x 在 ORM 上的区别是,beego v2.x 的 ORM 对象被设计为无状态的,它是线程安全的,建议大家在使用时,一个数据库只对应一个 ORM 对象。

本文全篇都是以 MySQL 为例。

关于 beego ORM 的安装和注册,已在「Golang 语言 Web 框架 beego v2 之写操作」中介绍,本文不再赘述。

02

普通查询

beego ORM 提供了两个普通查询的方法,分别是 Read 和 ReadOrCreate。

Read 方法默认把主键作为查询条件,也可以指定字段作为查询条件,如果指定字段作为查询条件,需要在 Read 方法的第二个参数中传入指定字段的名称。

示例代码:

Read 方法,主键查询

func (u *UserController) Read() {
  o := orm.NewOrm()
  user := &models.User{
    Id: 2,
  }
  err := o.Read(user)
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("user:%+v\n", user)
}

Read 方法,指定字段查询

func (u *UserController) Read() {
  o := orm.NewOrm()
  user := &models.User{
    Name: "Lucy",
  }
  err := o.Read(user, "Name")
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("user:%+v\n", user)
}

ReadOrCreate 方法默认必须传入一个参数作为条件字段,同时支持多个参数作为条件字段。根据条件字段从数据库中读取行,如果不存在,就插入一行。

ReadOrCreate 方法返回三个值,分别为一个 bool 类型,代表是否新插入一行;一个 int64 类型,代表查询对象(或新插入)的 Id;和一个 error 类型的错误。

示例代码:

ReadOrCreate 方法

func (u *UserController) Read() {
  o := orm.NewOrm()
  user := &models.User{
    Name: "Alan",
    Age: 37,
  }
  created, id, err := o.ReadOrCreate(user,"Name", "Age")
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("Created:%t,id:%d\n", created, id)
  fmt.Printf("user:%+v\n", user)
}

03

高级查询

beego ORM 高级查询是通过获取一个 QuerySeter 对象,使用 QuerySeter 对象的方法实现高级查询。

QuerySeter 接口包含的方法:

type QuerySeter interface {
    Filter(string, ...interface{}) QuerySeter
    FilterRaw(string, string) QuerySeter
    Exclude(string, ...interface{}) QuerySeter
    SetCond(*Condition) QuerySeter
    GetCond() *Condition
    Limit(limit interface{}, args ...interface{}) QuerySeter
    Offset(offset interface{}) QuerySeter
    GroupBy(exprs ...string) QuerySeter
    OrderBy(exprs ...string) QuerySeter
    ForceIndex(indexes ...string) QuerySeter
    UseIndex(indexes ...string) QuerySeter
    IgnoreIndex(indexes ...string) QuerySeter
    RelatedSel(params ...interface{}) QuerySeter
    Distinct() QuerySeter
    ForUpdate() QuerySeter
    Count() (int64, error)
    Exist() bool
    Update(values Params) (int64, error)
    Delete() (int64, error)
    PrepareInsert() (Inserter, error)
    All(container interface{}, cols ...string) (int64, error)
    One(container interface{}, cols ...string) error
    Values(results *[]Params, exprs ...string) (int64, error)
    ValuesList(results *[]ParamsList, exprs ...string) (int64, error)
    ValuesFlat(result *ParamsList, expr string) (int64, error)
    RowsToMap(result *Params, keyCol string, valueCol string) (int64, error)
    RowsToStruct(ptrStruct interface{}, keyCol string, valueCol string) (int64, error)
}

QuerySeter 对象

在介绍 QuerySeter 对象的方法之前,先给大家介绍如何获取一个 QuerySeter 对象,获取一个 QuerySeter 对象有三种方式,第一种是调用 ormer 的QueryTable 方法,参数传入一个 string 类型的表名;第二种是调用 ormer 的QueryTable 方法,参数传入一个结构体的地址;第三种是调用 ormer 的QueryTable 方法,参数传入一个指针类型的结构体;

示例代码:

var user models.User
err := o.QueryTable("beego_user").One(&user)
err = o.QueryTable(&user).One(&user)
err = o.QueryTable(new(models.User)).One(&user)

本小节我们主要介绍一下 One 方法、All 方法和 Count 方法。

One 方法

One 方法返回单条记录,默认情况下,返回主键升序的第一条记录。如果指定查询条件,则返回符合查询条件的一条记录,如果符合查询条件的记录大于 一条,则返回错误。

One 方法默认返回记录的所有字段,如果需要指定返回的字段,可以在 One 方法中传入需要返回的字段名称,多个字段名称以英文逗号分隔,未指定的返回字段,返回该字段的类型零值。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
  var user models.User
  // string 类型的表名
  // err := o.QueryTable("beego_user").One(&user)
  // 结构体的地址
  // err := o.QueryTable(&user).One(&user)
  // 使用对象作为表名
  err := o.QueryTable(new(models.User)).One(&user)
  // 指定返回字段,其他字段返回字段类型的零值
  // err := o.QueryTable(new(models.User)).One(&user, "Id", "Name")
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("user:%+v\n", user)
}

All 方法

All 方法返回对应的结果集对象,默认受 Limit 限制,最多显示 1000 条数据。All 方法的参数可以接收 []Type 和 *[]Type 两种形式的切片,如果需要指定查询的字段,可以在第二个参数开始传入字段名称,多个字段名称以英文逗号分隔,未指定查询的字段,返回字段类型的零值。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
  var users []models.User
  rows, err := o.QueryTable("beego_user").All(&users)
  // 指定返回字段,其他字段返回字段类型的零值
  // rows, err := o.QueryTable("beego_user").All(&users, "Id", "Name")
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("rows:%d users:%+v\n", rows, users)
}

Count 方法

Count 方法返回结果集行数。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
  num, err := o.QueryTable(new(models.User)).Count()
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Println("num:", num)
}

04

条件查询

上一小节介绍的查询方式,都没有使用查询条件,本小节内容介绍条件查询,在介绍条件查询之前,先来介绍一下 expr,expr 是 QuerySeter 用于描述字段和描述sql 操作符的一种表达方式。

字段组合的前后顺序依照表的关系,比如 User 表拥有 Profile 的外键,那么对 User 表查询对应的 Profile.Age 为条件,则使用 Profile__Age。

注意,字段的分隔符号使用双下划线 __,除了描述字段, expr 的尾部可以增加操作符以执行对应的 sql 操作。比如 Profile__Age__gt 代表 Profile.Age > 18 的条件查询。

expr 示例代码:

qs.Filter("id", 1) // WHERE id = 1
qs.Filter("profile__age", 18) // WHERE profile.age = 18
qs.Filter("Profile__Age", 18) // 使用字段名和 Field 名都是允许的
qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18
qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18
qs.Filter("profile__age__in", 18, 20) // WHERE profile.age IN (18, 20)

qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000

注释后面将描述对应的 sql 语句,仅仅是描述 expr 的类似结果,并不代表实际生成的语句。

表达式和操作符

在介绍 QuerySeter 的方法之前,先介绍表达式和操作符,表达式和操作符适用于 QuerySeter 的所有方法。

表达式

  • 等于
  • 大于 gt
  • 大于等于 gte
  • 小于 lt
  • 小于等于 lte
  • IN
  • isnull (true:isnull / false:is not null)

操作符

exact 等于(区分字母大小写)iexact 等于(不区分大小写)contains Like(区分大小写)icontains Like( 不区分大小写)startswith (前置模糊查询,区分大小写)istartswith(前置模糊查询,不区分大小写)endswith(后置模糊查询,区分大小写)iendswith(后置模糊查询,不区分大小写)

QuerySeter 的方法

Filter 包含

Filter 方法用来过滤查询结果,起到「包含条件」的作用。

Exclude 排除

Exclude 方法用来过滤查询结果,起到「排除条件」的作用。

Limit 限制条数

Limit 方法限制最大返回的记录数,默认值为 1000。第二个参数可以设置 offset,需要特别注意的是,这里的 limit / offset 和原生 sql 中的 limit / offset 是反过来的。

Offset 偏移

Offset 方法用来设置偏移量。

OrderBy 排序 "column" means ASC, "-column" means DESC.

OrderBy 方法用于排序,参数使用 expr 表达方式,默认是 ASC 排序规则,在 expr 前面用减号「-」表示 DESC 排序规则。

Distinct 方法

Distinct 方法返回指定字段不重复的查询结果。

Exist 是否存在

Exist 方法用于判断符合查询条件的结果是否存在。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
  // 条件查询
  var users []models.User
  // Filter 包含
  // 表达式和操作符
  // 等于
  err := o.QueryTable(new(models.User)).Filter("id", 2).One(&users)
  // 大于
  // num, err := o.QueryTable(new(models.User)).Filter("id__gt", 9).All(&users)
  // 大于等于
  // num, err := o.QueryTable(new(models.User)).Filter("id__gte", 9).All(&users)
  // 小于
  // num, err := o.QueryTable(new(models.User)).Filter("id__lt", 5).All(&users)
  // 小于等于
  // num, err := o.QueryTable(new(models.User)).Filter("id__lte", 5).All(&users)
  // IN
  // num, err := o.QueryTable(new(models.User)).Filter("id__in", 2, 4).All(&users)
  // isnull (true:isnull / false: is not null)
  // num, err := o.QueryTable(new(models.User)).Filter("id__isnull", false).All(&users)
  // num, err := o.QueryTable(new(models.User)).Filter("id__isnull", true).All(&users)

  // exact 等于(区分字母大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__exact", "frank").All(&users)
  // iexact 等于(不区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__iexact", "frank").All(&users)

  // contains Like(区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__contains", "frank").All(&users)
  // icontains Like( 不区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__icontains", "frank").All(&users)

  // startswith (前置模糊查询,区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__startswith", "fran").All(&users)
  // istartswith(前置模糊查询,不区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__istartswith", "fran").All(&users)

  // endswith(后置模糊查询,区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__endswith", "er").All(&users)
  // iendswith(后置模糊查询,不区分大小写)
  // num, err := o.QueryTable(new(models.User)).Filter("name__iendswith", "er").All(&users)

  // Exclude 排除
  // num, err := o.QueryTable(new(models.User)).Exclude("name__exact", "frank").All(&users)

  // Limit 限制条数
  // num, err := o.QueryTable(new(models.User)).Limit(4).All(&users)

  // Offset 偏移
  // num, err := o.QueryTable(new(models.User)).Offset(4).All(&users)

  // OrderBy 排序 "column" means ASC, "-column" means DESC.
  // num, err := o.QueryTable(new(models.User)).OrderBy("id").All(&users)
  // num, err := o.QueryTable(new(models.User)).OrderBy("-id").All(&users)

  // Distinct 去重
  // num, err := o.QueryTable(new(models.User)).Filter("id__gt", 9).Distinct().All(&users, "Age")

  // Exist 是否存在
  // isExisted := o.QueryTable(new(models.User)).Filter("name__exact", "frank1").Exist()
  // fmt.Println("isExisted:", isExisted)

  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("user:%+v\n", users)
}

05

原生 SQL 查询

beego ORM 原生 SQL 查询,通过获取一个 RawSeter 对象,使用 RawSeter 对象的 Raw 方法,实现原生 SQL 查询。

Raw 方法,参数 1 是原生 sql 语句的字符串,参数 2 是原生 sql 语句的参数,该参数支持模型结构体,切片和数组。

RawSeter 接口的方法:

type RawSeter interface {
    Exec() (sql.Result, error)
    QueryRow(containers ...interface{}) error
    QueryRows(containers ...interface{}) (int64, error)
    SetArgs(...interface{}) RawSeter
    Values(container *[]Params, cols ...string) (int64, error)
    ValuesList(container *[]ParamsList, cols ...string) (int64, error)
    ValuesFlat(container *ParamsList, cols ...string) (int64, error)
    RowsToMap(result *Params, keyCol string, valueCol string) (int64, error)
    RowsToStruct(ptrStruct interface{}, keyCol string, valueCol string) (int64, error)
    Prepare() (RawPreparer, error)
}

接下来,我们来介绍一下 QueryRow 方法和 QueryRows 方法。

QueryRow 方法

QueryRow 方法返回单条查询数据,不定长参数接收指针类型。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
    var user models.User
  err := o.Raw("SELECT id,name,age FROM beego_user WHERE id = ?", 2).QueryRow(&user)
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("user:%+v\n", user)
}

QueryRows 方法

QueryRows 方法返回多条查询数据,不定长参数接收指针类型。返回结果是查询结果集的数量和错误。

示例代码:

func (u *UserController) Read() {
  o := orm.NewOrm()
  var users []models.User
  ids := []int{1,3,5}
  num, err := o.Raw("SELECT id,name,age FROM beego_user WHERE id IN (?,?,?)", ids).QueryRows(&users)
  if err != nil {
    log.Fatalln(err.Error())
    return
  }
  fmt.Printf("nums:%d user:%+v\n", num, users)
}

06

总结

本文主要介绍 beego ORM 的读操作,包含普通查询、高级查询和原生 SQL 查询,先是介绍了普通查询,然后是介绍高级查询,包含 expr 表达式,QuerySeter 接口和其部分方法的使用,最后介绍了 RawSeter 接口和其部分方法的使用。限于篇幅,没有介绍关联查询和构造查询,关于未提及的内容,读者朋友可以参考官方手册。

参考资料:

https://beego.me/docs

 

Golang 语言 beego v
滚动到顶部