Beego入门(二)

1.类型相关内容

在实现类型相关业务之前,我们先创建类型表。这里我们添加上一对多多对多的关系。

一个类型下面有很多篇文章,但是一篇文章只属于一个类型,所以文章与类型属于一对多。

同时我们分析,一个用户可以阅读多篇文章,一篇文章也可以被多个用户阅读,所以文章和用户之间属于多对多关系。

由此,我们开始建表,建表代码如下,我们根据代码分析一对多,多对多如何设置:

type User struct {
    Id int
    Name string `orm:"unique"`
    Passwd string `orm:"size(20)"`
    Articles []*Article `orm:"rel(m2m)"` //设置多对多关系
}
//文章结构体
type Article struct {
    Id int `orm:"pk;auto"`
    ArtiName string `orm:"size(20)"`
    Atime time.Time `orm:"auto_now"`
    Acount int `orm:"default(0);null"`
    Acontent string `orm:"size(500)"`
    Aimg string  `orm:"size(100)"`

    ArticleType*ArticleType `orm:"rel(fk)"` //设置一对多关系
    Users []*User `orm:"reverse(many)"`  //设置多对多的反向关系
}
//类型表
type ArticleType struct {
    Id int
    Tname string `orm:"size(20)"`
    Articles []*Article `orm:"reverse(many)"` //设置一对多的反向关系
}


func init(){
    //1.连接数据库
    orm.RegisterDataBase("default","mysql","root:123456@tcp(127.0.0.1:3306)/test?charset=utf8")
    //2.注册表
    orm.RegisterModel(new(User),new(Article),new(ArticleType))
    //3.生成表
    //1.数据库别名
    //2.是否强制更新
    //3.创建表过程是否可见
    orm.RunSyncdb("default",false,true)
}

根据我们以前学过数据库知识,表与表之间有几种关系?一般有三种,一对一,一对多,多对多,但是我们开发中常用的是一对多和多对多,这里我们重点掌握这两种,了解一对一即可。

orm中如何设置两个表之间的关系呢?

