美文网首页
beego学习入门

beego学习入门

作者: 非典型_程序员 | 来源:发表于2018-12-31 22:16 被阅读0次

    今天主要来简单学习一下beego,一个比较流行的go框架。前段时间学习了go语言的基础,対go算是有了一个简单了解,但是实际开发中肯定会少不了各种框架得使用,所以我这次选择了一个比较流行得框架——beego,来做为go语言框架入门,beego官网。这里简单先说一下自己得感觉,首先beego是一个一体式的框架,也就是说,它包含了所有项目中常用的所有模块,不像java,不同得部分可能需要不同得框架(当然spring boot现在也能算是一个一体式)。
    今天主要学习得就是beego得MVC架构,它主要包括三个部分:model、controller、view。记得当时学习go和数据库交互的时候就说过,go自己和数据库交互其实感觉不太方便,今天看看beego的ORM是不是会带来惊喜。开始学习和写代码之前,先做好准备工作,即安装beego和bee工具,安装过程很简单,这里就不再叙述了,然后创建一个beego项目,项目结构如下图:

    项目结构.png
    根据项目结构就可以很直观的得看出来这是一个典型的MVC架构,main函数是整个应用得入口,routers主要是进行路由注册,即将用户请求得URL和相应得controller进行映射,models下面主要是beego得ORM模块,另外数据模型,即所谓得struct我也是放在这个目录下的。controller这个都比较熟悉了,就不说了。conf下主要是项目配置文件。static下面是项目得静态文件。views下面是项目得模板,当然我使用的还是html,没有使用模板。
    开始之前还是先了解下项目得执行过程,下面这张图就是从beego官网上,关于go的执行过程,这张图非常直观的,main函数执行前会先完成它引入包的init函数,依次类推。最终等main函数自己得init函数执行完之后再执行main函数,当然也不是必须通过init方法,也可以自己定义一个方法,然后显性的调用也是可以的。
    go执行过程.png
    一、项目配置

    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免费自学资料,欢迎大家关注。

    二维码.jpg

    相关文章

      网友评论

          本文标题:beego学习入门

          本文链接:https://www.haomeiwen.com/subject/lwhvlqtx.html