使用 beego 搭建 web 应用之二

Developing a web application with Beego - Part 2

欢迎回到本系列的第二部分,在这我们将用Go的Beego这个web开发框架来加速开发。如果你错过了第一部分,我建议你去读一下,因为它是这一系列的基础。

在第一部分中,我们有了一个好的开始,通过安装Beego及命令行的Bee了解并实际使用了Beego,创建一个基本的项目,添加一个控制行为,创建了一个基本的视图模板,添加一个自定义路由并以学习如何使用请求参数为结尾。

在第二部分中,我们将进入更有趣的部分,构建一个结合数据库的Web应用,使用的是Sqlite3,还要研究一下模型表单以及验证。我希望你已经准备就绪了,下面让我们一块愉快的走到最后。

Garfielt
Garfielt
翻译于 3 个月 前

0人顶

 翻译的不错哦!

两步视图

你会注意到manage控制器里的几个函数,代码如下:

manage.Layout = "basic-layout.tpl"
manage.LayoutSections = make(map[string]string)
manage.LayoutSections["Header"] = "header.tpl"
manage.LayoutSections["Footer"] = "footer.tpl"

这些代码做的是建立了一个两步视图布局。你可能还不熟悉这个术语,它是指你在外部一直显示布局的地方,如侧边栏导航headerfooter以及那些基于被执行动作的可变内容。

2 Step View Layout

我用上图来说明我的意思。绿色区域是在外部,封装视图和红色部分是基于执行动作内容将要改变的。

通过参考Layout和LayoutSections,我们能够指定外部布局的视图模板,basic-layout.tpl,以及其他的子模板,在我们的例子中为一个header和footer,分别为header.tpl和footer.tpl。

这样做的话,我们行为模板生成的内容将被插入到封装视图模板中,如通过指定{{.LayoutContent}},而header和footer分别为{{.Header}}和{{.Footer}}。

Garfielt
Garfielt
翻译于 3 个月 前

0人顶

 翻译的不错哦!

模型

为添加数据库支持,我们需要做的几件事。首先,我们需要建立一些模型。模型基本上只是一些拥有额外信息的结构。下面是一个模型文件,在models/models.go中,我们将在应用的剩余部分中一直使用它。

package models

type Article struct {
    Id     int    `form:"-"`
    Name   string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"`
    Client string `form:"client,text,client:"`
    Url    string `form:"url,text,url:"`
}

func (a *Article) TableName() string {
    return "articles"
}

你可以看到里面有一个模型,Article,一个简单的网站文章模型,其包含四个属性: IdNameClient 和 Url。你需要注意的是每个属性都有额外的数据,上面提到的form 和valid。

这是将模型用来同时处理生成和验证的一个非常简单的方法。让我们通过实际使用这四个属性解释每个的作用。

Id int `form:"-"`

在我们的数据库中,ID是一个自递增字段。当添加新记录时这个值会自动生成,当删除,更新或查找记录时需要提供这个值。所以对于form:"-",我们要说的是Id不是必需的。

Name   string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"`
Garfielt
Garfielt
翻译于 3 个月 前

0人顶

 翻译的不错哦!

我们这里有一个稍微复杂一些的例子,让我们分解开来,以"name,text,name:"开头。它的意思是当表单被解析的时候,我们将会看到:

  • 来自表单中一个名为name的字段值将会用来初始化Name属性

  • 该字段将会是一个文本字段

  • 字段的标签将会被设置为'name'。

接下来,让我们看一下valid:"MinSize(5);MaxSize(20)"。它指定了两个校验规则:MinSize和MaxSize。效果是,该值至少有5个字符,至多不能超过20个字符。

这里有多个校验规则供你使用,包括区间(Range),Email,IP,Mobile,Base64以及电话号码

Client string `form:"client,text,client:"`
Url    string `form:"url,text,url:"`

在上面的两个例子中,Client将会读取表单字段client的值,是一个文本字段,并且字段的标签是client:,Url会读取表单字段url的值,同样是一个文本字段,标签是url:。现在轮到TableName函数了。

我之所以添加它是因为表article的名字和结构体的名字不一致,叫做articles。如果两者一样,Beego的ORM会自动找到它。

但是,我在这里故意修改了名字,因为我想向你演示一下当结构体(struct)和表的名字不一致时该怎么做。现在,我们要讨论一下表的结构。它应该包括下面这些。

CREATE TABLE "articles" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(200) NOT NULL,
    "client" varchar(100),
    "url" varchar(400) DEFAULT NULL,
    "notes" text,
    UNIQUE (name)
);
中奖啦
中奖啦
翻译于 3 个月 前

0人顶

 翻译的不错哦!

在应用中整合模型

现在,我们已经建立并配置我们的模型连同随附的表单和验证信息。我们需要让其在我们的应用程序中可用,在main.go中,需多添加三条import语句,如下所示。