如果两个表之间有关系,ORM通过在两个表对应的结构体中添加对象指针或者对象指针数组来把两个表之间关联起来,并且在对象指针和对象指针数组字段添加上相应的属性,比如我们上面的文章表和类型表属于一对多,就需要在文章结构体中添加一个类型的对象指针,然后设置一对多关系(orm:"rel(fk)"),同样的,在类型表里面需要有一个文章的对象指针数组,并且设置一对多的反向关系(orm:"reverse(many)")。

  • **一对一 **

    关系设置:两个对应的结构体中都添加对方的结构体指针,然后设置一对一关系(orm:"rel(one)"),反向关系设置为orm:"rel(one)"

  • **一对多 **

    关系设置:一对多中两表之间的关系不可互换,以文章表和类型表为例,当创建表的时候

    • 在 文章表对应的文章结构体中添加类型表的对象指针,并且设置一对多关系(orm:"rel(fk)"),

    • 在 类型张表对应的结构体中添加文章表的对象指针数组,并且设置一对多的反向关系(orm:"reverse(many)"

    生成表的时候,数据库会自动在 文章表中添加类型表的表的Id作为文章表的外键。如图:

    [图片上传失败...(image-f30fc-1566458965867)]

    一对多插入操作:只需要在文章表插入类型对象即可。代码如下:

    o := orm.NewOrm()
    article := models.Article{}
    artiType := models.ArticleType{Id:id}
    o.Read(&artiType)
    article.ArticleType = &artiType
    o.Insert(&article)
    

    一对多查询: ORM做多表查询的时候默认是惰性查询,即不明确指出来要做多表查询,即便是两个表之间存在关系,ORM也不会给两个表做关联。指定多表查询的函数是RelatedSel()。参数是要关联的表名,可以有多个。代码如下:

    count,err = o.QueryTable("Article").RelatedSel("ArticleType").Count()
    

    如果关联表的那个字段没有值,那么数据查不到

  • 多对多

    关系设置:多对多中两表之间的关系是平等的,所以他们的属性设置可以呼唤,以文章表和用户表为例,当创建表的时候

    • 在 文章表对应的文章结构体中添加用户表的对象指针数组,并且设置多对多关系(orm:"rel(m2m)"),

    • 在用户表对应的结构体中添加文章表的对象指针数组,并且设置多对多的反向关系(orm:"reverse(many)"

    生成表的时候,数据库会生成一个用户和文章之间的关系表,有三个字段,Id,用户表Id,文章表ID。如下图:

1538796693152.png

多对多插入操作

o := orm.NewOrm()
//1.获取操作对象
arti:= Article{Id: 1}
//获取article的多对多操作对象
//第一个参数对象必须有主键,第二个参数是本表那个和外表有关联关系的多对多字段名
m2m := o.QueryM2M(&arti, "users")
//获取要插入的对象
user := &User{Id:1}
//这是根据条件查询到需要操作的其他表数据,方便后面操作
o.Read(&user)
//多对多对象插入
num, err := m2m.Add(user)//参数可以为对象,指针,对象数组,指针数组
o.Update(&arti)

多对多查询:

有两种方法:

第一种:直接用read查询,然后加上LoadRelated ()函数来关联两张表。代码如下:

post := Post{Id: 1}
err := o.Read(&post)
num, err := o.LoadRelated(&post, "Tags")

优点是简单,快捷。

缺点是返回值不是queryseter,不能调用其他的高级查询。

第二种方法,是通过过滤器查询,指定表之后,用Filter()过滤相应的条件,第一个参数是表示另一张表的字段__另外一张表的表名__比较的字段(注意是双下划线),第二个字段是要比较的值,需要注意的是这个顺序是和表的插入顺序相反的。代码如下:

  var posts []*Post
  num,err=dORM.QueryTable("post").Filter("Users__User__UserName","golang").All(&posts)

1.1添加类型

分析过多表之间的操作之后,我们来实现类型有关的业务,首先我们需要先添加类型。

1.1.1添加类型页面显示

  • 确定添加类型显示的请求路径为/AddArticleType

  • 在路由文件中添加相关代码。

    beego.Router("/addArticleType",&controllers.ArticleController{},"get:ShowAddType")
    
  • 然后去控制器中实现ShowAddType函数,先简单的指定视图。代码如下:

    //展示添加文章类型页面
    func(this*ArticleController)ShowAddType(){
      this.TplName = "addType.html"
    }
    
  • 然后在浏览器输入请求http://192.168.110.74:8080/addArticleType,页面显示如下:

1538797924716.png

由页面可知,我们添加文章类型界面,分两块,一块是上面以表格的形式显示所有类型,一块是下面增加分类。我们先来处理增加分类。

1.1.2添加类型数据处理

添加类型业务比较简单,首先是修改我们的视图页面内容,给form标签请求方式和请求路径,代码如下:

<form method="post" action="/HandleAddType">

接着我们要修改路由文件,给请求指定控制器,指定方法:

beego.Router("/addArticleType",&controllers.ArticleController{},"get:ShowAddType;post:HandleAddType")

然后我们实现一下后台处理函数,这个函数的实现步骤和以前实现添加文章的步骤一样,代码处理还更简单,不详细分析,我们直接看代码:

//处理添加文章类型数据
func(this*ArticleController)HandleAddType(){
    //获取数据
    typeName := this.GetString("typeName")
    //数据校验
    if typeName == ""{
        beego.Info("添加数据失败")
        return
    }

    //插入数据库
    o := orm.NewOrm()
    var articleType models.ArticleType
    articleType.Tname = typeName
    if _,err :=o.Insert(&articleType);err != nil{
        beego.Info("添加数据失败")
        return
    }

    //返回视图
    this.TplName = "addType.html"
}

这里我们用渲染的方式返回视图合适不合适,思考一下!

1.1.3查询类型数据

现在我们类型表有数据了,可以在显示页面的时候把数据填充在页面上

  • 后台代码

    //展示添加文章类型页面
    func(this*ArticleController)ShowAddType(){
      //查询数据
      o := orm.NewOrm()
      var articleTypes []models.ArticleType
      o.QueryTable("ArticleType").All(&articleTypes)
    
      //传递数据给视图并指定视图
      this.Data["articleTypes"] = articleTypes
      this.TplName = "addType.html"
    }
    
  • 视图代码

    在视图页面中,我们循环控制器传递过来的数组,拿到我们需要的数据

    {{range .articleTypes}}
        <tr>
           <td>{{.Id}}</td>
           <td>{{.Tname}}</td>
           <td><a href="javascript:;" class="edit">删除</a></td>
        </tr>
    {{end}}
    

    这时候我们在浏览器输入地址http://192.168.110.75:8080/addArticleType,得到如下页面:

1538841520043.png

添加一个类型测试,然后发现页面还是没有类型显示,这个说明我们代码处理出问题了,哪里出问题了呢?还记得前面给大家留的思考题吗?我们添加完文章类型之后,是直接渲染加载了视图,这时候并没有给视图传递数据,所以也就没有类型显示。这样的结果和我们的业务 不符合,所以我们需要把添加完类型之后跳转页面的方式改为重定向,然后再看结果,发现类型显示正常。

1.2首页根据下拉框选项不同,获取不同类型数据

现在有类型数据了,我们添加文章的时候也需要添加上类型了。

1.2.1添加带类型的文章

  • 在展示页面的时候需要把类型数据绑定添加类型的下拉框

    • 后台获取数据(在展示添加文章界面那个函数里面写相关代码)

      //展示添加文章界面
      func (this*ArticleController)ShowAddArticle(){
          //查询数据
          o := orm.NewOrm()
          var articleTypes []models.ArticleType
          o.QueryTable("ArticleType").All(&articleTypes)
      
          //传递数据给视图并指定视图
          this.Data["articleTypes"] = articleTypes
          this.TplName = "add.html"
      }
      
    • 视图展示数据

      循环获取数据,在下拉框中显示类型名称

      <select class="sel_opt" name="select">
           {{range .articleTypes}}
                  <option>{{.Tname}}</option>
           {{end}}
      </select>
      
  • 添加文章的时候指定文章类型,代码如下:

    //给文章对象指定文章类型
      var articleType models.ArticleType
      articleType.Tname = typeName
      o.Read(&articleType,"Tname")
      article.ArticleType = &articleType
      //插入
      o.Insert(&article)
    

1.2.2列表页展示文章时,展示类型信息。

  • 查询所有问章,关联文章类型表(查询的时候加上RelatedSel("ArticleType")),代码如下:

    qs.Limit(pageSize,start).RelatedSel("ArticleType").All(&articles)
    

    显示的时候显示出来

    {{range .articles}}
         <tr>
             <td>{{.ArtiName}}</td>
             <td><a href="ShowArticleDetail?id={{.Id}}">查看详情</a></td>
             <td> {{.Atime.Format "2006-01-02-15-04-05"}}</td>
             <td>{{.Acount}}</td>
             <td><a href="/DeleteArticle?id={{.Id}}" class="dels">删除</a></td>
             <td><a href="UpdateArticle?id={{.Id}}">编辑</a></td>
             <td>{{.ArticleType.Tname}}</td>
         </tr>
    {{end}}
    

    这时候你发现,以前添加的文章都没有显示,还记得我们前面介绍多表操作的时候介绍的吗,加上RelatedSel之后,如果相应的字段没有数据,将查询不出来。

1.2.3根据下拉框选项不同,获取不同类型数据

  • 查询类型数据,并把数据绑定到下拉框

    这个业务代码和添加文章的业务代码一样,我们就不做详细分析,直接看代码:

    //查询数据
      var articleTypes []models.ArticleType
      o.QueryTable("ArticleType").All(&articleTypes)
    
      this.Data["articleTypes"] = articleTypes
    

    视图代码:

    <select name="select" id="select" class="sel_opt">
        {{range .articleTypes}}
              <option selected="true">{{.Tname}}</option>
        {{end}}
    </select>
    
  • 根据下拉框选中类型,获取相同类型的文章

    • 把选中的类型数据传递给后台

      我们以前传递数据是用form表单,这里我们还是用form表单把下拉框包起来,然后把选中的数据传递给后台。代码如下:

      <form method="get" action="/ShowArticleList">
           <select name="select" id="select" class="sel_opt">
                {{range .articleTypes}}
                     <option selected="true">{{.Tname}}</option>
                {{end}}
           </select>
      </form>
      

      思考,我们为什么用get请求不用post请求

      这里没有发送请求按钮(尽量不要改美工设计的页面),我们通过js代码发送请求,js代码如下:

      $("#select").change(function () {
           $("#form").submit()
      })
      
    • 根据获取的类型,查询有多少条数据,以及显示相同类型的文章

      • 获取前端传递过来的数据

        //获取类型名称
          typeName := this.GetString("select")
        
      • 根据类型,查询有多少条符合条件的数据,但是,需要注意这里面要考虑没有传递类型名称的请求,所以需要做个判断,代码如下:

        //获取类型名称
        typeName := this.GetString("select")
        //查询数据,以及分页显示
        o := orm.NewOrm()
        qs := o.QueryTable("Article")
        var count int64
        //数据校验
        if typeName == ""{
          count,_ =qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Count()
        }else {
          count,_ =qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Count()
        }
        
      • 其他处理分页的业务代码不变,代码如下:

        //确定每页显示数
        pageSize := 2
        //获取总页数
        pageCount :=math.Ceil(float64(count) / float64(pageSize))
        //获取页码
        pageIndex,err := this.GetInt("pageIndex")
        if err != nil{
          pageIndex = 1
        }
        //确定数据的起始位置
        start := (pageIndex - 1) * pageSize
        
      • 根据类型查询相同类型的数据,同样需要做一个判断。代码如下:

        //查询相应类型的数据
        var articles []models.Article
        if typeName ==""{
          qs.RelatedSel("ArticleType").Limit(pageSize,start).All(&articles)
        }else {
          qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Limit(pageSize,start).All(&articles)
        }
        
      • 其他代码不变,获取列表页完整代码如下:

        func(this*ArticleController)ShowArticleList(){
          //获取类型名称
          typeName := this.GetString("select")
          //查询数据,以及分页显示
          o := orm.NewOrm()
          qs := o.QueryTable("Article")
        
          var count int64
          //数据校验s
          if typeName == ""{
              count,_ =qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Count()
          }else {
              count,_ =qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Count()
          }
        
          //确定每页显示数
          pageSize := 2
          //获取总页数
          pageCount :=math.Ceil(float64(count) / float64(pageSize))
        
          //获取页码
          pageIndex,err := this.GetInt("pageIndex")
          if err != nil{
              pageIndex = 1
          }
          //确定数据的起始位置
          start := (pageIndex - 1) * pageSize
        
          //查询相应类型的数据
          var articles []models.Article
          if typeName ==""{
              qs.RelatedSel("ArticleType").Limit(pageSize,start).All(&articles)
          }else {
              qs.RelatedSel("ArticleType").Filter("ArticleType__Tname",typeName).Limit(pageSize,start).All(&articles)
          }
          
          //查询数据库部分数据
          //获取类型数据
          //查询数据
          var articleTypes []models.ArticleType
          o.QueryTable("ArticleType").All(&articleTypes)
        
          this.Data["articleTypes"] = articleTypes
          this.Data["count"] = count
          this.Data["pageCount"] = int(pageCount)
          this.Data["pageIndex"] = pageIndex
        
        
          //传递数据并指定视图
          this.Data["articles"] = articles
          this.TplName = "index.html"
        }
        

        这时候你再看页面,会发现一个问题,有一个选项一直都不能够选中。为什么呢?

        是因为,我们每一次改变下拉框的选项,都会让js发出get请求给后台,后台就会重新查询所有的类型表绑定下拉框,所以每次显示的都是一个数据,这样的话,我们选中显示的那条数据,就无法触发js发送请求,因为js认为下拉框显示内容并没有变化。这时候下拉框显示也有问题,那怎么解决这个问题呢?

1.2.4解决下拉框选项显示的问题

​ 通过前面的分析,我们知道每次下拉框都是重新从数据库中获取类型数据进行绑定,这里面我们就需要对选中的类型加一个判断,当从数据库中取出的数据是选中的类型时,就给下拉框选项属性selected设置为true。首先后台要传递当前选中的类型名称给视图,代码如下:

//传递当下拉框选择的类型名给视图
this.Data["typeName"] = typeName
  • 前端代码处理

    视图中我们接收控制器传递过来的当前选中类型,然后与数据库中的类型名进行比较,如果相同就设置选中不同就不设置,代码如下:

    <select name="select" id="select" class="sel_opt">
         {{range .articleTypes}}
              {{if compare .Tname $.typeName}}
                    <option selected="true">{{.Tname}}</option>
              {{else}}
                    <option>{{.Tname}}</option>
              {{end}}
         {{end}}
    </select>
    

    需要注意的是,如果是在循环中获取控制器传递过来的数据,不能直接用. ,要用$.

    然后刷新页面,我们发现问题能够解决了。

2.Session和Cookie

接着我们再来重新看一下我们的项目还有哪些功能没有实现呢?1.我们打开登陆界面发现,登陆界面有一个记录用户名选项,这个功能我们还没有实现。2.我们实现功能其实都是类似一个新闻类APP的后台,这种页面肯定需要做登陆判断,所以我们还需要做登陆判断。3.有登陆判断,就要实现退出登陆功能。4.打开文章详情页,我们发现最近浏览这一行内容没有实现,这里我们也需要实现一下。

在实现这四个功能之前老师要给你们介绍一个新的知识点,Session和Cookie,我们这四个功能都需要用到这四个功能。那么Session和Cookie又是什么呢?Session和Cookie作用在有些时候是一样的,他们都是用来保存用户数据的。但是他们的某些特性又非常的不同,导致他们的应用场景不同。接下来我们来详细的了解一下这两种技术。

Cookie

用来一定时间的保存用户数据,数据存储在客户端(网站的客户端就是浏览器),启用的时候能设置Cookie的有效时间,当时间截至的时候,Cookie失效.

Beego中对Cookie的存取删

Beego把数据存储到Cookie中代码如下:

this.Ctx.SetCookie(key,value,time)//第一个参数是Cookie的key值,第二个参数是Cookie的value值,第三个参数是设置的Cookie的有效时间。

取Cookie的代码如下:

this.Ctx.GetCookie(key)//参数是Cookie的key值,返回值是对应的value值。当没有对应的Cookie或者Cookie已失效,返回空字符串

删除Cookie的代码如下:

this.Ctx.SetCookie(key,value,0)//第一个参数是Cookie的key值,第二个参数任意值,第三个参数把Cookie的值设置为小于0,就马上失效。

Session

也是用来一定时间的保存用户数据,不过数据存储在服务器,Beego启用Sesssion的时候需要在配置文件中开启Session功能。在Beego使用中,一般不设置Session的时间,当浏览器关闭的时候,Session失效。

**Beego中对Session的存取 **

如果想要在项目中使用Session功能,需要先在配置文件中设置Sessionon=true

Beego存储Session的代码:

this.SetSession(key,value)//两个参数,一个是Session的key,第二个是Session的Value

获取Session的代码如下:

this.GetSession(key)//参数是Session的key值,返回值是Session对应的value值,类型是interface{}

删除Session的代码如下:

this.DelSession(key)//参数是Session的key值

我们通过表格来分析他们的不同

不同点 Cookie Session
数据存储位置 客户端 服务器
数据安全性(相比较而言)
生命周期 随着设置时间的结束,生命周期结束 当浏览器关闭的时候,生命周期结束
适用场景 对安全性要求不高的,需要存储时间较长的数据 安全性要求搞,不需要长期存储的数据

简单了解了这两个知识点之后,我们来看一下,如何实现我们项目剩余的四个功能。

2.1记住用户名

在登录页如果我们勾选了记住用户名的选项框,在下次登陆的时候,用户名那一栏就默认显示上次存储的用户名。并且记住用户名默认勾选,如果我们取消勾选记住用户名,下次访问登陆页面的时候就不显示用户名,记住用户名也不默认勾选。一般情况下,记住用户名都能记住很久,对安全系数要求也不是很高,这里我们用Cookie来实现这个功能。

我们观察视图代码发现,当登陆的时候,form表单提交了记住用户名单选框的数据,用beego.Info()打印一下获取到的数据,发现当记住用户名选中的时候我们在后台会会获取到字符串"on",没有选中的时候获取不到,根据这个现象,我们可以用来判断是否邓丽,当登陆的时候,我们可以用Cookie存储用户名,在没有选中的时候删除Cookie。代码如下:

//处理注册用户名数据
//获取数据
remember := this.GetString("remember")
beego.Info(remember)
if remember == "on"{
    beego.Info(remember)
    this.Ctx.SetCookie("userName",userName,1000)
}else {
    this.Ctx.SetCookie("userName",userName,-1)
}

在展示登陆页面的时候,我们需要去获取Cookie的值,然后判断,如果获取到了Cookie的值,就在用户名里面显示,并且把记住用户名设置为选中状态,如果没有获取到Cookie的值就把用户名设置为空,记住用户名设置为非选中状态,代码如下:

//获取数据
userName := this.Ctx.GetCookie("userName")
//对数据进行判断,然后设置数据传递给视图
if userName != ""{
    this.Data["userName"] = userName
    this.Data["checked"] = "checked"
}else{
    this.Data["userName"] = ""
    this.Data["checked"] = ""
}

视图中接收数据:

<form  class="login_form"  name = "login" action="/login" method="post">
    <h1 class="login_title">用户登录</h1>
    <input type="text"  class="input_txt" name = "userName" value="{{.userName}}">
    <input type="password" name = "passwd"  class="input_txt">
    <div class="remember"><input type="checkbox" name="remember" {{.checked}} ><label>记住用户名</label></div>
    <input type="submit" value="登 录" class="input_sub">
</form>

注意,当checkbox添加一个checked属性时,checkbox就为选中状态

2.2登陆判断

因为我们操作的都是后台管理界面,所以我们需要做登陆判断。我们这里面用Session来实现这个功能。

在使用Session之前记得要在配置文件中设置sessionon=true

当登陆成功之后就设置Session,代码如下:

//设置session
this.SetSession("userName",userName)

后台几个展示页面的函数都需要获取session,然后判断,代码如下:

//获取session,并判断是否为空,如果为空跳转到登录页面
userName := this.GetSession("userName")
if userName == nil{
    this.Redirect("/ShowLogin",302)
    return
}

2.3退出登陆

退出登录其实就是删除登陆session,然后跳转回登陆界面。

  • 在文章列表页有个退出登陆,我们需要给他加一个href,这里我们规定退出登陆的请求路径为/logout

    <a href="/logout" class="logout fr">退 出</a>
    
  • 接着我们在路由中指定请求对应的控制器和方法

    beego.Router("/logout",&controllers.ArticleController{},"get:Logout")
    
  • 然后我们实现一个Logout函数,业务逻辑很简单,我们直接看代码

    //退出登录
    func(this*ArticleController)Logout(){
      //删除session
      this.DelSession("userName")
      //跳转
      this.Redirect("/login",302)
    }
    

2.4最近浏览

最近浏览也就是在我们浏览文章的时候给文章添加上用户信息,然后在再查询这些信息,在页面中显示。

  • 添加浏览信息

    我们这里是给文章表添加浏览的用户信息。代码如下:

    //获取ORM对象
    o := orm.NewOrm()
    //获取插入数据的对象
    var article models.Article
    article.Id = id
    o.Read(&article)
    //获取多对多操作对象,用的是函数QueryM2M(),第一个参数是要插入数据的对象,第二个参数是要插入数据的字段名,返回值是多对多操作对象
    m2m := o.QueryM2M(&article,"Users")
    //获取要插入的对象
    user := models.User{Name:userName.(string)}
    o.Read(&user,"Name")
    //多对多插入
    m2m.Add(user)
    
  • 显示浏览信息

    有两种显示多对多信息的方法

    第一种,直接加载多对多关系,用的函数是LoadRelated(),第一个参数是查询对象,第二个参数是多对多关系字段,代码如下:

    num,err := o.LoadRelated(&article,"Users")
    

    这时候我们在前端就可以循环显示最近浏览的用户信息,这里我们用第二种视图循环语法:

    <label>最近浏览:</label>
    <p class="detail">{{range .article.Users}}{{.Name}} | {{end}}</p>
    

    这时候我们多点几次查看详情会发现个问题,我们添加关系的时候是浏览一次就添加一次,那么我们显示的时候就会重复显示相同用户的用户名,效果如下:

1539152548601.png

但是我们一般浏览网页的时候,一个用户浏览过了只显示一次该用户信息即可,所以这里面我们需要去重,还记得我们前面介绍的高级查询去重的方法吗?Distinct()去重,但是这个函数必须要是queryseter对象才能操作,所以我们第一种多对多查询方法就不行了。这里我们用第二种多对多查询。代码如下:

var users []models.User
o.QueryTable("User").Filter("Articles__Article__Id",article.Id).Distinct().All(&users)

注意:我们这里插入的是想article中插入user,但是查询的是从user中去获取。

3.项目优化

3.1路由过滤器

我们在项目实现的时候,只给文章列表页和详情页添加了登陆判断,我们思考一下,我们这个案例其实是整个的后台管理,所以每个页面都需要添加登陆判断,那我们就需要每个地方都要添加登陆判断,重复代码很多。这里给大家介绍一个新的技术,路由过滤器,在路由层面添加一个过滤,实现登陆判断。那我们来看一下什么是路由过滤器。

作用:可以根据指定的匹配规则特定的项目运行阶段执行自定义函数,函数一般放在beego.router()之前 。

那我们看一下路由过滤器函数的格式:

beego.InsertFilter(pattern string, position int, filter FilterFunc)

第一个参数是路由匹配规则,支持正则

第二个参数是指定项目运行阶段,在beego项目运行过程中,框架帮我们分了五个阶段,分别是:

a) BeforeStatic 静态地址之前

b) BeforeRouter 寻找路由之前

