今天主要来简单学习一下beego,一个比较流行的go框架。前段时间学习了go语言的基础,対go算是有了一个简单了解,但是实际开发中肯定会少不了各种框架得使用,所以我这次选择了一个比较流行得框架——beego,来做为go语言框架入门,beego官网。这里简单先说一下自己得感觉,首先beego是一个一体式的框架,也就是说,它包含了所有项目中常用的所有模块,不像java,不同得部分可能需要不同得框架(当然spring boot现在也能算是一个一体式)。
今天主要学习得就是beego得MVC架构,它主要包括三个部分:model、controller、view。记得当时学习go和数据库交互的时候就说过,go自己和数据库交互其实感觉不太方便,今天看看beego的ORM是不是会带来惊喜。开始学习和写代码之前,先做好准备工作,即安装beego和bee工具,安装过程很简单,这里就不再叙述了,然后创建一个beego项目,项目结构如下图:
根据项目结构就可以很直观的得看出来这是一个典型的MVC架构,main函数是整个应用得入口,routers主要是进行路由注册,即将用户请求得URL和相应得controller进行映射,models下面主要是beego得ORM模块,另外数据模型,即所谓得struct我也是放在这个目录下的。controller这个都比较熟悉了,就不说了。conf下主要是项目配置文件。static下面是项目得静态文件。views下面是项目得模板,当然我使用的还是html,没有使用模板。
开始之前还是先了解下项目得执行过程,下面这张图就是从beego官网上,关于go的执行过程,这张图非常直观的,main函数执行前会先完成它引入包的init函数,依次类推。最终等main函数自己得init函数执行完之后再执行main函数,当然也不是必须通过init方法,也可以自己定义一个方法,然后显性的调用也是可以的。
一、项目配置
beego目前也是支持多种格式的配置文件,比如xml、yml、json等,但是但是默认采用了 INI 格式解析。另外就是可以支持多个配置文件引入,比如这个项目我将数据库相关的配置单独的作为一个配置文件,然后默认配置文件中引入数据库配置文件就行了,比如:
appname=helloGo
httpport=9090
runmode=dev
# 视图的路径
# viewspath="views/user"
viewspath="views"
# 引入的其他配置文件
include "database.conf"
# 接收json参数
copyrequestbody=true
有一点不太方便的就是没有提示,不像spring boot可以提示,这个的话只能自己去查看常用的配置文档,当然也可以通过代码的方式指定相关的配置,选择代码还是配置文件就看自己的喜好或者习惯了。另外还可以指定不同运行环境下的配置,比如测试
二、路由映射
首先说一下就是beego项目的启动我感觉和spring boot项目启动非常的类似,main函数里面实际调用的是app的Run方法,这里先不往下深究了,暂时知道就可以了。首先我在main函数引入router这个包,这个包上面说了主要是路由的映射,即将用户请求的URL和相应的controller进行映射。根据go执行的过程图可以知道它会一级一级的把需要的所有包的init函数都执行一遍,或者用spring的思想就是会完成所有依赖的注入(不知道这么理解有没有问题,但是我觉得思路应该都是相通的)。所以可以在router的init函数里面完成URL和controller的映射,代码如下:
func init() {
/*固定路由*/
beego.Router("/", &controllers.MainController{})
beego.Router("/user",&controllers.UserController{})
beego.BConfig.EnableGzip = true
beego.BConfig.RouterCaseSensitive = true
beego.BConfig.MaxMemory = 1 << 26
beego.BConfig.WebConfig.AutoRender = true
beego.BConfig.CopyRequestBody = true
/*设置模板取值方式*/
//beego.BConfig.WebConfig.TemplateLeft = "${"
//beego.BConfig.WebConfig.TemplateRight = "}"
/*页面文件路径*/
//beego.BConfig.WebConfig.ViewsPath="views/user"
/*注解路由*/
beego.Include(&controllers.UserController{})
beego.Include(&controllers.CommonController{})
beego.Include(&controllers.FileController{})
beego.Include(&controllers.LoginController{})
/*同时注册多个路由*/
beego.Include(&controllers.UserController{},&controllers.CommonController{},&controllers.FileController{})
//beego.ErrorController(&controllers.ErrorController{})
}
init函数里面有固定路由即每一个固定的URL都会对应一个controller或者通过正则路由,即满足一定格式的URL对应一个controller,因为我已经习惯了spring注解的方式,所以我比较喜欢的还是注解形式的路由方式。注解路由通过一个调用beego的Include函数将一个或者多个handler注册到应用中。另外beego也支持restful形式的路由,有兴趣的可以看下官方文档。另外就是init函数里面有一些设置,这些设置都是可以写到配置文件内的,这个我觉得不用特别的在意,还是看个人习惯。
注册好路由后还需要的就是将URL和对应的handler进行映射,所以每个controller都有一个URLMapping方法,代码如下:
type UserController struct {
beego.Controller
}
//配置注解路由
func (u *UserController) URLMapping() {
u.Mapping("QueryById",u.QueryById)
u.Mapping("QueryList",u.QueryList)
u.Mapping("Register",u.Register)
u.Mapping("SaveUser",u.SaveUser)
u.Mapping("UserJSON",u.UserJSON)
}
在beego项目中每一个controller结构都包含了一个beego.Controller,可以理解为自定义的controller继承了beego.Controller,而beego.Controller封装了很多我们所需要的信息,看下它的结构就明白了,建议还是看下源码。
其实这个方法主要就是将方法名称和对应的方法或者所谓的handler进行映射,而真正的路由是在相应的方法或者handler上的。比较熟悉springMVC应该都知道,在进行URL映射的时候,通常我们会在类上使用一个@requestMapping注解,然后方法上也会加上相应的@requestMapping,二者拼接在一起就完成整个的URL。beego的稍微有点区别,而且注解的形式也不太一样,下面就贴一个根据id查找用户的handler,这个URL上会附上请求的参数值,看下下面的代码:
// @router /user/get/:id [get]
func (u *UserController) QueryById() {
strId := u.Ctx.Input.Param(":id")
logs.Info(">>>> query user by userId start <<<<")
id,err := strconv.Atoi(strId)
user := models.QueryUserById(id)
checkError(err)
u.Data["User"] = user
bytes,err := json.Marshal(user)
u.Ctx.ResponseWriter.Write(bytes)
//u.TplName = "user/user.html"
}
// @router /user/get/:id [get]就是一个完整的注解路由,请求的URL是/user/get/:id,请求类型是GET请求。和springMVC确实不太一样,相对来讲beego的要更简洁一点(go语言相比java就简洁不少),但是go语言并不支持注解,这种路由形式准确的说应该叫注释,但是实际的作用确实和java的注解效果是相同的。当然我个人还是很喜欢使用这种方式的,虽然和springMVC不太一样,但是相差并不大,还是很好理解的。只要明白了用户请求、路由、映射、处理器等之间的关系,基本就没问题了,无非是具体怎么实现的问题。关于其他形式的路由方式这里就不过多介绍了,不过还是应该了解一下,毕竟具体使用哪种并不是自己能决定的。
三、models设计
这部分我主要说一下beego的ORM部分,前面学习go的时候没有使用任何的ORM框架,所有对数据库的CRUD都需要自己手写SQL,然后对结果进行处理,非常的不方便。
models可以像routers一样,在main函数引入models的包,然后在其init函数获取数据连接等等,但是自己定义一个函数,然后在项目的main函数,显性调用。像这样:
func main() {
// 数据库初始化
models.Init()
// 过滤器
//beego.InsertFilter("/*",beego.BeforeExec,controllers.UserFilter)
// 错误handler
beego.ErrorController(&controllers.ErrorController{})
beego.Run()
}
这里我自己定义了一个Init函数,并在main函数里面调用,当然也可以自己重写main的init函数,并在init函数内完调用(我觉得应该是可以的,但是按理放到init函数内更好)。接下来看看在models的Init函数内做了什么
func Init() {
logs.Info(">>>> database init start <<<<")
// 数据库驱动
orm.RegisterDriver("postgres",orm.DRPostgres)
// 注册数据库
username := beego.AppConfig.String("username")
password := beego.AppConfig.String("password")
url := beego.AppConfig.String("url")
dbname := beego.AppConfig.String("dbname")
datasource := "postgres://" + username + ":" + password + "@" + url + "/" + dbname + "?sslmode=disable"
orm.RegisterDataBase("default","postgres",datasource)
// 最大连接和空闲连接
orm.SetMaxOpenConns("default",30)
orm.SetMaxIdleConns("default",10)
// 注册model
orm.RegisterModel(new(User))
}
首先是注册数据库驱动,我使用的依然是postgresql,此外beego的ORM还支持mysql、oracle、sqlite和TiDB。应该说对关系型数据库这方面的支持稍微差了一点,比如就没有sql server。其次就是注册数据库,beego的ORM必须注册一个默认名称为default的数据库。数据源所需参数如:数据库名称、密码以及URL等信息都是写在数据库配置文件内的,而获取配置文件的配置参数,是通过使用 beego.AppConfig加参数类型,如string bool int float等来获取。然后就是配置数据库连接池,最大连接、最大空闲连接等。最后注册model,当然是可以注册多个数据模型的,这一步我觉得其实还是很重要的,我的感觉就是将数据库表和对应的结构做一个映射关系,可以根据定义的结构创建数据库表,以及表与表之间的关联关系(什么一对一、一对多等等),另外如果要使用orm.QuerySeter 进行高级查询的话,这一步是必须的。感觉有点像hibernate,我自己是不太喜欢这种方式,当然这种难度感觉也会高一些,因为hibernate当时学的就挺晕的。下面这些代码是官方文档定义结构的一些例子:
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)"`
}
当然上面只是一部分,此外还可以设置主键、自增长等等,这里说一下 orm:"reverse(one)"
这种表达方式,我看到网上有人把这种形式称为go的注解,我也不知道对或者不对,知道它是一种表达形式即可,除了orm之外我知道的还有form和json,这两个下面会说到,关于orm其实心里挺排斥的,感觉有点复杂了,但是我觉得还是很有必要了解一下的。总的来说beego的ORM功能上确实十分强大,支持的查询方式也很多,比如对象的CRUD,还有高级查询、原生的SQL查询还有构造查询。具体怎么使用就看自己了,当然我还是更喜欢自己来写SQL,但是还是要看具体场景。这次学习项目中会用到对象的CRUD也会使用到高级查询。这部分内容其实比较多,我自己并没有怎么去学习,可能还是受mybatis的影响吧,感觉自己写sql更方便,但是我觉得这部分还是应该多了解一下的。
关于views也即视图模板这部分,自己感觉会使用就好,和其他视图模板应该只是语法上的差别,而且现在基本都是前后端分离的开发模式,后端提供接口就好,但是掌握这部分也不算多,这部分就略过了。
四、项目
1、数据模型定义
接下来就是项目的开发,我还是从最简单的开始,什么登录、注册、查询之类,一是这些相对最简单适合入门,二是这次的内容还是蛮多的,只是自己选择性的忽略了一些。项目还是单库单表(ORM是支持多个数据库的),先从设计结构,即数据模型开始,这里会用到form和json这两种形式的注解(网上有人是这么叫的),看下面的代码:
type User struct {
Id int `form:"-" table:"user" json:"id"`
UserName string `form:"username" column:"username" json:"userName"`
Age int `form:"age" json:"age"`
Sex string `form:"sex" json:"sex"`
Mobile string `form:"mobile" json:"mobile"`
Password string `json:"password"`
Email string `form:"email" json:"email"`
}
form表示的是使用这个结构体接受前端form表单时对应的名称,"-"表示忽略这一项的值;table表示的这个结构体对应的数据库表的名称;column表示的是这个属性在数据库表中对应列的名称;json表示的是将这个结构体或者对象转成json对象后对应的key的名称,因为结构体里面如果属性对外可见,那么属性名称首字母必须大写,这样转成json后key的首字母也是大写,所以为了美观需要转换一下,当然换个key的名称也是可以。
然后再定义一个作为json响应结果的结构体。我建议是将数据库中有对应表的数据模型都进行注册,数据模型最好单独新建go文件,并在其init函数进行注册,因为我就一个数据模型,所以我放到models的Init函数中了。
2、编写相关handler
这个controller无非就是一些跳转页面、注册登陆之类的逻辑,以及数据库的CRUD,代码如下:
type LoginController struct {
beego.Controller
}
func (c *LoginController) URLMapping() {
c.Mapping("/login",c.Login)
c.Mapping("/register",c.Register)
c.Mapping("/register/do",c.DoRegister)
c.Mapping("/login/do",c.DoLogin)
}
// @router /login [get]
func (c *LoginController) Login() {
logs.Info(">>>> forward to login page start <<<<")
c.TplName = "login.html"
}
// @router /register [get]
func (c *LoginController) Register() {
logs.Info(">>>> forward to Register page start <<<<")
c.TplName = "register.html"
}
// @router /register/do [post]
func (c *LoginController) DoRegister() {
var user models.User
var result models.Result
err := json.Unmarshal(c.Ctx.Input.RequestBody,&user)
checkError(err)
userName := user.UserName
password := user.Password
email := user.Email
logs.Info(">>>> user register information username=" + userName + ",email=" + email + ",password=" + password + " <<<<")
pass,err := regexp.MatchString(`[a-zA-Z0-9]{8,16}`,password)
checkError(err)
if !pass {
result.Code = 404
result.Success = false
result.Message = "密码格式错误"
}
em,err := regexp.MatchString(`^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$`,email)
checkError(err)
if !em {
result.Code = 404
result.Success = false
result.Message = "邮箱格式错误"
}
// 对密码加密
user.Password = passwordEncode(password)
id,e := orm.NewOrm().Insert(&user)
if e != nil {
result.Code = 404
result.Success = false
result.Message = "系统异常"
logs.Error(e)
} else {
logs.Info(">>>> user register success,user id = " + strconv.FormatInt(id,10) + " <<<<")
result.Code = 200
result.Success = true
result.Message = "注册成功"
}
bytes,err := json.Marshal(&result)
c.Ctx.ResponseWriter.Write(bytes)
}
func passwordEncode(password string) string {
h := md5.New()
h.Write([]byte(password))
return hex.EncodeToString(h.Sum(nil))
}
// @router /login/do [post]
func (c *LoginController) DoLogin() {
logs.Info(">>>> user do login start <<<<")
var user models.User
// 表单映射成struct
err := json.Unmarshal(c.Ctx.Input.RequestBody,&user)
logs.Error(err)
user.Password = passwordEncode(user.Password)
right := models.QueryByNamePwd(user.UserName,user.Password)
var r models.Result
if right {
// 用户名和密码正确
r.Code = 200
r.Success = true
r.Message = "success"
} else {
r.Code = 404
r.Success = false
r.Message = "用户或密码错误"
}
bytes,err := json.Marshal(&r)
c.Ctx.ResponseWriter.Write(bytes)
}
另一个用户操作相关的controller
type UserController struct {
beego.Controller
}
//配置注解路由
func (u *UserController) URLMapping() {
u.Mapping("QueryById",u.QueryById)
u.Mapping("QueryList",u.QueryList)
u.Mapping("Register",u.Register)
u.Mapping("SaveUser",u.SaveUser)
u.Mapping("UserJSON",u.UserJSON)
}
// @router /user [get]
func (c *UserController) Register() {
logs.Info(">>>> forward to register page start <<<<")
c.TplName = "user/form.html"
}
// @router /user/get/:id [get]
func (u *UserController) QueryById() {
strId := u.Ctx.Input.Param(":id")
logs.Info(">>>> query user by userId start <<<<")
id,err := strconv.Atoi(strId)
user := models.QueryUserById(id)
checkError(err)
u.Data["User"] = user
bytes,err := json.Marshal(user)
u.Ctx.ResponseWriter.Write(bytes)
//u.TplName = "user/user.html"
}
// @router /user/list [get]
func (u *UserController) QueryList() {
logs.Info(">>>> query user list start <<<<")
// 数据库查询所有用户
users := models.QueryUserList()
//bytes,err := json.Marshal(users)
//checkError(err)
//u.Ctx.ResponseWriter.Write(bytes)
u.Data["List"] = users
u.TplName = "user/list.html"
}
// @router /user/save [post]
func (u *UserController) SaveUser() {
logs.Info(">>>> save register information start <<<<")
// 获取表单数据
//form := u.Ctx.Request.PostForm
//username := form.Get("username")
//age := form.Get("age")
//sex := form.Get("sex")
//mobile := form.Get("mobile")
// 表单转struct
var user models.User
err := u.ParseForm(&user)
checkError(err)
// 校验...
// 写入数据库,返回id值
id := models.InsertUser(&user)
println(id)
//var r orm.RawSeter
//insert := "insert into t_user (username,age,sex,mobile) values(?,?,?,?)"
//r = o.Raw(insert,username,age,sex,mobile)
//_,err := r.Exec()
//checkError(err)
u.Ctx.Redirect(302,"/success")
}
/*接收requestBody参数*/
// @router /json/save [post]
func (u *UserController) UserJSON() {
logs.Info(">>>> save user json information start <<<<")
// requestBody数据
var user models.User
err := json.Unmarshal(u.Ctx.Input.RequestBody,&user)
checkError(err)
// 数据库处理
id := models.InsertUser(&user)
println("insert user id=" + strconv.FormatInt(id,10))
u.Ctx.Redirect(302,"/success")
}
func checkError(e error) {
if e != nil {
log.Fatal(e)
}
}
这个controller有些地方可能实现方式略有不同,还有一点就是这个controller里面对数据库是我在models里面写的,使用的是ORM的高级查询,代码如下:
func QueryUserById(id int) *User {
var user User
orm := orm.NewOrm()
orm.QueryTable("user").Filter("id",id).One(&user,"username","age","sex","mobile")
logs.Info(">>>> query user by user id from database <<<<")
return &user
}
func QueryUserList() []*User {
var list []*User
orm := orm.NewOrm()
orm.QueryTable("user").All(&list,"username","age","sex","mobile")
return list
}
func InsertUser(u *User) int64 {
o := orm.NewOrm()
id,err := o.Insert(u)
if err!= nil {
log.Fatal(err)
}
return id
}
func QueryByNamePwd(username, password string) bool {
var user User
orm := orm.NewOrm()
err := orm.QueryTable("user").Filter("username",username).Filter("password",password).One(&user,"username","age","sex","mobile","email")
logs.Info(">>>> query user by user id from database <<<<")
var result bool
if err!=nil {
logs.Error(err)
result = false
} else {
result = true
}
return result
}
orm.QueryTable("user").Filter("id",id)....
第一个参数表示查询表的名称,filter是过滤条件,可以看成是SQL中的where条件,里面的参数就是对应的列名和值,One表示只有一个结果,All表示所有,里面一个是struct结构体接受查询的值,而后面的"username","age","sex","mobile"表示查询的列。我自己的感觉是简单的查询还好,复杂的话我宁愿写SQL。当然它的功能不止这些,还有一些模糊的查询比如startwith、contains等等,可以参考文档。另外还有构造查询感觉和写SQL没什么却别了,也可以了解一下。
另外就是错误处理器,beego 默认支持 401、403、404、500、503 这几种错误的处理。一般我们都会自定义错误的处理页面,我在main函数注册了一个处理错误的controller,但是只自定义了404和500两种
type ErrorController struct {
beego.Controller
}
func (c *ErrorController) URLMapping() {
c.Mapping("Error404",c.Error404)
c.Mapping("Error500",c.Error500)
}
func (c *ErrorController) Error404() {
logs.Info(">>>> forward to 404 error page <<<<")
c.Data["Content"] = "抱歉,查找的内容不存在"
c.TplName = "error/404.html"
}
func (c *ErrorController) Error500() {
logs.Info(">>>> forward to 500 error page <<<<")
c.Data["Content"] = "抱歉,系统错误,努力解决中"
c.TplName = "error/500.html"
}
这个里面方法名称好像是不可以变的,自己后面有时间再测试一下吧,另外就是自己其实在main函数还定义了一个过滤器,但是时间关系,自己还没有认真的去看,感觉和springMVC的拦截器有相似之处,都有在路由之前还是之后处理的逻辑,关于这部分内容下次再继续的研究吧!beego模块确实比较多,功能上看非常的全,而且还有缓存、日志等模块,使用beego可以说能解决项目中所需要的大部分问题,所以我说它是一个体式的框架,功能全而且强大。
这次项目的代码已经上传到我的github,最近自己也有使用beego做一个个人小网站的想法。关于文章中有什么疑问或者错误之处希望大家批评指正,谢谢!2018年的最后一天完成2018年的最后一篇文章,感觉还是有点感慨。祝大家2019实现所有的flag.........祝大家新年快乐!!
最后:自己在微信开了一个个人号:
超超学堂
,都是自己之前写过的一些文章,另外关注还有Java免费自学资料,欢迎大家关注。