"github.com/astaxie/beego/orm"
_ "github.com/mattn/go-sqlite3"
models "sitepointgoapp/models"

第一条是导入Beego的ORM库,第二条是为了支持sqlite3,被依赖是因为我们使用的是SQLite3数据库。第三条是导入我们刚刚创建的模型,给models一个别名。

func init() {
    orm.RegisterDriver("sqlite", orm.DR_Sqlite)
    orm.RegisterDataBase("default", "sqlite3", "database/orm_test.db")
    orm.RegisterModel(new(models.Article))
}

最后一步我们需要做的是,注册的驱动程序,数据库,以及我们将在我们的应用程序使用的模型。我们用上面的三条语句来这样做这事。我们声明,我们正在使用SQLite的驱动程序,并将其设置为默认的数据库连接,连接到我们的测试数据库,位于indatabase/ orm_test.db。 

最后,我们注册我们要使用的模型,在我们的案例中,只需models.Article模型。

AndyLam
AndyLam
翻译于 3 个月 前

1人顶

 翻译的不错哦!

CRUD 操作

做好这些,我们现在可以将数据库的支持集成到我们的应用程序中。让我们开始与两个简单的CRUD操作,删除更新。这些都不使用表单,因为我想保持这一节更注重ORM代码,而不是形式和验证码。我会在后面说道这些。

删除一条记录

我们试图从数据库中基于id删除一篇文章。在Inrouters/routers.go中,向你的function中添加一条:

beego.Router("/manage/delete/:id([0-9]+)", &controllers.ManageController{}, "*:Delete")

然后将代码添加到controllers/manage.go,也就是:

func (manage *ManageController) Delete() {
    // convert the string value to an int
    articleId, _ := strconv.Atoi(manage.Ctx.Input.Param(":id"))

在这里,我们试图取回id参数,并从String转换成int,然后使用STRCONV包中的Atoi方法就行了。这是一个简单的例子,所以我跳过这可能会引发任何错误,包括acticleId解析错误

  o := orm.NewOrm()
  o.Using("default")
  article := models.Article{}
似年
似年
翻译于 3 个月 前

1人顶

 翻译的不错哦!

接着,我们初始化一个新的ORM实例并确认我们使用了缺省的数据库。我们可以设置任意数量的数据库连接,例如一个用于读,一个用于写等。最后,我们创建一个新的,空的,Article模型实例。

   // Check if the article exists first
    if exist := o.QueryTable(article.TableName()).Filter("Id", articleId).Exist(); exist {
        if num, err := o.Delete(&models.Article{Id: articleId}); err == nil {
	    beego.Info("Record Deleted. ", num)
	} else {
	    beego.Error("Record couldn't be deleted. Reason: ", err)
	}
    } else {
	beego.Info("Record Doesn't exist.")
    }
}

首先,我们查询article表,检查是否有一篇文章的Id和传入的Id参数值匹配。如果存在记录,我们调用ORM的Delete方法,传入文章的Id。

如果没有错误返回,表示文章已经删除,然后调用beego.Info,用Info方法记录删除日志。如果不能删除此记录,将调用Error方法,传入err对象,显示文章不能被删除的原因。

地狱星星
地狱星星
翻译于 3 个月 前

1人顶

 翻译的不错哦!

更新一条记录

以上是删除一条记录,现在让我们更新一条记录;这次我将会使用flash信使。

func (manage *ManageController) Update() {
    o := orm.NewOrm()
    o.Using("default")
    flash := beego.NewFlash()

像以前一样,我们初始化一个ORM实例并指定默认数据库。然后我们取得一个Beego Flash对象的句柄,它可以在请求间存储信息。

    // convert the string value to an int
    if articleId, err := strconv.Atoi(manage.Ctx.Input.Param(":id")); err == nil {
	article := models.Article{Id: articleId}

这次我们尝试取得一个id参数并且当它存在时初始化一个新的Article模型。

	if o.Read(&article) == nil {
	    article.Client = "Sitepoint"
	    article.Url = "http://www.google.com"
	    if num, err := o.Update(&article); err == nil 
            {
		flash.Notice("Record Was Updated.")
		flash.Store(&manage.Controller)
		beego.Info("Record Was Updated. ", num)
	    }

接下来我们调用Read方法,传入这个Article对象,如果有一条记录与Article.Id指定的id相匹配,它会尝试从数据库中加载此article的其他属性。

假设数据库中有这个Id对应的记录,我们在这个article对象上设置了Client和Url属性并且将它传递给了Update方法,它会在数据库中更新这个记录。

--zxp
--zxp
翻译于 3 个月 前

1人顶

 翻译的不错哦!

假设没有发生错误,接下来我们调用Flash对象上的Notice函数并传入一个简单的消息,然后调用Store来持久化这个消息.

	} else {
		flash.Notice("Record Was NOT Updated.")
		flash.Store(&manage.Controller)
		beego.Error("Couldn't find article matching id: ", articleId)
	    }
	} else {
		flash.Notice("Record Was NOT Updated.")
		flash.Store(&manage.Controller)
		beego.Error("Couldn't convert id from a string to a number. ", err)
	}

如果出了点错,例如不能更新记录或者我们不能将id参数转换成integer类型,我们要在flash信息和log信息中加以标记。

	// redirect afterwards
	manage.Redirect("/manage/view", 302)
}