c) BeforeExec 找到路由之后,开始执行相应的 Controller 之前

d) AfterExec 执行完 Controller 逻辑之后执行的过滤器

e) FinishRouter 执行完逻辑之后执行的过滤器

具体对应是如下这种图的时间点:

1539154395379.png

第三个参数,就是指定过滤器函数。

路由过滤器一般放在beego.Router()之前。

那么我们接着来看一下过滤器函数的格式:

type FilterFunc func(*context.Context)

参数必须是context.Context

示例代码:

var BeforeExecFunc = func(ctx * context.Context) {
    userName:=ctx.Input.Session("userName")
    if userName == nil{
        ctx.Redirect(302,"/login")
    }
}
beego.InsertFilter("/index",beego.BeforeExec,BeforeExecFunc)

其实就是在router.go文件中定义函数,然后在 init函数最上面注册这个定义的函数,
而且注意导包是beego/context而不是直接context包

3.2视图布局

实现了过滤器函数之后,我们再来看我们整个项目,页面显示如下:

1539155058223.png

你会发现有些内容在每个页面中都有显示,那我们能不能避免这些重复操作呢?这里给大家介绍一个新的知识点,视图布局:

作用:通过设置模板页面,其他页面可以直接调用模板,避免再次处理重复代码。

视图布局本质上就是两个html界面的拼接,比如我们现在有一个包含重复部分的html界面layout.html,还有一个只包含添加文章业务的界面,我们可以根据如下去实现两个页面的拼接 。

