Beego快速入门教程
beego 是一个快速开发 Go 应用的 HTTP 框架,可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,相对于echo框架仅包含路由和控制器核心模块,beego是一个完整的MVC框架包括路由&控制器、model 数据库ORM封装、view模板处理。
下面一步步的介绍如何快速开始一个项目。
1.beego的流程设计
流程图说明:
-
http请求从左侧main入口函数开始进入框架
-
UrL路由解析然后确定执行那个控制器(controller)
-
执行请求前的过滤器 (过滤器一般用来拦截请求,例如做api签名校验,session处理,安全验证等等)
-
执行控制器 (控制器根据需要调用model,session, 日志等模块)
-
执行请求后的过滤器
-
视图输出返回给用户
2.安装包
安装beego核心包
go get -u github.com/beego/beego/v2
安装orm包用于操作数据库,beego的orm包是独立的模块需要单独安装
go get github.com/beego/beego/v2/client/orm
安装mysql驱动, 必须安装mysql驱动,orm包才能工作
go get github.com/go-sql-driver/mysql
安装bee工具包,这个是beego开发的辅助工具,用于快速创建项目,运行项目以及打包项目
go get -u github.com/beego/bee/v2
提示:安装bee包后,需要将$GOPATH/bin路径添加到环境变量path中, 否则会提示找不到bee命令,$GOPATH代表的就是你的GOPATH路径,如果你不知道GOPATH路径在哪里,执行下go env命令,会列出所有go相关的环境变量,从里面找到GOPATH路径。
3.使用bee创建项目
下面使用bee快速创建一个新的项目, 打开命令窗口,进入$GOPATH/src目录,然后执行bee命令。
bee new tizi365
bee工具在当前目录创建了一个tizi365的目录,里面包含了beego项目的基本结构。
提示: 如果你熟悉beego项目结构也可以手动创建项目,当然为了效率还是建议用bee快速创建。
4.项目结构
下面介绍下项目结构
tizi365
├── conf - 配置文件存放目录
│ └── app.conf - beego应用配置文件,里面包含一些默认的配置包括启动端口、运行模式等等
├── controllers - 控制器目录
│ └── default.go
├── main.go - 入口文件
├── models - model目录,存放我们的业务逻辑和数据库相关的操作
├── routers - 路由配置目录,主要存放我们各个业务模块的路由设置
│ └── router.go
├── static - 静态资源目录,默认静态资源访问url为 "http://域名/static/资源路径"
│ ├── css
│ ├── img
│ └── js
├── tests - 单元测试脚本目录
│ └── default_test.go
└── views - 视图模板目录
└── index.tpl
5.运行项目
前面已经创建了项目,现在可以把项目运行起来看看效果.
通过bee工具运行项目, 首先在命令创建进入 "项目根目录"。
bee run
提示:bee运行项目,支持热编译,就是如果你修改了go文件,bee会自动重新编译,你只要刷新页面就可以看到最新的效果,不需要手动编译。
然后通过浏览器访问: http://localhost:8080 , 可以看到如下效果:
提示:你也可以直接运行main.go文件, 例如: go run main.go,这种方式每次修改文件,需要手动编译。
6.控制器逻辑
我们看下beego控制器怎么写? 文件: tizi365/controllers/default.go
package controllers
import (
beego "github.com/beego/beego/v2/server/web"
)
// 定义一个控制器结构体
// 我们一般一个模块定义一个控制器
type MainController struct {
// 嵌套beego基础控制器,在go语言中嵌套struct,就类似继承的概念。
// 这里就相当于,继承了beego.Controller的方法和属性。
beego.Controller
}
// 覆盖beego.Controller的Get方法,用于处理RESTful请求中的get请求
// beego.Controller默认支持多种RESTful方法,例如:Post、Put、Delete等等
func (c *MainController) Get() {
// Data是继承过来的属性,是map类型,可以保存任意类型数据,主要用于保存请求响应数据
// 我们可以通过Data将参数,传入视图模板文件。
// 这里设置了两个参数
c.Data["Website"] = "tizi365.com"
c.Data["Email"] = "tizi365@demo.com"
// 设置需要渲染的模板文件,框架会去views目录查找这个模板文件
c.TplName = "index.tpl"
}
7.设置Url路由
下面看看怎么设置路由。
package routers
import (
"tizi365/controllers"
beego "github.com/beego/beego/v2/server/web"
)
// go 包初始化函数,go语言中在导入一个包的时候,如果被导入包存在init函数,会执行init函数
// 因此这里可以使用init函数初始化路由设置
func init() {
// 使用beego.Router函数,注册路由规则。
// 第一个参数是url路由,第二个参数是控制器
// 这里的意思就是将访问 / 这个url的请求,交给controllers.MainController控制器处理。
beego.Router("/", &controllers.MainController{})
}
如果我们增加下面路由设置:
beego.Router("/tizi365", &controllers.MainController{})
访问:http://localhost:8080/tizi365 和 http://localhost:8080/ 得到的结果一样,因为这两个url地址都是由同一个控制器处理。
路由规则说明: 大家可能发现上面的路由注册规则,只是定义了,那个url由那个控制器执行,但是没有说明url请求由控制器的那个函数执行, 一个控制器可以包含多个函数;
beego RESTful路由规则,默认是通过 请求方法 确认由那个控制器方法执行,例如get请求,由Get方法执行,POST请求由Post方法执行。
8.编写model逻辑
这里我们看一个mysql数据库操作的例子。
8.1.定义表结构:
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`username` varchar(30) NOT NULL COMMENT '账号',
`password` varchar(100) NOT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
大家可以使用自己熟悉的工具将mysql表创建好。
8.2.初始化数据库连接
一般初始化数据库连接都是在main.go入口的地方设置一次就行,下面看下main.go文件改成什么样了。
package main
import (
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/client/orm"
_ "tizi365/routers"
//导入mysql驱动,这是必须的
_ "github.com/go-sql-driver/mysql"
)
//初始化应用设置, 我们通过init函数初始化数据库连接,go语言中这个函数会优先执行
func init() {
// 这里注册一个default默认数据库,数据库驱动是mysql.
// 第三个参数是数据库dsn, 配置数据库的账号密码,数据库名等参数
// dsn参数说明:
// username - mysql账号
// password - mysql密码
// db_name - 数据库名
// 127.0.0.1:3306 - 数据库的地址和端口
orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/db_name?charset=utf8")
}
func main() {
beego.Run()
}
为了初始化mysql连接,在入口main.go文件,增加init函数初始化数据库设置。
8.3.创建model
然后创建一个user model, 文件路径:tizi365/models/user.go , 代码如下
package models
import (
"github.com/beego/beego/v2/client/orm"
)
// 定义User模型,绑定users表结构, 其实就是用来保存sql查询结果。
type User struct {
Id int
Username string
Password string
}
// 定义User 模型绑定那个表?
func (u *User) TableName() string {
// 返回mysql表名
return "users"
}
//初始化函数,可以用来向orm注册model
func init() {
// 向orm注册user模型
orm.RegisterModel(&User{})
}
// 根据id查询用户信息
func GetUserById(id int) *User {
if id == 0 {
return nil
}
// 创建orm对象, 后面都是通过orm对象操作数据库
o := orm.NewOrm()
// 初始化一个User模型对象
user := User{}
// 设置查询参数
user.Id = id
// 调用Read方法,根据user设置的参数,查询一条记录,结果保存到user结构体变量中
// 默认是根据主键进行查询
// 等价sql: SELECT `id`, `username`, `password` FROM `users` WHERE `id` = 1
err := o.Read(&user)
// 检测查询结果,
if err == orm.ErrNoRows {
// 找不到记录
return nil
} else if err == orm.ErrMissPK {
// 找不到住建
return nil
}
return &user
}
提示: 具体的orm用法,后续教程有详细说明,这里大概了解下大概的用法。
8.4.通过控制器调用model
下面修改控制器代码 文件: tizi365/controllers/default.go
func (c *MainController) Get() {
c.Data["Website"] = "tizi365.com"
c.Data["Email"] = "tizi365@demo.com"
// 调用model,查询用户id为1 的用户信息
user := models.GetUserById(1)
// 然后将user数据保存到Data中, 将参数传给后面的views视图模板处理
c.Data["user"] = user
// 使用新的视图模板user.tpl
c.TplName = "user.tpl"
}
9.编写view视图逻辑
这里编写一个新的视图模板, 代码如下: 文件: tizi365/views/user.tpl
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Demo</title>
<meta charset="utf-8">
</head>
<body>
<h1>网站: {{.Website}}</h1>
{{ if .user }}
用户名: {{.user.Username}}
{{else}}
查找不到用户
{{ end }}
</body>
</html>
访问url: http://localhost:8080, 如果查询的用户存在,则显示用户名,否则显示查找不到用户。
提示: beego使用的是go自带的模板引擎,如果暂时看不懂,大家可以先忽略具体的模板语法,后续的教程会有讲解。
10.项目打包
项目完成后需要将代码打包发布到线上,这里依然推荐使用bee工具打包,bee工具可以一键将项目需要的相关文件一起打包成一个压缩包,只需要到线上解压即可。
下面是bee打包的例子, 首先将命令窗口的目录切换到 "项目根目录", 然后执行下面命令
bee pack
打包成功后再项目根目录下生成一个tizi365.tar.gz的压缩包,命名格式: ${项目名}.tar.gz
我们可以解压缩,看看压缩包包含什么内容:
tizi365.tar.gz
├── conf - 配置文件存放目录,这里包含我们的配置文件
├── static - 静态资源目录,包含我们静态资源文件
├── views - 视图模板目录,包含模板文件
└── tizi365 - 这个就是我们的项目打包后的可执行程序,按我们项目命名
注意: 线上服务器一般都是linux环境,所以建议在linux环境打包项目,不要在windows环境打包,否则打包生成的可执行程序是.exe格式的,linux无法运行。
如果你不嫌麻烦,依然可以手动编译项目,然后将相关文件打包到一块。
beego参数配置
beego 默认使用了 INI 格式解析配置文件,通常在项目中会存在很多系统参数、业务参数配置,这些参数通常都是通过配置文件进行配置,而且不是写死在代码里面。
提示:例如mysql账号密码之类的系统参数,如果写死在代码里面,每次修改参数都得重新打包升级,非常不灵活。
提示:修改配置文件后,需要重启应用,配置才生效,即使使用bee run运行项目也得重启。
1.beego系统参数
我先介绍下beego自带的系统参数有哪些?默认情况,conf/app.conf就是我们的默认配置文件。
例子:
# 这是注释
#应用名称
appname = tizi356
#http 服务端口
httpport = 8080
#运行模式,常用的运行模式有dev, test, prod
runmode = dev
下面表格是beego常用配置:
提示: 参数名不区分大小写, 下面的参数配置,了解下即可,需要的时候再查。
参数名 | 默认值 | 说明 |
---|---|---|
AppName | beego | 应用名 |
RunMode | dev | 程序运行模式,常用模式有dev、test、prod,一般用于区分不同的运行环境 |
RouterCaseSensitive | true | 是否路由忽略大小写匹配 |
ServerName | beego | beego 服务器默认在请求的时候输出 server 头的值。 |
RecoverPanic | true | 是否异常恢复,默认值为 true,即当应用出现异常的情况,通过 recover 恢复回来,而不会导致应用异常退出。 |
EnableGzip | false | 是否开启 gzip 支持 |
MaxMemory | 64M | 文件上传默认内存缓存大小,单位是字节 |
AutoRender | true | 是否模板自动渲染,对于 API 类型的应用,应用需要把该选项设置为 false,不需要渲染模板。 |
StaticDir | static | 静态文件目录设置 |
ViewsPath | views | 模板路径 |
Graceful | false | 是否开启热升级,默认是 false,关闭热升级。 |
ServerTimeOut | 0 | 设置 HTTP 的超时时间,默认是 0,不超时。 |
HTTPAddr | 应用监听地址,默认为空,监听所有的网卡 IP。 | |
HTTPPort | 8080 | 应用监听端口 |
EnableHTTPS | false | 是否启用 HTTPS,默认是 false 关闭。当需要启用时,先设置 EnableHTTPS = true,并设置 HTTPSCertFile 和 HTTPSKeyFile |
HTTPSAddr | https应用监听地址,默认为空,监听所有的网卡 IP。 | |
HTTPSPort | 10443 | https应用监听端口 |
HTTPSCertFile | 开启 HTTPS 后,ssl 证书路径 | |
HTTPSKeyFile | 开启 HTTPS 之后,SSL 证书 keyfile 的路径。 | |
EnableAdmin | false | 是否开启进程内监控模块,默认 false 关闭。 |
AdminAddr | localhost | 监控程序监听的地址。 |
AdminPort | 8088 | 监控程序监听的地址。 |
SessionOn | false | session 是否开启 |
SessionProvider | memory | session 的引擎, 详情参考session章节的教程 |
SessionName | beegosessionID | 存在客户端的 cookie 名称。 |
SessionGCMaxLifetime | 3600 | session 过期时间, 单位秒。 |
SessionProviderConfig | 配置信息,根据不同的session引擎设置不同的配置信息,详细的配置请参考session章节的教程 | |
SessionCookieLifeTime | 3600 | session 默认存在客户端的 cookie 的时间, 单位秒。 |
SessionDomain | session cookie 存储域名。 |
2.自定义参数
除了beego系统自带的配置,我们也可以自定义配置,然后通过beego.AppConfig对象的函数读取配置。
例子: 我们在app.conf增加下面自定义配置
# 下面是关于mysql数据库的配置参数
mysql_user = "root"
mysql_password = "123456"
mysql_host = "127.0.0.1:3306"
mysql_dbname = "tizi365"
下面是读取配置代码:
web.AppConfig.String("mysql_user")
web.AppConfig.String("mysql_password")
web.AppConfig.String("mysql_host")
web.AppConfig.String("mysql_dbname")
beego.AppConfig对象,为我们定义了一些常用的函数,用于读取配置,下面列出一些常用的函数:
函数名 | 说明 |
---|---|
String | 以字符串的方式返回参数 |
Int | 以int类型的方式返回参数 |
Int64 | 以Int64类型的方式返回参数 |
Bool | 以Bool类型的方式返回参数 |
Float | 以Float类型的方式返回参数 |
提示: 以上函数,只有一个参数,就是配置的名字
如果配置项的参数为空,希望返回默认值,可以使用下面的函数:
函数名 | 说明 |
---|---|
DefaultString | 以字符串的方式返回参数 |
DefaultInt | 以int类型的方式返回参数 |
DefaultInt64 | 以Int64类型的方式返回参数 |
DefaultBool | 以Bool类型的方式返回参数 |
DefaultFloat | 以Float类型的方式返回参数 |
提示: 以上函数,只有两个参数,第一个参数是配置项名字,第二个参数是默认值
例子:
// 如果mysql_port配置项的参数为空,则返回3306
web.AppConfig.DefaultInt("mysql_port", 3306)
3.不同运行级别的参数
前面提到runmode参数可以设置不同的运行级别,我们一般用来区分不用的运行环境,例如: dev、test等等。 如果我们希望数据库配置在不同环境,账号密码都不一样,可以使用如下配置方式:
例子:
# 配置运行级别
runmode ="dev"
[dev]
mysql_user = "root"
mysql_password = "123456"
mysql_host = "127.0.0.1:3306"
mysql_dbname = "tizi365"
[test]
mysql_user = "root"
mysql_password = "Ihd9ay86asgk"
mysql_host = "61.99.21.1:3306"
mysql_dbname = "tizi365"
[prod]
mysql_user = "root"
mysql_password = "8hlabdias986"
mysql_host = "202.12.91.1:3306"
mysql_dbname = "tizi365"
上面的例子,我们为dev,test,prod三个环境配置了不同的数据库参数,当我们通过web.AppConfig读取参数的时候,由runmode决定读取那个环境的参数。 例如:当runmode=test, mysql_password=Ihd9ay86asgk
4.使用多个配置文件
在实际项目中,我们一般都使用多个配置文件管理配置,多个配置文件也方便我们模块化管理配置。
例如: 我们新建一个mysql.conf配置文件,保存数据库配置。 文件: conf/mysql.conf
[dev]
mysql_user = "root"
mysql_password = "123456"
mysql_host = "127.0.0.1:3306"
mysql_dbname = "tizi365"
然后我们在conf/app.conf主配置文件中,通过include 将mysql配置文件包含进去。
AppName = tizi356
HttpPort = 8080
runmode = dev
# 包含mysql配置
include "mysql.conf"
这种通过include包含其他配置文件的方式,跟把所有配置都写在一个配置文件的效果是一样的, 区别就是使用多个配置文件,各个模块的配置更加清晰。
说明: 无论是使用include包含配置文件,还是直接将所有配置都写在一个配置文件,读取配置的方式都一样。
5.支持环境变量配置
到目前为止,我们的配置参数都是通过ini配置文件进行配置,如果想通过环境变量进行配置怎么办?尤其是在docker容器环境运行,通常都需要通过环境变量配置应用参数。
beego支持优先从环境变量中读取参数, 只要在ini配置文件中通过 ${环境变量名},定义配置项的值。
例子:
runmode = "${APP_RUN_MODE || dev}"
httpport = "${APP_PORT || 9090}"
上面例子的意思就是: 如果环境变量APP_RUN_MODE值不为空,runmode配置的参数就等于APP_RUN_MODE环境变量的值,如果为空,则使用dev作为默认参数。 同理APP_PORT为空,则使用9090作为默认值,否则使用APP_PORT的值。
控制器
beego 路由设置
路由指的就是一个url请求由谁来处理,在beego设计中,url请求可以由控制器的函数来处理,也可以由一个单独的函数来处理,因此路由设置由两部分组成:url路由 和 处理函数。
beego提供两种设置处理函数的方式:
-
直接绑定一个函数
-
绑定一个控制器对象 (RESTful方式)
1.直接绑定处理函数
这种方式直接将一个url路由和一个函数绑定起来。
例子:
// 这就是将url / 和一个闭包函数绑定起来, 这个url的Get请求由这个闭包函数处理。
web.Get("/",func(ctx *context.Context){
ctx.Output.Body([]byte("hi tizi365.com"))
})
// 定义一个处理函数
func Index(ctx *context.Context){
ctx.Output.Body([]byte("欢迎访问 tizi365.com"))
}
// 注册路由, 将url /index 和Index函数绑定起来,由Index函数处理这个url的Post请求
web.Post("/index", Index)
下面是beego支持的基础函数:
-
web
.Get(router, web.FilterFunc) -
web
.Post(router, web.FilterFunc) -
web
.Put(router, web.FilterFunc) -
web
.Patch(router, web.FilterFunc) -
web
.Head(router, web.FilterFunc) -
web
.Options(router, web.FilterFunc) -
web
.Delete(router, web.FilterFunc) -
web
.Any(router, web.FilterFunc) - 处理任意http请求,就是不论请求方法(Get,Post,Delete等等)是什么,都由绑定的函数处理
根据不同的http请求方法(Get,Post等等)选择不同的函数设置路由即可。
2.RESTful路由方式
RESTful 是一种目前比较流行的url风格,beego默认支持这种风格。 在beego项目中,RESTful路由方式就是将url路由跟一个控制器对象绑定,然后Get请求由控制的Get函数处理,Post请求由Post函数处理,以此类推。
RESTful路由使用web.Router函数设置路由。
例子:
// url: / 的所有http请求方法都由MainController控制器的对应函数处理
web.Router("/", &controllers.MainController{})
// url: /user 的所有http请求方法都由UserController控制器的对应函数处理
// 例如: GET /user请求,由Get函数处理, POST /user 请求,由Post函数处理
web.Router("/user", &controllers.UserController{})
3.url路由方式
上面介绍了设置处理函数的方式,下面介绍beego支持的url路由方式。
提示: 下面介绍的所有url路由规则,都适用于上面介绍的所有路由设置函数。
3.1.固定路由
前面介绍的url路由例子,都属于固定路由方式,固定路由指的是url规则是固定的一个url。 例子:
web.Router("/user", &controllers.UserController{})
web.Router("/shop/order", &controllers.OrderController{})
web.Router("/shop/comment", &controllers.CommentController{})
3.2.正则路由
正则路由比较灵活,一个正则路由设置代表的是一序列的url, 正则路由更像是一种url模板。 url路由例子:
-
/user/:id 匹配/user/132,参数 :id=132
-
/user/:id([0-9]+) 匹配/user/123,参数 :id=123, 跟上面例子的区别就是只能匹配数字
-
/user/:username([\w]+) 匹配/user/tizi, 参数 :username=tizi
-
/list:cat([0-9]+):page([0-9]+).html 匹配/list_2_1.html, 参数 :cat=2, :page=1
-
/api/* 匹配/api为前缀的所有url, 例子: /api/user/1 , 参数: :splat=user/1
在 Controller 对象中,可以通过下面的方式获取url路由匹配的参数:
func (c *MainController) Get() {
c.Ctx.Input.Param(":id")
c.Ctx.Input.Param(":username")
c.Ctx.Input.Param(":cat")
c.Ctx.Input.Param(":page")
c.Ctx.Input.Param(":splat")
}
3.3.自动路由
自动路由指的是通过反射获取到控制器的名字和控制器实现的所有函数名字,自动生成url路由。
使用自动路由首先需要beego.AutoRouter函数注册控制器。 例子:
web.AutoRouter(&controllers.UserController{})
url自动路由例子:
/user/login 调用 UserController 中的 Login 方法
/user/logout 调用 UserController 中的 Logout 方法
除了前缀两个 /:controller/:method 的匹配之外,剩下的 url beego 会帮你自动化解析为参数,保存在 this.Ctx.Input.Params 当中:
/user/list/2019/09/11 调用 UserController 中的 List 方法,参数如下:map[0:2019 1:09 2:11]
提示:自动路由会将url和控制器名字、函数名字转换成小写。
3.4.namespace
路由名字空间(namespace),一般用来做api版本处理。
例子:
// 创建版本1的名字空间
ns1 := web.NewNamespace("/v1",
// 内嵌一个/user名字空间
web.NSNamespace("/user",
// 下面开始注册路由
// url路由: /v1/user/info
web.NSRouter("/info", &controllers.UserController{}),
// url路由: /v1/user/order
web.NSRouter("/order", &controllers.UserOrderController{}),
),
// 内嵌一个/shop名字空间
web.NSNamespace("/shop",
// 下面开始注册路由
// url路由: /v1/shop/info
web.NSRouter("/info", &controllers.ShopController{}),
// url路由: /v1/shop/order
web.NSRouter("/order", &controllers.ShopOrderController{}),
),
)
// 创建版本2的名字空间
ns2 := web.NewNamespace("/v2",
web.NSNamespace("/user",
// url路由: /v2user/info
web.NSRouter("/info", &controllers.User2Controller{}),
),
web.NSNamespace("/shop",
// url路由: /v2/shop/order
web.NSRouter("/order", &controllers.ShopOrder2Controller{}),
),
)
//注册 namespace
web.AddNamespace(ns1)
web.AddNamespace(ns2)
通过NewNamespace函数创建多个名字空间,NSNamespace函数可以无限嵌套名字空间, 根据上面的例子可以看出来,名字空间的作用其实就是定义url路由的前缀,如果一个名字空间定义url路由为/user, 那么这个名字空间下面定义的所有路由的前缀都是以/user开头。
下面是namespace支持的路由设置函数:
-
NewNamespace(prefix string, funcs …interface{})
-
NSNamespace(prefix string, funcs …interface{})
-
NSInclude(cList …ControllerInterface)
-
NSRouter(rootpath string, c ControllerInterface, mappingMethods …string)
-
NSGet(rootpath string, f FilterFunc)
-
NSPost(rootpath string, f FilterFunc)
-
NSDelete(rootpath string, f FilterFunc)
-
NSPut(rootpath string, f FilterFunc)
-
NSHead(rootpath string, f FilterFunc)
-
NSOptions(rootpath string, f FilterFunc)
-
NSPatch(rootpath string, f FilterFunc)
-
NSAny(rootpath string, f FilterFunc)
-
NSHandler(rootpath string, h http.Handler)
-
NSAutoRouter(c ControllerInterface)
-
NSAutoPrefix(prefix string, c ControllerInterface)
这些路由设置函数的参数,跟前面的路由设置函数一样,区别就是namespace的函数名前面多了NS前缀。
beego控制器函数
控制器函数指的是处理用户请求的函数,前面路由设置章节介绍过,beego框架支持两种处理用户请求的函数。
-
beego.FilterFunc 类型的独立函数
-
控制器函数 (RESTful 风格实现, beego默认推荐的格式)
1.beego.FilterFunc函数
这是最简单的请求处理函数,函数原型定义:
type FilterFunc func(*context.Context)
也就是只要定义一个函数,并且接收一个Context参数,那么这个函数就可以作为处理用户请求的函数。
例子:
func DoLogin(ctx *context.Context) {
// ..处理请求的逻辑...
// 可以通过Context 获取请求参数,返回请求结果
}
有了处理函数,我们就可以将处理函数跟一个url路由绑定起来.
例子:
web.Get("/user/login", DoLogin)
提示: 新版的beego设计,默认不推荐使用beego.FilterFunc函数方式,这种方式处理请求的方式比较原始,后面介绍的控制器函数拥有更多高级特性,后续的教程以控制器函数为主。
2.控制器函数
控制器函数是beego的RESTful api的实现方式,在beego的设计中,控制器就是一个嵌套了beego.Controller的结构体对象。
例子:
// 定义一个新的控制器
type UserController struct {
// 嵌套beego基础控制器
web.Controller
}
前面介绍过,struct嵌套,就类似其他高级语言的 继承 特性,嵌套了web.Controller控制器,就拥有了web.Controller定义的属性和函数。
控制器命名规则约定:XxxController Xxx就是我们的控制器名字, 这是为了便于阅读,看到Controller结尾的struct就知道是一个控制器。
下面看一个完整控制器的例子:
type UserController struct {
// 嵌套beego基础控制器
web.Controller
}
// 在调用其他控制器函数之前,会优先调用Prepare函数
func (this *UserController) Prepare() {
// 这里可以跑一些初始化工作
}
// 处理get请求
func (this *UserController) Get() {
// 处理逻辑
}
// 处理post请求
func (this *UserController) Post() {
// 处理逻辑
}
注册路由
// 在这里参数:id是可选的
web.Router("/user/?:id", &controllers.UserController{})
根据上面注册的路由规则, 下面的展示对应的http请求和处理函数:
-
GET /user/2 - 由Get函数处理
-
POST /user - 由Post函数处理
提示:前面路由设置章节介绍过控制器的路由规则是Get请求由Get函数处理,Post请求由Post函数处理,以此类推。
下表展示了web.Controller默认为我们提供了哪些可选的函数:
提示: 根据业务需要,控制器可以覆盖下表中的函数。
函数名 | 说明 |
---|---|
Prepare() | 这个函数会优先执行,才会执行Get、Post之类的函数, 可以在Prepare做一些初始化工作。 |
Get() | 处理get请求, 如果没有实现该函数,默认会返回405错误。 |
Post() | 处理Post请求, 默认会返回405错误。 |
Delete() | 处理Delete请求, 默认会返回405错误。 |
Put() | 处理PUT请求, 默认会返回405错误。 |
Finish() | 执行完Get、Post之类http请求函数之后执行,我们可以在Finish函数处理一些回收工作。 |
3.如何提前结束请求。
如果我们在Prepare函数处理用户的权限验证,验证不通过,我们一般都希望结束请求,不要执行后面的函数,beego提供了StopRun函数来结束请求。 例子:
func (this *UserController) Prepare() {
// 处理权限验证逻辑
// 验证不通过,返回错误信息,结束请求
this.Data["json"] = map[string]interface{}{"error":"没有权限", "errno":401}
this.ServeJSON()
this.StopRun()
}
提示:调用 StopRun 之后,不会再执行Finish函数,如果有需要可以在调用StopRun之后,手动调用Finish函数。
beego处理请求参数
web.Controller基础控制器,为我们提供了多种读取请求参数的函数,下面分别介绍各种获取参数的场景。
1.默认获取参数方式
web.Controller基础控制器为我们提供了GetXXX序列获取参数的函数, XXX指的就是返回不同的数据类型。 例子:
// 处理get请求
func (this *UserController) Get() {
// 获取参数, 返回int类型
id ,_:= this.GetInt("id")
// 获取参数,返回string类型, 如果参数不存在返回none作为默认值
username := this.GetString("username", "none")
// 获取参数,返回float类型, 参数不存在则返回 0
price, _ := this.GetFloat("price", 0)
}
下面是常用的获取参数的函数定义:
-
GetString(key string, def ...string) string
-
GetInt(key string, def ...int) (int, error)
-
GetInt64(key string, def ...int64) (int64, error)
-
GetFloat(key string, def ...float64) (float64, error)
-
GetBool(key string, def ...bool) (bool, error)
默认情况用户请求的参数都是 字符串 类型,如果要转换成其他类型,就可能会出现类型转换失败的可能性,因此除了GetString函数,其他GetXXX函数,都返回两个值,第一个值是需要获取的参数值,第二个就是error,表示是数据类型转换是否失败。
2.绑定struct方式
除了上面一个一个的获取请求参数,针对POST请求的表单数据,beego支持直接将表单数据绑定到一个struct变量。
例子:
// 定义一个struct用来保存表单数据
// 通过给字段设置tag, 指定表单字段名, - 表示忽略这个字段不进行赋值
// 默认情况下表单字段名跟struct字段名同名(小写)
type UserForm struct {
// 忽略掉Id字段
Id int `form:"-"`
// 表单字段名为username
Name string `form:"username"`
Phone string
}
说明: 如果表单字段跟struct字段(小写)同名,不需要设置form标签。 表单html代码:
<form action="/user" method="POST">
手机号:<input name="phone" type="text" /><br/>
用户名:<input name="username" type="text" />
<input type="submit" value="提交" />
</form>
控制器函数:
func (this *UserController) Post() {
// 定义保存表单数据的struct对象
u := UserForm{}
// 通过ParseForm函数,将请求参数绑定到struct变量。
if err := this.ParseForm(&u); err != nil {
// 绑定参数失败
}
}
提示:使用struct绑定请求参数的方式,仅适用于POST请求。
3.处理json请求参数
一般在接口开发的时候,有时候会将json请求参数保存在http请求的body里面。我们就不能使用前的方式获取json数据,需要直接读取请求body的内容,然后格式化数据。
处理json参数的步骤:
-
在app.conf配置文件中,添加CopyRequestBody=true
-
通过this.Ctx.Input.RequestBody获取请求body的内容
-
通过json.Unmarshal反序列化json字符串,将json参数绑定到struct变量。
例子:
定义struct用于保存json数据
// 如果json字段跟struct字段名不一样,可以通过json标签设置json字段名
type UserForm struct {
// 忽略掉Id字段
Id int `json:"-"`
// json字段名为username
Name string `json:"username"`
Phone string
}
控制器代码:
func (this *UserController) Post() {
// 定义保存json数据的struct对象
u := UserForm{}
// 获取body内容
body := this.Ctx.Input.RequestBody
// 反序列json数据,结果保存至u
if err := json.Unmarshal(body, &u); err == nil {
// 解析参数失败
}
}
提示: 如果将请求参数是xml格式,xml参数也是保存在body中,处理方式类似,就是最后一步使用xml反序列化函数进行处理。
beego处理响应数据
我们处理完用户的请求之后,通常我们都会返回html代码,然后浏览器就可以显示html内容;除了返回html,在api接口开发中,我们还可以返回json、xml、jsonp格式的数据。
下面分别介绍beego返回不同数据类型的处理方式。
注意:如果使用beego开发api,那么在app.conf中设置AutoRender = false, 禁止自动渲染模板,否则beego每次处理请求都会尝试渲染模板,如果模板不存在则报错。
1.返回json数据
下面是返回json数据的例子:
// 定义struct
// 如果struct字段名跟json字段名不一样,可以使用json标签,指定json字段名
type User struct {
// - 表示忽略id字段
Id int `json:"-"`
Username string `json:"name"`
Phone string
}
func (this *UserController) Get() {
// 定义需要返回给客户端的数据
user := User{1, "tizi365", "13089818901"}
// 将需要返回的数据赋值给json字段
this.Data["json"] = &user
// 将this.Data["json"]的数据,序列化成json字符串,然后返回给客户端
this.ServeJSON()
}
提示:请参考Go处理json数据教程,了解详细的json数据处理方式。
2.返回xml数据
下面是返回xml数据的处理方式跟json类似。
例子:
// 定义struct
// 如果struct字段名跟xml字段名不一样,可以使用xml标签,指定xml字段名
type User struct {
// - 表示忽略id字段
Id int `xml:"-"`
Username string `xml:"name"`
Phone string
}
func (this *UserController) Get() {
// 定义需要返回给客户端的数据
user := User{1, "tizi365", "13089818901"}
// 将需要返回的数据赋值给xml字段
this.Data["xml"] = &user
// 将this.Data["xml"]的数据,序列化成xml字符串,然后返回给客户端
this.ServeXML()
}
提示:请参考Go处理xml数据教程,了解详细的xml数据处理方式。
3.返回jsonp数据
返回jsonp数据,于返回json数据方式类似。
例子:
func (this *UserController) Get() {
// 定义需要返回给客户端的数据
user := User{1, "tizi365", "13089818901"}
// 将需要返回的数据赋值给jsonp字段
this.Data["jsonp"] = &user
// 将this.Data["json"]的数据,序列化成json字符串,然后返回给客户端
this.ServeJSONP()
}
4.返回html
如果我们开发的是网页,那么通常需要返回html代码,在beego项目中关于html视图部分,使用的是模板引擎技术,渲染html,然后将结果返回给浏览器。
例子:
func (c *MainController) Get() {
// 设置模板参数
c.Data["Website"] = "tizi365.com"
c.Data["Email"] = "tizi365@demo.com"
// 需要渲染的模板, beego会渲染这个模板,然后返回结果
c.TplName = "index.tpl"
}
5.添加响应头
为http请求添加header
func (c *MainController) Get() {
// 通过this.Ctx.Output.Header设置响应头
this.Ctx.Output.Header("Content-Type", "message/http")
this.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate")
}
提示:后续会有关于beego视图开发的详细的教程。
模型
beego orm数据库操作入门教程
Beego ORM框架是一个独立的ORM模块,主要用于数据库操作。
说明: 对象-关系映射(Object/Relation Mapping,简称ORM), 在Go语言中就是将struct类型和数据库记录进行映射。
下面介绍如何操作mysql数据库。
1.安装包
因为beego orm是独立的模块,所以需要单独安装包。
// 安装beego orm包
go get github.com/beego/beego/v2/client/orm
安装mysql驱动
go get github.com/go-sql-driver/mysql
beego orm包操作什么数据库,就需要单独安装对应的数据库驱动。
2.导入包
import (
// 导入orm包
"github.com/beego/beego/v2/client/orm"
// 导入mysql驱动
_ "github.com/go-sql-driver/mysql"
)
3.连接mysql数据库
操作数据库之前首先需要配置好mysql数据库连接参数,通常在beego项目中,我们都会在main.go文件,对数据库进行配置,方便整个项目操作数据库。
例子:
package main
import (
_ "beegodemo/routers"
"github.com/beego/beego/v2/server/web"
// 导入orm包
"github.com/beego/beego/v2/client/orm"
// 导入mysql驱动
_ "github.com/go-sql-driver/mysql"
)
// 通过init函数配置mysql数据库连接信息
func init() {
// 这里注册一个default默认数据库,数据库驱动是mysql.
// 第三个参数是数据库dsn, 配置数据库的账号密码,数据库名等参数
// dsn参数说明:
// username - mysql账号
// password - mysql密码
// db_name - 数据库名
// 127.0.0.1:3306 - 数据库的地址和端口
orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/db_name?charset=utf8&parseTime=true&loc=Local")
// 打开调试模式,开发的时候方便查看orm生成什么样子的sql语句
orm.Debug = true
}
func main() {
web.Run()
}
4.定义模型(Model)
orm操作通常都是围绕struct对象进行,我们先定义一个表结构,方便后面演示数据库操作。
4.1.定义表结构
这里我们创建一个简单的订单表。
CREATE TABLE `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`shop_id` int(10) unsigned NOT NULL COMMENT '店铺id',
`customer_id` int(10) unsigned NOT NULL COMMENT '用户id',
`nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称',
`address` varchar(200) NOT NULL DEFAULT '' COMMENT '用户地址',
`init_time` datetime NOT NULL COMMENT '创建订单的时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
4.2.定义模型
所谓模型(Model)指的就是关联数据库表的struct类型。 这里我们定义个Order结构体, 他们的字段对应着上面的orders表结构。
// 默认情况struct字段名按照下面规则转换成mysql表字段名:
// 规则: 以下滑线分割首字母大写的单词,然后转换成小写字母。
type Order struct {
// 对应表字段名为: id
Id int
// 对应表字段名为: shop_id , 下面字段名转换规则以此类推。
ShopId int
// struct字段名跟表字段名不一样,通过orm标签指定表字段名为customer_id
Uid int `orm:"column(customer_id)"`
Nickname string
Address string
// 数据库init_time字段是datetime类型,支持自动转换成Go的time.Time类型,但是数据库连接参数必须设置参数parseTime=true
InitTime time.Time
}
// 指定Order结构体默认绑定的表名
func (o *Order) TableName() string {
return "orders"
}
// 注册模型
orm.RegisterModel(new(Order))
5.插入数据
例子1:
// 创建orm对象, 后面都是通过orm对象操作数据库
o := orm.NewOrm()
// 创建一个新的订单
order := Order{}
// 对order对象赋值
order.ShopId = 1
order.Uid = 1002
order.Nickname = "大锤"
order.Address = "深圳南山区"
order.InitTime = time.Now()
// 调用orm的Insert函数插入数据
// 等价sql: INSERT INTO `orders` (`shop_id`, `customer_id`, `nickname`,
`address`, `init_time`) VALUES (1, 1002, '大锤', '深圳南山区', '2019-06-24 23:08:57')
id, err := o.Insert(&order)
if err != nil {
fmt.Println("插入失败")
} else {
// 插入成功会返回插入数据自增字段,生成的id
fmt.Println("新插入数据的id为:", id)
}
例子2 批量插入数据:
o := orm.NewOrm()
orders := []Order{
{ShopId:1, Uid:1001, Nickname:"大锤1", Address:"深圳南山区", InitTime: time.Now()},
{ShopId:1, Uid:1002, Nickname:"大锤2", Address:"深圳南山区", InitTime: time.Now()},
{ShopId:1, Uid:1003, Nickname:"大锤3", Address:"深圳南山区", InitTime: time.Now()},
}
// 调用InsertMulti函数批量插入, 第一个参数指的是要插入多少数据
nums, err := o.InsertMulti(3, orders)
6.更新数据
orm的Update函数是根据主键id进行更新数据的,因此需要预先对id赋值。
例子:
o := orm.NewOrm()
// 需要更新的order对象
order := Order{}
// 先对主键id赋值, 更新数据的条件就是where id=2
order.Id = 2
// 对需要更新的数据进行赋值
order.Nickname = "小锤"
order.Address = "深圳宝安区"
// 调用Update函数更新数据, 默认Update根据struct字段,更新所有字段值,如果字段值为空也一样更新。
// 等价sql: update orders set shop_id=0, customer_id=0, nickname='小锤', address='深圳宝安区', init_time='0000:00:00' where id = 2
num, err := o.Update(&order)
if err != nil {
fmt.Println("更新失败")
} else {
fmt.Println("更新数据影响的行数:", num)
}
// 上面Update直接更新order结构体的所有字段,如果只想更新指定字段,可以这么写
num, err := o.Update(&order, "Nickname", "Address")
// 这里只是更新Nickname和Address两个字段
7.查询数据
默认orm的Read函数也是通过主键id查询数据。
例子:
o := orm.NewOrm()
// 定义order
order := Order{}
// 先对主键id赋值, 查询数据的条件就是where id=2
order.Id = 2
// 通过Read函数查询数据
// 等价sql: select id, shop_id, customer_id, nickname, address, init_time from orders where id = 2
err := o.Read(&order)
if err == orm.ErrNoRows {
fmt.Println("查询不到")
} else if err == orm.ErrMissPK {
fmt.Println("找不到主键")
} else {
fmt.Println(order.Id, order.Nickname)
}
// 通过ReadOrCreate函数,先尝试根据主键id查询数据,如果数据不存在则插入一条数据
created, id, err := o.ReadOrCreate(&order, "Id")
// ReadOrCreate返回三个参数,第一个参数表示是否插入了一条数据,第二个参数表示插入的id
8.删除数据
orm的Delete函数根据主键id删除数据。
例子:
o := orm.NewOrm()
// 定义order
order := Order{}
// 先对主键id赋值, 删除数据的条件就是where id=2
order.Id = 2
if num, err := o.Delete(&order); err != nil {
fmt.Println("删除失败")
} else {
fmt.Println("删除数据影响的行数:", num)
}
beego orm数据库连接设置
本章介绍beego orm数据库连接相关设置。
1.beego支持的数据库类型
目前 ORM 支持三种数据库,分别是:
-
mysql
-
sqlite3
-
Postgres
使用不通的数据库,需要导入不通的数据库驱动:
import (
// 导入mysql驱动
_ "github.com/go-sql-driver/mysql"
// 导入sqlite3驱动
_ "github.com/mattn/go-sqlite3"
// 导入Postgres驱动
_ "github.com/lib/pq"
)
根据需要导入自己想要的驱动即可。
2.mysql数据库连接
这里介绍mysql数据库的详细链接参数,要想连接mysql数据库,首先得注册一个数据库,在调用查询函数会自动创建连接。
ORM 必须注册一个别名为 default 的数据库,作为默认使用的数据库。
注册数据库的函数原型:
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error
参数说明:
参数名 | 说明 |
---|---|
aliasName | 数据库的别名,用来在 ORM 中切换数据库使用 |
driverName | 驱动名字 |
dataSource | 数据库连接字符串 |
params | 附加参数 |
例子:
// 注册默认数据库,驱动为mysql, 第三个参数就是我们的数据库连接字符串。
orm.RegisterDataBase("default", "mysql", "root:123456@tcp(localhost:3306)/tizi?charset=utf8")
mysql数据库连接字符串DSN (Data Source Name)详解:
格式:
username:password@protocol(address)/dbname?param=value
参数说明:
参数名 | 说明 |
---|---|
username | 数据库账号 |
password | 数据库密码 |
protocol | 连接协议,一般就是tcp |
address | 数据库地址,可以包含端口。例: localhost:3306 , 127.0.0.1:3306 |
dbname | 数据库名字 |
param=value | 最后面问号(?)之后可以包含多个键值对的附加参数,多个参数之间用&连接。 |
常用附加参数说明:
参数名 | 默认值 | 说明 |
---|---|---|
charset | none | 设置字符集,相当于 SET NAMES <value> 语句 |
loc | UTC | 设置时区,可以设置为Local,表示根据本地时区走 |
parseTime | false | 是否需要将 mysql的 DATE 和 DATETIME 类型值转换成GO的time.Time类型。 |
readTimeout | 0 | I/O 读超时时间, sql查询超时时间. 单位 ("ms", "s", "m", "h"), 例子: "30s", "0.5m" or "1m30s". |
timeout | 0 | 连接超时时间,单位("ms", "s", "m", "h"), 例子: "30s", "0.5m" or "1m30s". |
例子:
root:123456@(123.180.11.30:3306)/tizi?charset=utf8&timeout=5s&loc=Local&parseTime=true
3.数据库连接池设置
数据库连接词参数主要有下面两个:
3.1. SetMaxIdleConns
根据数据库的别名,设置数据库的最大空闲连接
orm.SetMaxIdleConns("default", 20)
3.2. SetMaxOpenConns
根据数据库的别名,设置数据库的最大数据库连接
orm.SetMaxOpenConns("default", 100)
4.数据库调试模式
打开调试模式,当执行orm查询的时候,会打印出对应的sql语句。
orm.Debug = true
beego orm高级查询
针对业务比较复杂,涉及复杂的查询条件的场景,beego orm为我们提供了QuerySeter 对象,用来组织复杂的查询条件。
1.QuerySeter入门
因为QuerySeter是专门针对ORM的模型对象进行操作的,所以在使用QuerySeter之前必须先定义好模型。
1.1.表定义
模型(model)是跟表结构一一对应的,作为例子这里先定义下表结构。
// 定义用户表
CREATE TABLE `users` (
`id` int(10) UNSIGNED NOT NULL COMMENT '自增ID',
`username` varchar(30) NOT NULL COMMENT '账号',
`password` varchar(100) NOT NULL COMMENT '密码',
`city` varchar(50) DEFAULT NULL COMMENT '城市',
`init_time` datetime DEFAULT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1.2.模型定义
//定义User模型,绑定users表结构
type User struct {
Id int
Username string
Password string
City string
// 驼峰式命名,会转换成表字段名,表字段名使用蛇形命名风格,即用下划线将单词连接起来
// 这里对应数据库表的init_time字段名
InitTime time.Time
}
// 定义模型表名
func (u *User) TableName() string {
return "users"
}
1.3.QuerySeter例子
// 创建orm对象
o := orm.NewOrm()
// 获取 QuerySeter 对象,并设置表名orders
qs := o.QueryTable("users")
// 定义保存查询结果的变量
var users []User
// 使用QuerySeter 对象构造查询条件,并执行查询。
num, err := qs.Filter("city", "shenzhen"). // 设置查询条件
Filter("init_time__gt", "2019-06-28 22:00:00"). // 设置查询条件
Limit(10). // 限制返回行数
All(&users, "id", "username") // All 执行查询,并且返回结果,这里指定返回id和username字段,结果保存在users变量
// 上面代码的等价sql: SELECT T0.`id`, T0.`username` FROM `users` T0 WHERE T0.`city` = 'shenzhen' AND T0.`init_time` > '2019-06-28 22:00:00' LIMIT 10
if err != nil {
panic(err)
}
fmt.Println("结果行数:", num)
2.QuerySeter查询表达式
beego orm针对QuerySeter设置一套查询表达式,用于编写查询条件。
提示:下面例子,使用Filter函数描述查询表达式,实际上其他查询函数也支持查询表达式。
表达式格式1:
qs.Filter("id", 1) // 相当于条件 id = 1
表达式格式2: 使用双下划线 __ 作为分隔符,尾部连接操作符
qs.Filter("id__gt", 1) // 相当于条件 id > 1
qs.Filter("id__gte", 1) // 相当于条件 id >= 1
qs.Filter("id__lt", 1) // 相当于条件 id < 1
qs.Filter("id__lte", 1) // 相当于条件 id <= 1
qs.Filter("id__in", 1,2,3,4,5) // 相当于In语句 id in (1,2,3,4,5)
下面是支持的操作符:
-
exact / iexact 等于
-
contains / icontains 包含
-
gt / gte 大于 / 大于等于
-
lt / lte 小于 / 小于等于
-
startswith / istartswith 以…起始
-
endswith / iendswith 以…结束
-
in
-
isnull 后面以 i 开头的表示:大小写不敏感
例子:
qs.Filter("Username", "大锤") // 相当于条件 name = '大锤'
qs.Filter("Username__exact", "大锤") // 相当于条件 name = '大锤'
qs.Filter("Username__iexact", "大锤") // 相当于条件 name LIKE '大锤'
qs.Filter("Username__iexact", "大锤") // 相当于条件 name LIKE '大锤'
qs.Filter("Username__contains", "大锤") // 相当于条件 name LIKE BINARY '%大锤%' , BINARY 区分大小写
qs.Filter("Username__icontains", "大锤") // 相当于条件 name LIKE '%大锤%'
qs.Filter("Username__istartswith", "大锤") // 相当于条件 name LIKE '大锤%'
qs.Filter("Username__iendswith", "大锤") // 相当于条件 name LIKE '%大锤'
qs.Filter("Username__isnull", true) // 相当于条件 name is null
qs.Filter("Username__isnull", false) // 相当于条件 name is not null
多个Filter函数调用使用 and 连接查询条件。 例子:
qs.Filter("id__gt", 1).Filter("id__lt", 100) // 相当于条件 id > 1 and id < 100
3.处理复杂的查询条件
上面的例子多个Filter函数调用只能生成and连接的查询条件,那么如果要设置or条件就不行了;beego orm为我们提供了Condition对象,用于生成查询条件。
例子:
// 创建一个Condition对象
cond := orm.NewCondition()
// 组织查询条件, 并返回一个新的Condition对象
cond1 := cond.And("Id__gt", 100).Or("City","shenzhen")
// 相当于条件 id > 100 or city = 'shenzhen'
var users []User
qs.SetCond(cond1). // 设置查询条件
Limit(10). // 限制返回数据函数
All(&users) // 查询多行数据
3.查询数据
3.1.查询多行数据
使用All函数可以返回多行数据。
例子:
// 创建orm对象
o := orm.NewOrm()
// 获取 QuerySeter 对象,并设置表名orders
qs := o.QueryTable("users")
// 定义保存查询结果的变量
var users []User
// 使用QuerySeter 对象构造查询条件,并执行查询。
// 等价sql: select * from users where id > 1 and id < 100 limit 10
num, err := qs.Filter("Id__gt", 1).
Filter("Id__lt", 100).
Limit(10). // 限制返回行数
All(&users) // 返回多行数据, 也可以设置返回指定字段All(&users, "id", "username")
3.2.查询一行数据
使用One函数返回一条记录
var user User
// 等价sql: select * from users where id = 1 limit 1
err := o.QueryTable("users").Filter("id", 1).One(&user)
if err == orm.ErrNoRows {
fmt.Printf("查询不到数据")
}
One也可以返回指定字段值, 例: One(&user, "id", "username")
3.3. Group By & Order BY
这里介绍一个包含group by, order by语句的例子
// 创建orm对象
o := orm.NewOrm()
// 获取 QuerySeter 对象,并设置表名orders
qs := o.QueryTable("users")
// 定义保存查询结果的变量
var users []User
// 使用QuerySeter 对象构造查询条件,并执行查询。
// 等价sql: select * from users where id > 1 and id < 100 group by city order by init_time desc limit 10
num, err := qs.Filter("Id__gt", 1).
Filter("Id__lt", 100).
GroupBy("City"). // 根据city字段分组
OrderBy("-InitTime"). // order by字段名前面的减号 - , 代表倒序。
Limit(10). // 限制返回行数
All(&users)
if err != nil {
panic(err)
}
fmt.Println("结果行数:", num)
3.4. Count统计总数
sql语句中的count语句的例子
// 这里可以忽略错误。
num, _ := o.QueryTable("users").Filter("Id__gt", 1).Filter("Id__lt", 100).Count()
// 等价sql: select count(*) from users where id > 1 and id < 100
fmt.Printf("总数: %s", num)
4.更新数据
使用QuerySeter更新数据,可以根据复杂的查询条件更新数据, 用法组织好查询条件后调用Update函数即可。
例子:
// Update参数,使用的是orm.Params对象,这是一个map[string]interface{}类型, 用于指定我们要更新的数据
num, err := o.QueryTable("users").Filter("Id__gt", 1).Filter("Id__lt", 100).Update(orm.Params{
"City": "深圳",
"Password": "123456",
})
// 等价sql: update users set city = '深圳', password = '123456' where id > 1 and id < 100
fmt.Printf("影响行数: %s, %s", num, err)
5,删除数据
组织好查询条件后,调用Delete函数即可。
num, err := o.QueryTable("users").Filter("Id__gt", 1).Filter("Id__lt", 100).Delete()
// 等价sql: delete from users where id > 1 and id < 100
fmt.Printf("影响行数: %s, %s", num, err)
beego orm如何执行SQL查询
beego orm包除了支持model查询的方式,也支持直接编写sql语句的方式查询数据。
sql原生查询有如下特点:
-
使用 Raw SQL 查询,无需使用 ORM 表定义
-
多数据库,都可直接使用占位符号 ?,自动转换
-
查询时的参数,支持使用 Model Struct 和 Slice, Array
1.原生sql查询
在遇到比较复杂的查询的时候,使用sql语句更加灵活和直观,也比较容易把控sql查询的性能。
1.1. 执行插入、更新、删除SQL语句
执行insert、update、delete语句,需要使用Exec函数,执行后返回 sql.Result 对象,通过sql.Result对象我们可以查询最新插入的自增ID,影响行数。
例子:
// 创建orm对象
o := orm.NewOrm()
// insert
// 使用Raw函数设置sql语句和参数
res, err := o.Raw("insert into users(username, password) values(?, ?)", "tizi365", "123456").Exec()
// 插入数据的自增id
id := res.LastInsertId()
// update
res, err := o.Raw("update users set password=? where username=?", "654321", "tizi365").Exec()
// 获取更新数据影响的行数
rows := res.RowsAffected()
// delete
o.Raw("delete from users where username=?", "tizi365").Exec()
1.2. 查询语句
查询数据主要通过QueryRow和QueryRows两个函数,分别对应查询一条数据还是多条数据,这两个函数都支持将查询结果保存到struct中.
1.2.1. 查询一行数据
type User struct {
Id int
Username string
}
var user User
err := o.Raw("SELECT id, username FROM users WHERE id = ?", 1).QueryRow(&user)
1.2.2. 查询多行数据
type User struct {
Id int
UserName string
}
var users []User
num, err := o.Raw("SELECT id, username FROM users WHERE id > ? and id < ?", 1, 100).QueryRows(&users)
if err == nil {
fmt.Println("查询总数: ", num)
}
2.QueryBuilder sql生成工具
除了上面直接手写sql语句之外,beego orm也为我们提供了一个工具QueryBuilder对象,可以用来生成sql语句.
例子:
// 定义保存用户信息的struct
type User struct {
Id int
Username string
Password string
}
// 定义保存结果的数组变量
var users []User
// 获取 QueryBuilder 对象. 需要指定数据库驱动参数。
// 第二个返回值是错误对象,在这里略过
qb, _ := orm.NewQueryBuilder("mysql")
// 组织sql语句, 跟手写sql语句很像,区别就是sql语句的关键词都变成函数了
qb.Select("id", "username", "password").
From("users").
Where("id > ?").
And("id < ?").
Or("init_time > ?").
OrderBy("init_time").Desc().
Limit(10)
// 生成SQL语句
sql := qb.String()
// 生成这样的sql语句 SELECT id, username, password FROM users WHERE id > ? AND id < ? OR init_time > ? ORDER BY init_time DESC LIMIT 10
// 执行SQL
o := orm.NewOrm()
// 上面sql有三个参数(问号),这里传入三个参数。
o.Raw(sql, 1, 100, "2019-06-20 11:10:00").QueryRows(&users)
提示: 使用QueryBuilder生成sql语句还是直接手写sql,这个就看个人喜好了。
beego orm数据库事务处理
通常在一些订单交易业务都会涉及多个表的更新/插入操作,这个时候就需要数据库事务处理了,下面介绍beego orm如何处理mysql事务。
手动处理事务
// 创建orm对象
o := orm.NewOrm()
// 开始事务
tx, err := o.Begin()
// 开始执行各种sql语句,更新数据库,这里可以使用beego orm支持任何一种方式操作数据库
// 例如,更新订单状态
_, err1 := tx.QueryTable("orders").Filter("Id", 1001).Update(orm.Params{
"Status": "SUCCESS",
})
// 给用户加积分
_, err2 := tx.Raw("update users set points = points + ? where username=?", "tizi365", 100).Exec()
// 检测事务执行状态
if err1 != nil || err2 != nil {
// 如果执行失败,回滚事务
tx.Rollback()
} else {
// 任务执行成功,提交事务
tx.Commit()
}
自动处理事务
在一个闭包函数内执行事务处理,如果函数返回error则回滚事务。
// 创建orm对象
o := orm.NewOrm()
// 在闭包内执行事务处理
err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
// 准备数据
user := new(User)
user.Name = "test_transaction"
// 插入数据
// 使用txOrm执行SQL
_, e := txOrm.Insert(user)
return e
})
视图
beego 模板入门教程
beego 的视图(view)模板引擎是基于Go原生的模板库(html/template)进行开发的,因此在开始编写view模板代码之前需要先学习下Go内置模板引擎的语法。
提示: 如果还不了解Go内置模板引擎(html/template)的模板语法,可以点击这里学习下Go内置模板引擎教程
beego模板,默认支持 tpl 和 html 的后缀名。
1.基础例子
下面看个视图模板的例子。 模板文件: views/user/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户个人信息:</h1>
<p>
{{ if ne .user nil}}
用户名: {{.user.Username}} <br/>
注册时间: {{.user.InitTime}}
{{else}}
用户不存在!
{{end}}}
</p>
</body>
</html>
下面看控制器如何渲染这个模板文件。
// 处理get请求
func (this *UserController) Get() {
// 初始化,模板渲染需要的数据
user := &User{1, "tizi365", time.Now()}
// 通过Data, 将参数传入模板, Data是map类型支持任意类型数据
this.Data["user"] = user
// 设置我们要渲染的模板路径, 也就是views目录下面的相对路径
// 如果你不设置TplName,那么beego就按照 <控制器名字>/<方法名>.tpl 这种格式去查找模板文件。
this.TplName = "user/index.html"
// 如果你关闭了自动渲染,则需要手动调用渲染函数, beego 默认是开启自动渲染的
// this.Render()
}
提示:在app.conf配置文件中配置AutoRender参数为true或者false,表示是否开启自动渲染。
2.模板标签冲突
默认情况,模板引擎使用 {{ 模板表达式 }} 作为模板标签,如果我们前端开发使用的是Vue、angular之类的框架,这些前端框架也是使用 {{ 模板表达式 }} 作为模板标签,这样就造成冲突了。
我们可以通过修改Go模板引擎的默认标签解决模板标签冲突问题。
例子:
// 修改Go的模板标签
web.TemplateLeft = "<<<"
web.TemplateRight = ">>>"
修改后的模板表达式:
<<<.user.username>>>
你可以改成你喜欢的样式。
beego模板函数
模板引擎为我们提供了函数机制,方面我们在处理模板时执行一些特定的功能,例如格式化输出内容、字母大小写转换等等。
1.函数调用语法
语法格式:
functionName [Argument...]
Argument参数是可选的,如果有多个参数,参数直接用空格分隔。
例子:
{{html "<h1>www.tizi365.com</h1>"}}
输出:
<h1>www.tizi365.com</h1>
html函数的作用是对输入参数进行html转义处理,html标签都转义了。
多个函数参数的例子:
{{printf "%d - %s" 100 "www.tizi365.com"}}
printf函数主要用于格式化输出字符串,是fmt.Sprintf函数的别名,用法跟fmt.Sprintf函数一样,区别就是模板函数的参数用空格隔开, 这里为printf输入了3个参数。 下面是输出:
100 - www.tizi365.com
2.内置模板函数
模板引擎预先定义了一些函数,我们可以直接在模板中进行函数调用,下面介绍常用的内置函数。
2.1. 关系运算和逻辑运算函数
在模板引擎中关系运算(==、<、<=、>、>=、!=)和逻辑运算(and、or、not)都封装成函数形式,也就是说我们需要通过函数调用的方式进行关系运算和逻辑运算。
下面是关系运算函数:
说明:下表中的arg1和arg2代表两个参数
函数名 | 函数调用格式 | 对应关系运算 | 说明 |
---|---|---|---|
eq | eq arg1 arg2 | arg1 == arg2 | arg1等于arg2则返回true |
ne | ne arg1 arg2 | arg1 != arg2 | arg1不等于arg2则返回true |
lt | lt arg1 arg2 | arg1 < arg2 | arg1小于arg2则返回true |
le | le arg1 arg2 | arg1 <= arg2 | arg1小于等于arg2则返回true |
gt | gt arg1 arg2 | arg1 > arg2 | arg1大于arg2则返回true |
ge | ge arg1 arg2 | arg1 >= arg2 | arg1大于等于arg2则返回true |
下面是逻辑运算函数:
函数名 | 函数调用格式 | 对应逻辑运算 | 说明 |
---|---|---|---|
and | and 表达式1 表达式2 | 表达式1 && 表达式2 | 表达式1和表达式2都为真的时候返回true |
or | or 表达式1 表达式2 | 表达式1 || 表达式2 | 表达式1和表达式2其中一个为真的时候返回true |
not | not 表达式 | !表达式 | 表达式为false则返回true, 反之返回false |
提示: 关系运算和逻辑运算函数通常跟if语句一起使用
例子:
{{$x := 100}}
//等价于$x == 100
{{if eq $x 100}}
...代码...
{{end}}
//等价于$x < 100
{{if lt $x 500}}
...代码...
{{end}}
//等价于$x >= 100
{{if ge $x 500}}
...代码...
{{end}}
//等价于$x > 50 && $x < 200
//这里调用了and函数和gt、lt三个函数, gt和lt函数的结果作为and的参数,gt和lt函数调用分别用括号包括起来
{{if and (gt $x 50) (lt $x 200)}}
...代码...
{{end}}
{{$y := 200}}
//等价于$x > 100 || $y > 100
{{if or (gt $x 100) (gt $y 100)}}
...代码...
{{end}}
2.2. html函数
对html内容进行转义处理,前面的例子已经介绍。
2.3. len函数
用于计算数组大小。 例子:
数组大小: {{len .}}
假如传入的模板参数是一个数组:
//模板参数定义如下
a := []int{1,2,3,4}
输出:
数组大小: 4
2.4. printf函数
主要用于格式化字符串,是go fmt.Sprintf函数的别名,前面的例子已经介绍。
2.5. urlquery函数
主要用于url编码。 例子:
/search?keyword={{urlquery "搜索关键词"}}
输出:
/search?keyword=%E6%90%9C%E7%B4%A2%E5%85%B3%E9%94%AE%E8%AF%8D
3.pipeline
pipeline 翻译过来可以称为管道或者流水线, pipeline运算的作用是将多个函数调用或者值串起来,从左往右执行,左边执行的结果会传递给右边,形成一个任务流水。
pipeline运算符:| (竖线)
语法格式:
command1 | command2 | command3 ...
command可以是一个值,也可以是一个函数。
例子1:
{{"<h1>www.tizi365.com</h1>" | html}}
这里意思就是将第一个字符串值传递给html函数。
输出:
<h1>www.tizi365.com</h1>
例子2:
{{"关键词" | html | urlquery}}
这个例子就是先将 "关键词" 传递给html函数转义下html标签,然后在将html执行结果传递给urlquery函数进行url编码。
输出:
%E5%85%B3%E9%94%AE%E8%AF%8D
提示: 如果函数有多个参数,pipeline运算会将值传递给函数的最后一个参数, 例如: {{100 | printf "value=%d"}}, 这里将100传递给printf函数的最后一个参数。
4.自定义模板函数
内置的模板函数使用有限,我们可以自己定义模板函数。
下面代码展示如何自定义模板函数:
// 第一步,我们先创建FuncMap, 然后注册我们要定义的函数
// FuncMap是一个map类型
funcMap := template.FuncMap{
// "toupper" 就是我们在模板中可以调用的函数名,
// strings.ToUpper就是我们要注册的函数,
// 他的作用是将小写字母转成大写字母,这里我们直接将golang自带的函数注入进去。
// 当然你也可以自己写一个函数
"toupper": strings.ToUpper,
}
// 这里定义个模板代码
const templateText = `
自定义函数调用例子:{{"abcdef" | toupper}}
`
// 创建一个template对象,模板名字为test,
// 然后调用Funcs注册我们定义的模板函数,
// 然后调用Parse加载templateText模板代码。
tmpl, err := template.New("test").Funcs(funcMap).Parse(templateText)
if err != nil {
log.Fatalf("解析模板失败: %s", err)
}
// 渲染模板
err = tmpl.Execute(os.Stdout, "")
if err != nil {
log.Fatalf("渲染模板失败: %s", err)
}
输出:
自定义函数调用例子:ABCDEF
Go 模板引擎子模版用法
在实际项目中,我们不可能只有一个模板,一般来说都有很多个模板,而且这些模板也会共享一些公共的模板,这些公共的模板我们都可以定义成子模板,在需要的时候调用子模板,就可以将子模板的内容嵌入当前模板中。
提示:在项目中使用子模板,可以让项目模板具有模块化的能力,提高模块复用能力和可维护性。
1.定义子模板
定义子模板语法:
{{define "子模板名字"}}
模板内容
{{end}}
例子:
{{define "T1"}}
模板内容T1
{{end}}
{{define "T2"}}
模板内容T2
{{end}}
2.调用子模板
调用子模板语法:
{{template "子模板名字" 参数}}
template函数的第一个参数是模板名字,第二个参数是模板参数, 在子模板内部也是通过点( . ),引用模板参数。
提示: template的第二个模板参数是可选的。
例子:
//模板代码
//这里定义了T1 T2 T3三个模板,T3调用了T1和T2的模板内容,最后我们调用T3模板内容
const templateText = `{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}`
// 创建一个template对象,模板名字为test,然后调用Parse加载templateText模板代码。
tmpl, err := template.New("test").Parse(templateText)
if err != nil {
log.Fatalf("解析模板失败: %s", err)
}
// 渲染模板
err = tmpl.Execute(os.Stdout, "")
if err != nil {
log.Fatalf("渲染模板失败: %s", err)
}
输出:
ONE TWO
3.模板管理
上面的例子,我们将模板代码定义在一个变量或者常量中,这个只是用于演示,实际项目中模板代码通常非常多,建议大家按如下方式组织模板代码:
-
一个模块的模板代码,保存在一个模板文件中,模板文件名为tpl。
-
所有的模板代码都定义在子模板中,方便根据模板名字进行渲染。
例子: 模板目录views, 下面分别按功能模块创建不同的模板文件
创建公共模板文件: views/common.tpl 主要用于保存一些公共的模板定义
{{define "common1"}}
这里是共享模块1
{{end}}
{{define "common2"}}
这里是共享模块2
{{end}}
创建mod1模块的模板文件: views/mod1.tpl
{{define "mod1"}}
这里是模块1
{{- template "common1"}}
{{end}}
创建mod2模块的模板文件: views/mod2.tpl
{{define "mod2"}}
这里是模块2
{{- template "common2"}}
{{end}}
渲染模板代码:
//创建template对象,并且加载views目录下面所有的tpl模板文件。
t := template.Must(template.ParseGlob("views/*.tpl"))
// 渲染mod1子模板
t.ExecuteTemplate(os.Stdout, "mod1", nil)
// 渲染mod2子模板
t.ExecuteTemplate(os.Stdout, "mod2", nil)
输出:
这里是模块1
这里是共享模块1
这里是模块2
这里是共享模块2