最后,我们调用Redirect方法,传入我们要重定向的url和http状态码。这样,不论我们能否更新此记录,我们都会被重定向到/manage/view,在这里我们可以看到定义的结果。

--zxp
--zxp
翻译于 3 个月 前

2人顶

 翻译的不错哦!

查看所有记录

View函数主要有两个目的:首先它需要展示article表中所有已存在的记录,并且还要展示我们在update操作时设置的flash信息,这样我们才能知道操作的成功与否。

func (manage *ManageController) View() {
    flash := beego.ReadFromRequest(&manage.Controller)

    if ok := flash.Data["error"]; ok != "" {
	// Display error messages
	manage.Data["errors"] = ok
    }

    if ok := flash.Data["notice"]; ok != "" {
	// Display error messages
	manage.Data["notices"] = ok
    }

首先我们通过读取请求并查找error与notice两个属性来初始化一个称为flash的变量。这两个属性与flash.Notice和flash.Error的调用相关联。如果已设置了此信息,我们要将其设置到Data属性,这样我们就可以在模板中访问它。

    o := orm.NewOrm()
    o.Using("default")

    var articles []*models.Article
    num, err := o.QueryTable("articles").All(&articles)

    if err != orm.ErrNoRows && num > 0 {
	manage.Data["records"] = articles
    }
}

对于最后两个示例,我们建立了一个与默认数据库的连接,并且初始化一个存放Article模型的数组articles。然后调用ORM的QueryTable方法,指定表名,然后调用它的All方法,传入那个articles数组,结果会被加载到articles中,然后就可以访问这些结果了。

如果没有意外的话,我们已经取得了所有可访问的记录并且将它们存储在了模板变量records中。

--zxp
--zxp
翻译于 3 个月 前

1人顶

 翻译的不错哦!

插入一条记录

现在让我们看看在Add action中添加一条记录,包括表单和验证(除了ORM交互)。

func (manage *ManageController) Add() {
    o := orm.NewOrm()
    o.Using("default")
    article := models.Article{}

由于前文已经提及,此处不再赘述。

    if err := manage.ParseForm(&article); err != nil {
	beego.Error("Couldn't parse the form. Reason: ", err)
    } else {
	manage.Data["Articles"] = article

此处我们调用ParseForm方法,传入article对象。假设没有异常,我们将article设置为一个模板变量,它会帮我们渲染一个表单,稍后我们会看到。

    if manage.Ctx.Input.Method() == "POST" {
	valid := validation.Validation{}
	isValid, _ := valid.Valid(article)
	if !isValid {
	    manage.Data["Errors"] = valid.ErrorsMap
	    beego.Error("Form didn't validate.")
	} else {

在这我们检查该方法是否使用的POST请求。如果是的话我们要实例化一个新的Validation对象,然后将这个article对象传入它的Valid方法,通过在这个模型上的规则来检查这个POST数据是否合法,

如果提交的数据不合法,我们要存储valid.ErrorsMap,模板变量errors还有验证失败的log中的所有验证错误。否则我们要尝试插入这个article记录。不管是否有错误,我们都要做如下log记录。

	id, err := o.Insert(&article)
	if err == nil {
	    msg := fmt.Sprintf("Article inserted with id:", id)
	    beego.Debug(msg)
	} else {
	    msg := fmt.Sprintf("Couldn't insert new article. Reason: ", err)
	beego.Debug(msg)
			}
		}
	}
}
--zxp
--zxp
翻译于 3 个月 前

0人顶

 翻译的不错哦!

结尾

我们已经浏览完了Beego。因为这个框架有很多特性,所以在这个只有两部分的系列教程中不可能包含它的所有特性。

另外,在今天的教程中一些示例可能看起来有点奇怪。这样做的原因不是为了好的编码实践,而是为了强调以一种半真实世界的方式实现的功能。这就是你觉得本文有点奇怪的原因。我希望你能享受这篇对Beego的简单介绍,并且希望你能尝试下这个框架。想到到目前为止我在它身上花费的时间,我很享受并打算继续使用它。

如果你有任何疑问,确认查看它的在线文档,也可以在本文提交评论。不要忘记,本文的代码也可以在Github仓库中获得,所以,check出它,然后摆弄并试验下吧。

--zxp
--zxp
翻译于 3 个月 前

0人顶

 翻译的不错哦!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。