操作如下:

控制器代码如下:

this.Layout = "layout.html"
this.TplName = "add.html"

layout.html中的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>后台管理页面</title>
    <link rel="stylesheet" type="text/css" href="/static/css/reset.css">
    <link rel="stylesheet" type="text/css" href="/static/css/main.css">
    <script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>

</head>
<body>

<div class="header">
    <a href="#" class="logo fl"><img src="/static/img/logo.png" alt="logo"></a>
    <a href="/logout" class="logout fr">退 出</a>
</div>

<div class="side_bar">
    <div class="user_info">
        <img src="/static/img/person.png" alt="张大山">
        <p>欢迎你 <em>李雷</em></p>
    </div>

    <div class="menu_con">
        <div class="first_menu active"><a href="javascript:;" class="icon02">文章管理</a></div>
        <ul class="sub_menu show">
            <li><a href="#" class="icon031">文章列表</a></li>
            <li><a href="/addArticle" class="icon032">添加文章</a></li>
            <li><a href="#" class="icon034">添加分类</a></li>
        </ul>
    </div>
</div>

{{.LayoutContent}}
    
    
</body>
</html>

注意这里面的 {{.LayoutContent}},这个标签的地方就是用来存放add.html的地方。

add.html中就可以删除掉相同的代码,代码如下:

    <div class="main_body" id="main_body">
        <div class="breadcrub">
            当前位置:文章管理>添加文章
        </div>
        <div class="pannel">
            <form method="post" action="/addArticle" enctype="multipart/form-data">
            <h3 class="review_title">添加文章</h3>
            <div class="form_group">
                <label>文章标题:</label>
                <input type="text" class="input_txt2" name="articleName" >
            </div>
            <div class="form_group">
                <label>文章类型:</label>
                <select class="sel_opt" name="select">
                    {{range .articleTypes}}
                        <option>{{.Tname}}</option>
                    {{end}}
                </select>
            </div>
            <div class="form_group">
                <label>文章内容:</label>
                <textarea class="input_multxt" name="content"></textarea>
            </div>
            <div class="form_group">
                <label>上传图片:</label>
                <input type="file" class="input_file"  name="uploadname">
            </div>
            <div class="form_group indent_group line_top">
                <input type="submit" value="添 加" class="confirm">
                <span>{{.errmsg}}</span>
            </div>
        </form>
        </div>
</div>

在浏览器输入网址,这时候你可能会发现问题,我们的title标签 这种小部分没办法改变。这里我们可以通过this.Data给layout传值。

  • js代码传递

    细心的同学还会发现,我们在某些页面需要加js代码,这个 内容怎么传递到页面当中呢,这里再给大家介绍一个功能LayoutSection。

    **LayoutSection **作用:this.Layout指定了模板文件,可以实现两个页面的拼接,那有时候某些js或者是css样式,该如何传递呢?我们可以用LayoutSection传递。

    **LayoutSection **:用法:

    控制器代码:

    this.Layout = " layout.html"
    this.LayoutSections = make(map[string]string)
    this.LayoutSections["Scripts"] = "scripts.html"
    

    在layout.html中添加下面相应内容:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>后台管理页面</title>
        <link rel="stylesheet" type="text/css" href="/static/css/reset.css">
        <link rel="stylesheet" type="text/css" href="/static/css/main.css">
        <script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>
    
    </head>
    <body>
    
    <div class="header">
        <a href="#" class="logo fl"><img src="/static/img/logo.png" alt="logo"></a>
        <a href="/logout" class="logout fr">退 出</a>
    </div>
    
    <div class="side_bar">
        <div class="user_info">
            <img src="/static/img/person.png" alt="张大山">
            <p>欢迎你 <em>李雷</em></p>
        </div>
    
        <div class="menu_con">
            <div class="first_menu active"><a href="javascript:;" class="icon02">文章管理</a></div>
            <ul class="sub_menu show">
                <li><a href="#" class="icon031">文章列表</a></li>
                <li><a href="/addArticle" class="icon032">添加文章</a></li>
                <li><a href="#" class="icon034">添加分类</a></li>
            </ul>
        </div>
    </div>
    
    {{.LayoutContent}}
        
        
    </body>
    </html>
    {{.Scripts}}
    

3.3补充

我们回顾一下,看看我们的项目还有哪点没有实现呢?类型的删除是不是还没有实现,可能有的学生会说,老师这个删除和文章的删除一样,直接删除不久行了嘛!这里要特别提醒:**类型是与多表操作有关的,删除效果和单表的文章不一样 **

那我们来看一下类型的删除:

同样还是四步骤:**请求->路由->控制器->视图 **

  • 请求

    删除类型是在添加类型页面中实现的,在这个页面中有一个删除的<a>标签,如下图所示:

1539156697631.png

那我们给这个<a>标签加上请求路径,同样的,我们需要给请求路径上加上类型Id。代码如下:

<a href="/deleteType?id={{.Id}}" class="edit">删除</a>
  • 路由

    添加相应路由,指定控制器和方法

    beego.Router("/deleteType",&controllers.ArticleController{},"get:DeleteType")
    
  • 控制器

    有了方法名,就实现相关代码:

    //删除类型
    func(this*ArticleController)DeleteType(){
      //获取数据
      id,err:=this.GetInt("id")
      //校验数据
      if err != nil{
          beego.Info(err)
          return
      }
      //处理数据
      var articleType models.ArticleType
      articleType.Id = id
      o := orm.NewOrm()
      o.Delete(&articleType)
      //返回视图
      this.Redirect("/addArticleType",302)
    }
    
  • 视图

    删除之后,我们返回页面发现,类型确实删除了。但是需要注意的是,我们类型绑定的还有相关的文章,这时候我们回到文章列表页,发现,删除类型,把该类型有关的文章也删除了,这是因为,beego默认执行的是级联删除,那这个级联删除能不能设置呢?在beego中级联删除的设置,是在建表的时候添加的设置如下:

    设置对应的 rel 关系删除时,如何处理关系字段。

    cascade        级联删除(默认值)
    set_null       设置为 NULL,需要设置 null = true
    set_default    设置为默认值,需要设置 default 值
    do_nothing     什么也不做,忽略
    

    示例:

    //文章结构体
    type Article struct {
      Id int `orm:"pk;auto"`
      ArtiName string `orm:"size(20)"`
      Atime time.Time `orm:"auto_now"`
      Acount int `orm:"default(0);null"`
      Acontent string `orm:"size(500)"`
      Aimg string  `orm:"size(100)"`
    
      ArticleType*ArticleType `orm:"rel(fk);null;on_delete(set_null)"`
      Users []*User `orm:"reverse(many)"`
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容