Beego框架安装及学习(附项目案例)

1.规划

2.Beego框架快速入门

2.1beego框架了解

Beego框架是go语言开发的web框架。

那什么是框架呢?就是别人写好的代码,我们可以直接使用!这个代码是专门针对某一个开发方向定制的,例如:我们要做一个网站,利用 beego 框架就能非常快的完成网站的开发,如果没有框架,每一个细节都需要我们处理,开发速度会大大降低。

go语言的web框架:beego,gin,echo等等,那为什么我们选择beego呢?

beego对于新手来说更易学习

2.2MVC架构

Beego是MVC架构。MVC 是一种应用非常广泛的体系架构,几乎所有的编程语言都会使用到,而且所有的程序员在工作中都会遇到!用 MVC 的方式开发程序,可以让程序的结构更加合理和清晰。 画图说明

beego具体是如何内嵌MVC呢?我们搭起环境通过代码分析。

2.3环境搭建(Ubuntu下搭建)

2.3.1安装Golang环境

1.配置DNS服务

echo "nameserver 114.114.114.114" >> /etc/resolv.conf(这里需要切换root用户sudo su

2.下载wget和ntpdate工具

apt install ntpdate wget -y

3.date查看一下系统时间

若不是实际的实时时间就同步一下

4.创建golang的工程目录及bin目录

这里把$HOME/work/go作为了golang的project目录

mkdir -p $HOME/work/go $HOME/dev_env && cd $HOME/dev_env

5.下载对应的golang包

  • 官方下载地址:https://go.dev/dl/
  • 也可用命令行下载:wget https://dl.google.com/go/go1.20.5.linux-amd64.tar.gz

核实一下下载的golang包是损坏,确保与下载网页上的hash值一致

sha256sum go1.20.5.linux-amd64.tar.gz

6.下载完golang安装包后,解压安装包

tar -zxvf go1.20.5.linux-amd64.tar.gz

7.配置环境变量

vim ~/.bashrc在文件最后添加并保存

export GOROOT=$HOME/dev_env/go
export GOPATH=$HOME/work/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN

执行source ~/.bashrc以便配置生效

8.检测安装配置是否成功

go version

9.写一个简单的程序测试一下

vi $GOPATH/hello.go

package main

import "fmt"

func main() {
    fmt.Printf("Hello,world!,aust blockchain, it's for beego!\n")
}

进入GOPATH文件夹cd $GOPATH

编译刚刚写的go文件go build hello.go

2.3.2安装Beego环境

1.搭建代理方便下载依赖包

vim ~/.bashrc加入以下两句

export GO111MODULE=on
export GOPROXY="https://goproxy.cn,direct"

执行source ~/.bashrc以便配置生效

2.创建一个独立存放beego源码和bee源码的目录

mkdir -p $HOME/work/beego_dev && cd $HOME/work/beego_dev

3.使用git下载源码

虚拟机没安装git操作的先安装一下sudo apt install git

然后下beego和bee

git clone https://github.com/beego/bee.git
git clone https://github.com/beego/beego.git

然后编译出bee这个构建beego工程的工具,由于下载bee的源码里已经有了go.mod,所以在go build的时候会自动下载一些build的依赖包

cd bee
go build

go build 之后会发现会多出一个bee,生成bee之后,把bee放到环境变量里,这里把bee放到了GOBIN里,将bee放到环境变量里之后,刚才git下载的beego和bee 源码其实就可以删掉了,因为bee新建的api项目,通过go mod tidy就可以进行管理依赖包cp bee $GOBIN

4.结果验证

随便换一个工作目录,执行bee version

5.创建项目

运行bee new 项目名来创建一个项目bee new myapp

6.运行项目

进入项目目录cd myapp

输入go get 项目名同步依赖go get myapp

再输入bee run运行该项目

7.浏览器进入

在虚拟机自带浏览器中输入localhost:8080进入

在本机浏览器中进入

首先获取虚拟机ipifconfig

在本机浏览器中输入http://192.168.232.137:8080/

2.4beego的项目结构分析

进入项目目录输入tree查看项目目录结构

  • conf/:存放应用的配置文件,如数据库连接信息。
  • controllers/:包含处理用户请求的控制器文件。
  • go.mod 和 go.sum:定义项目的依赖关系及其版本。
  • main.go:应用的入口文件,负责初始化并运行应用。
  • models/:用于存放表示应用数据结构的模型文件(本例中为空)。
  • routers/:定义应用的URL路由规则,将路径映射到控制器。
  • static/:存放CSS、JavaScript和图片等静态资源。
  • tests/:编写应用的单元测试文件。
  • views/:包含生成HTML内容的视图模板文件。

2.5Beego快速体验

2.5.1Ubuntu下安装Goland

方法一:

直接在Ubuntu软件商店进行下载安装

方法二:

1.下载压缩包:Download GoLand: A Go IDE with extended support for JavaScript, TypeScript, and databases

2.打开终端,并将压缩包复制到/usr/local下并进行解压

sudo cp goland-2024.3.tar.gz /usr/local/

3.进入/usr/local进行解压

cd /usr/local/

sudo tar -xvf goland-2024.3.tar.gz

4.启动Goland

解压完成后进入goland中的bin目录中

执行./goland.sh进行启动

5.Goland图标添加

转到/usr/share/applications路径cd /usr/share/applications

执行:sudo vim goland.desktop

注:Icon和Exec下改自己的对应路径

[Desktop Entry]
Version=1.0
Type=Application
Name=GoLand
# /usr/local/GoLand-2024.3 goland安装的路径
Icon=/usr/local/GoLand-2024.3/bin/goland.png
Exec=/usr/local/GoLand-2024.3/bin/goland.sh
Comment=The Drive to Develop
Categories=Development;IDE;
Terminal=false
StartupWMClass=jetbrains-goland 

保存完成后,就可以在应用程序下看到GoLand图标了,若没有,可重启查看。

最后,右键图标添加到收藏夹,就可以显示在桌面左侧了

2.5.2项目体验

1.打开controllers目录下的default.go文件进行修改

修改后:

package controllers

import (
	beego "github.com/beego/beego/v2/server/web"
)

type MainController struct {
	beego.Controller
}

func (c *MainController) Get() {
	c.Data["Website"] = "beego.vip"
	c.Data["Email"] = "astaxie@gmail.com"
	c.Data["data"] = "aust blockchain"
	c.TplName = "test.html"
}

2.在views目录下新建test.html文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Title</title>
  </head>
  <body>
    hello {{.data}}
  </body>
</html>

3.运行项目

Goland终端中输入bee run

4.总结

c.Data["Email"] = "astaxie@gmail.com"是给视图传递数据,在视图界面里面需要用{{ }}加上.才能获取到,比如这行代码的意思就是,给视图传递,Key为Email,valueastaxie@gmail.com的数据。在视图中要通过{{.Email}}就能获取到value值。

c.TplName = "index.tpl"的作用是指定视图。这里面需要注意的是,默认指定的界面是tpl结尾,但是打开这个文件分析,发现还是一个html界面。所以我们也可以用html文件当视图文件。

同时得出结论:

  1. 控制器(Controller)的作用
  • 能够给视图传递数据
  • 能够指定视图
  1. 视图(View)的作用
  • view本质就是个html。所以能在浏览器显示
  • 能够接收控制器传递过来的数据

2.6Beego运行流程分析

  • 浏览器发出请求
  • 路由拿到请求,并给相应的请求指定相应的控制器
  • 找到指定的控制器之后,控制器看是否需要查询数据库
  • 如果需要查询数据库就找model取数据
  • 如果不需要数据库,直接找view要视图
  • 控制器拿到视图页面之后,把页面返回给浏览器

根据文字流程分析代码流程

  • 从项目的入口main.go开始
  • 找到router.go文件的Init函数
  • 找到路由指定的控制器文件default.go的Get方法
  • 然后找到指定视图的语法,整个项目就串起来了

2.7Post案例实现

刚才分析了beego项目的整个运行流程,最终是如何调到Get方法的呢?beego通过内部语法给不同的http请求指定了不同的方法,因为我们是从浏览器地址栏发送的请求,属于get请求,所以调用的是Get方法。接下来实现一个post请求。

2.7.1前端修改

前端代码如下:

修改刚才创建的新的视图,为了能够发送post请求,在视图中添加一个能发送post请求的控件form

<form method="post" action="/">
    <input type="submit" value="提交">
</form>

全部代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>hello {{.data}}</h1>

<form method="post" action="/">
    <input type="submit" value="提交">
</form>

</body>
</html>

2.7.2后台代码修改

后台代码

设置post请求要传递的数据和要显示的视图页面

func (c *MainController) Post() {
	c.Data["data"] = "AUST BLOCKCHAIN"
	c.TplName = "test.html"
}

全部代码

package controllers

import (
    beego "github.com/beego/beego/v2/server/web"
)

type MainController struct {
    beego.Controller
}

func (c *MainController) Get() {
    c.Data["data"] = "aust blockchain"
    c.TplName = "test.html"
}

func (c *MainController) Post() {
    c.Data["data"] = "AUST BLOCKCHAIN"
    c.TplName = "test.html"
}

验证

bee run进入浏览器localhost:8080查看

点击提交按钮后

2.8Beego中路由的快速体验

2.8.1路由的简单设置

路由的作用:根据不同的请求指定不同的控制器

路由函数:beego.Router("/path",&controller.MainController{})

函数参数:

先分析一下Url地址由哪几部分组成? 同一资源定位符

http://192.168.232.136:8080/index

http://地址:端口/资源路径

第一个参数:资源路径,也就是/后面的内容

第二个参数:需要指定的控制器指针

beego.Router("/", &controllers.MainController{})
beego.Router("/index", &controllers.IndexController{})
beego.Router("/login", &controllers.LoginController{})

2.8.2高级路由设置

一般在开发过程中,我们基本不使用beego提供的默认请求访问方法,都是自定义相应的方法。那我们来看一下如何来自定义请求方法。

自定义请求方法需要用到Router的第三个参数。这个参数是用来给不同的请求指定不同的方法。具体有如下几种情况。

一个请求访问一个方法(也是最常用的),请求和方法之间用 : 隔开,不同的请求用 ; 隔开:

  • beego.Router("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")

可以多个请求,访问一个方法 ,请求之间用,隔开,请求与方法之间用:隔开:

  • beego.Router("/api",&RestController{},"get,post:ApiFunc")

所有的请求访问同一个方法,用*号代表所有的请求,和方法之间用:隔开:

  • beego.Router("/api/list",&RestController{},"*:ListFood")

如果同时存在 * 和对应的 HTTP请求,那么优先执行 HTTP请求所对应的方法,例如同时注册了如下所示的路由:

  • beego.Router("/simple",&SimpleController{},"*:AllFunc;post:PostFunc")

那么当遇到Post请求的时候,执行PostFunc而不是AllFunc。

如果用了自定义方法之后,默认请求将不能访问。

示例:在router.go文件中修改

代码:

package routers

import (
    beego "github.com/beego/beego/v2/server/web"
    "myapp/controllers"
)

func init() {
    beego.Router("/", &controllers.MainController{})
    //给请求指定自定义方法,一个请求指定一个方法
    beego.Router("login",&controllers.LoginController{},"get:ShowLogin;post:PostFunc")
    //给多个请求制定一个方法
    beego.Router("index",&controllers.IndexController{},"get,post:HandleFunc")
    //给所有请求制定一个方法
    beego.Router("index",&controllers.IndexController{},"*:HandleFunc")
    //当两种指定方法冲突的时候
    beego.Router("index",&controllers.IndexController{},"*:HandleFunc;post:PostFunc")
}

2.9Go操作MySQL数据库

2.9.1Ubuntu下安装mysql

1.更新一下列表

sudo apt-get update

2.安装MySQL

sudo apt-get install mysql-server

3.启动和关闭mysql的命令如下

启动::sudo service mysql start

重启:sudo service mysql restart

关闭::sudo service mysql stop

4.修改mysql的连接方式和数据库密码

无密码登录数据库: sudo mysql -u root -p

不用输入任何密码直接回车,然后输一下命令

use mysql
  
#开启远程连接
update user set host='%' where user='root';
#修改了之后刷新以下权限
flush privileges;

#修改数据库密码.
ALTER USER 'root'@'%' identified with mysql_native_password BY 'wzj123456';
#修改了之后刷新以下权限
flush privileges;

#退出数据库
quit

5.再次进入数据库

mysql -u root -p然后输入刚刚设置的密码就可以正常进入mysql

6.创建测试数据库

create database test;

2.9.2Go操作Mysql数据库

1.安装go操作MySQL的驱动

go get github.com/go-sql-driver/mysql

2.创建文件

在models目录下创建model.go文件

同时在main.go文件中导包

_ "myapp/models"

2.导入数据库驱动

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
)

3.连接数据库

用sql.Open()方法,open()方法的第一个参数是驱动名称,第二个参数是用户名:密码@tcp(ip:port)/数据库名称?编码方式,返回值是连接对象和错误信息,例如:

// 连接数据库
dsn := "root:wzj123456@tcp(127.0.0.1:3306)/test"
conn, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatalf("连接数据库失败: %v", err)
}
defer conn.Close()

4.创建表

创建表的方法也是Exec(),参数是SQL语句,返回值是结果集和错误信息:

_, err = conn.Exec("INSERT INTO user(userName, passwd) VALUES(?, ?)", "aust", "blockchain")
if err != nil {
    log.Fatalf("插入数据失败: %v", err)
}
log.Println("数据插入成功")

5.增删改操作

执行增删改操作语句的是Exec(),参数是SQL语句,返回值是结果集和错误信息,通过对结果集的判断,得到执行结果的信息。以插入数据为例代码如下:

	// 插入数据到user表中
	_, err = conn.Exec("INSERT INTO user(userName, passwd) VALUES(?, ?)", "aust", "blockchain")
	if err != nil {
		// 如果插入数据失败,则记录错误信息并终止程序
		log.Fatalf("插入数据失败: %v", err)
	}
	// 记录数据插入成功的日志
	log.Println("数据插入成功")

6.查询操作

用的函数是Query(),参数是SQL语句,返回值是查询结果集和错误信息,然后循环结果集取出其中的数据。代码如下:

// 查询数据
rows, err := conn.Query("SELECT userName FROM user")
if err != nil {
    log.Fatalf("查询数据失败: %v", err)
}
defer rows.Close()

var userName string
for rows.Next() {
    if err := rows.Scan(&userName); err != nil {
        log.Fatalf("扫描数据失败: %v", err)
    }
    log.Println("用户名:", userName)
}

if err := rows.Err(); err != nil {
    log.Fatalf("迭代行时出错: %v", err)
}

全部代码

package models

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动,但不直接使用它
	"log"
)

func init() {
	// 数据库连接字符串,包含用户名、密码、地址、端口和数据库名
	dsn := "root:wzj123456@tcp(127.0.0.1:3306)/test"

	// 使用sql.Open尝试打开数据库连接,这里并不会立即进行连接,只是初始化一个连接对象
	conn, err := sql.Open("mysql", dsn)
	if err != nil {
		// 如果初始化连接对象失败,则记录错误信息并终止程序
		log.Fatalf("连接数据库失败: %v", err)
	}

	// 确保在main函数结束时关闭数据库连接
	defer conn.Close()

	// 使用Ping方法检查数据库连接是否有效
	err = conn.Ping()
	if err != nil {
		// 如果连接无效,则记录错误信息并终止程序
		log.Fatalf("数据库连接无效: %v", err)
	}

	// 执行SQL语句创建表,如果表已存在则不会重新创建(使用IF NOT EXISTS)
	_, err = conn.Exec("CREATE TABLE IF NOT EXISTS user(userName VARCHAR(40), passwd VARCHAR(40))")
	if err != nil {
		// 如果创建表失败,则记录错误信息并终止程序
		log.Fatalf("创建表失败: %v", err)
	}
	// 记录表创建成功的日志
	log.Println("表创建成功")

	// 插入数据到user表中
	_, err = conn.Exec("INSERT INTO user(userName, passwd) VALUES(?, ?)", "aust", "blockchain")
	if err != nil {
		// 如果插入数据失败,则记录错误信息并终止程序
		log.Fatalf("插入数据失败: %v", err)
	}
	// 记录数据插入成功的日志
	log.Println("数据插入成功")

	// 查询user表中的所有用户名
	rows, err := conn.Query("SELECT userName FROM user")
	if err != nil {
		// 如果查询失败,则记录错误信息并终止程序
		log.Fatalf("查询数据失败: %v", err)
	}

	// 确保在rows使用完毕后关闭它,释放资源
	defer rows.Close()

	// 迭代查询结果
	var userName string
	for rows.Next() {
		// 扫描当前行的数据到userName变量中
		if err := rows.Scan(&userName); err != nil {
			// 如果扫描失败,则记录错误信息并终止程序
			log.Fatalf("扫描数据失败: %v", err)
		}
		// 记录查询到的用户名
		log.Println("用户名:", userName)
	}

	// 检查在迭代过程中是否发生了错误
	if err := rows.Err(); err != nil {
		// 如果迭代过程中发生错误,则记录错误信息并终止程序
		log.Fatalf("迭代行时出错: %v", err)
	}
}

2.10 ORM框架

Beego中内嵌了ORM框架,用来操作数据库。那么ORM框架是什么呢?ORM框架是Object-RelationShip-Mapping的缩写,中文叫对象关系映射,他们之间的关系,我们用图来表示:

2.10.1 ORM初始化

1.首先导包

import (
	"github.com/beego/beego/v2/adapter/orm"
	_ "github.com/go-sql-driver/mysql" 
)

同时在main.go文件中导包

_ "myapp/models"

2.然后要定义一个结构体

type User struct{
    Id int
    Name string
    PassWord string
}

3.然后向数据库中注册表,这一步又分为三步:

a)连接数据库

用RegisterDataBase()函数,第一个参数为数据库别名,也可以理解为数据库的key值,项目中必须有且只能有一个别名为default的连接,第二个参数是数据库驱动,这里我们用的是MySQL数据库,所以以MySQL驱动为例,第三个参数是连接字符串,和传统操作数据库连接字符串一样,格式为:用户名:密码@tcp(ip:port)/数据库名称?编码方式,代码如下:

orm.RegisterDataBase("default","mysql","root:123456@tcp(127.0.0.1:3306)/class1?charset=utf8")

注意:ORM只能操作表,不能操作数据库,所以我们连接的数据库要提前在MySQL终端创建好。

b)注册数据库表

用orm.RegisterModel()函数,参数是结构体对象,如果有多个表,可以用 ,隔开,多new几个对象:

orm.RegisterModel(new(User))

c)生成表

用orm.RunSyncdb()函数,这个函数有三个参数,

第一个参数是数据库的别名和连接数据库的第一个参数相对应。

第二个参数是是否强制更新,一般我们写的都是false,如果写true的话,每次项目编译一次数据库就会被清空一次,fasle的话会在数据库发生重大改变(比如添加字段)的时候更新数据库。

第三个参数是用来说,生成表过程是否可见,如果我们写成课件,那么生成表的时候执行的SQL语句就会在终端看到。反之看不见。代码如下:

orm.RunSyncdb("default",false,true)

完整代码如下:

package models

import (
	"github.com/beego/beego/v2/adapter/orm"
	_ "github.com/go-sql-driver/mysql"
)

// 定义 Student 结构体,表示学生信息
type Student struct {
	Id       int    // 学生ID
	Name     string // 学生姓名
	PassWord string // 学生密码(注意:在实际应用中,密码应该进行加密存储)
}

// init 函数是 Go 语言的特殊函数,它会在包被导入时自动执行
// 这里用于初始化 ORM 和数据库连接
func init() {
	// 注册数据库连接
	// "default" 是连接名称,可以在后续操作中引用
	// "mysql" 是数据库类型
	// "root:wzj123456@tcp(127.0.0.1:3306)/test" 是数据库连接字符串,包括用户名、密码、地址和数据库名
	orm.RegisterDataBase("default", "mysql", "root:wzj123456@tcp(127.0.0.1:3306)/test")
	
	// 注册模型,以便 ORM 能够识别并操作 Student 结构体对应的数据库表
	orm.RegisterModel(new(Student))
	
	// 自动同步数据库表结构
	// 第一个参数 "default" 是之前注册的数据库连接名称
	// 第二个参数 false 表示不会删除已存在的表并重新创建(在生产环境中通常设置为 false)
	// 第三个参数 true 表示如果表不存在,则自动创建(在开发环境中通常设置为 true)
	// 注意:在生产环境中,自动创建表可能会导致数据丢失,因此应该谨慎使用
	orm.RunSyncdb("default", false, true)
}

2.10.2 简单的ORM增删改查操作

这里我们在default.go文件中进行操作

插入操作

func (c *MainController) Get() {
	//获取ORM对象
	o := orm.NewOrm()
	//插入操作
    //定义一个要插入数据库的结构体对象
	var stu models.Student
    //给定义的对象赋值
    //这里不用给Id赋值,因为建表的时候我们没有指定主键,ORM默认会以变量名为Id,类型为int的字段当主键
	stu.Name = "AUST"
	stu.PassWord = "aust@edu"
    //执行插入操作,o.Insert()插入,参数是结构体对象,返回值是插入的id和错误信息。
	id, err := o.Insert(&stu)
	if err != nil {
		log.Fatalf("插入数据失败: %v", err)
	}
	log.Printf("数据插入成功, ID: %d\n", id)

	c.Data["data"] = "aust blockchain"
	c.TplName = "test.html"
}

查询

	//查询操作
    //先获得一个ORM对象
    o := orm.NewOrm()
    //定义一个要获取数据的结构体对象
	var stu models.Student
    //给结构体对象赋值,相当于给查询条件赋值
	stu.Id = 1
    //查询,用o.Read(),第一个参数是对象地址,第二个参数是指定查询字段,返回值只有错误信息。
    //如果查询字段是查询对象的主键的话,可以不用指定查询字段
	err := o.Read(&stu, "Id")
	if err != nil {
		log.Fatalf("查询失败: %v", err)
	}
	//返回数据
	log.Printf("students: %v", stu)

	c.Data["data"] = "aust blockchain"
	c.TplName = "test.html"

如果查询字段是查询对象的主键的话,可以不用指定查询字段

更新

    //更新操作
    //先获得ORM对象
    o := orm.NewOrm()
    //定义一个要更新的结构体对象
	var stu models.Student
    //给结构体对象赋值,相当于给查询条件赋值
	stu.Id = 1
    //查询要更新的对象是否存在
	err := o.Read(&stu, "Id")
	if err != nil {
		log.Fatalf("查询失败: %v", err)
	}
    //如果查找到了要更新的对象,就给这个对象赋新值
	stu.Name = "handsome"
    //执行更新操作,用o.Update()函数,参数是结构体对象指针,返回值是更新的条目数和错误信息
	num, err := o.Update(&stu)
	if err != nil {
		log.Fatalf("更新失败: %v", err)
	}
	log.Printf("更新数据成功, 影响行数: %d\n", num)

删除

	//删除操作
    //获取ORM对象,获取要删除的对象
	var stu models.Student
    //给删除对象赋值,删除的对象主键必须有值,如果主键没值,就查询一下。我们这里直接给主键赋值
	stu.Id = 1
    //执行删除操作,用的方法是o.Delete(),参数是删除的结构体对象,返回值是删除的条目数和错误信息
	num, err := o.Delete(&stu)
	if err != nil {
		log.Fatalf("删除失败: %v", err)
	}
	log.Printf("删除数据成功, 影响行数: %d\n", num)

3.注册和登陆案例

在开始具体的业务之前我们要做数据库设计,在正式开发中这一步是非常重要,也比较复杂的,先实现登陆和注册,就简单有个用户名和密码即可,models.go内容如下:

package models

import (
    "github.com/beego/beego/v2/adapter/orm"
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
    Id       int
    Name     string
    PassWord string
}

func init() {
    //orm操作数据库
    orm.RegisterDataBase("default", "mysql", "root:wzj123456@tcp(127.0.0.1:3306)/test")
    orm.RegisterModel(new(User))
    orm.RunSyncdb("default", false, true)
}

3.1注册

确定注册请求路径,修改路由文件

我们这里以/register作为注册的请求路径。所以这里需要修改router.go文件的内容。

beego.Router("/register", &controllers.UserController{},"get:ShowRegister;post:HandleRegister")

根据上面路由的指定,需要添加注册控制器

在controllers文件夹下创建一个user.go,然后在这个文件里面定义一个结构体UserController当控制器。

type UserController struct{
    beego.Controller
}

注意这里面添加的beego.Controller,是为了继承自beego自带的控制器。

添加显示注册页面函数

添加函数的时候需要注意,这个函数必须是UserController的函数才可以,不然在路由里面调用不到。那如何把函数设置成UserController的成员函数呢?是在函数名前面加上括号,然后放上UserController的指针。这里先指定注册的视图。代码如下:

func (this*UserController)ShowRegister(){
    this.TplName = "register.html"
}

注意:这里如果函数名首字母小写,路由同意找不到函数,所以函数名首字母必须大写

添加视图页面

在views文件夹下面创建一个名字为register.html的文件。

form标签里面需要添加两个属性,一个是action,一个是method,action其实就是请求路径,这里处理的还是注册业务,所以我们还用register请求,action = "/register",因为是上传数据,所以我们把method设置为post,即method="post",代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form method="post" action="/register">
    用户名:<input type="text" name="userName"></br>
    密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"></br>
    <input type="submit" value="注册"></br>
    //接收错误提示显示
    {{.errmsg}}
</form>
</body>
</html>

让项目运行起来,然后在浏览器里面输入相应的地址就能看见注册页面了。

注册业务处理

首先在user.go中添加这个函数:

func (this*UserController)HandleRegister(){
}

接着开始处理注册业务

首先要获取数据。这里先介绍一类方法,这类方法将会在项目一中高频率的出现

this.GetString():获取字符串类型值
this.GetInt():获取整型值
this.GetFloat:获取浮点型值
...
this.GetFile():获取上传的文件
作用:接收前端传递过来的数据,不管是get请求还是post请求,都能接收。
参数:  是传递数据的key值,一般情况下是form表单中<input>标签的name属性值
返回值:根据返回类型不同,返回值也不一样,最常用的GetString()只有一个返回值,如果没有取到值就返回空字符串,其他几个函数会返回一个错误类型。获取的值一般是<input>标签里面的value属性值。

获取注册的用户名和密码

userName := this.GetString("userName")
passwd := this.GetString("passwd")

对数据进行校验

if userName == "" || pwd == "" {
    //把错误提示传递给视图
    this.Data["errmsg"] = "用户名或密码为空,请重新注册"
    log.Printf("用户名或密码为空,请重新注册")
    this.TplName = "register.html"
    return
}

把数据插入数据库,如果数据校验没有问题,那我们就需要把数据插入到数据库中。代码如下:

//获取orm对象
o := orm.NewOrm()
var user models.User
user.Name = userName
user.PassWord = pwd
// 判断用户名是否存在
existingUser := models.User{Name: userName}
err := o.Read(&existingUser, "Name")
if err == nil {
    // 用户名已存在,注册失败
    this.Data["errmsg"] = "用户名已存在,请重新注册"
    log.Printf("用户名已存在,请重新注册")
    this.TplName = "register.html"
    return
}
// 用户名不存在,插入新用户
o.Insert(&user)
log.Printf("注册成功")

完整后台代码如下

package controllers

import (
    "github.com/beego/beego/v2/adapter/orm"
    beego "github.com/beego/beego/v2/server/web"
    "log"
    "myapp/models"
)

type UserController struct {
    beego.Controller
}

// 显示注册页面
func (this *UserController) ShowRegister() {
    this.TplName = "register.html"
}

// 处理注册数据
func (this *UserController) HandlePost() {
    //1.获取数据
    userName := this.GetString("userName")
    pwd := this.GetString("password")
    //2.校验数据
    if userName == "" || pwd == "" {
        this.Data["errmsg"] = "用户名或密码为空,请重新注册"
        log.Printf("用户名或密码为空,请重新注册")
        this.TplName = "register.html"
        return
    }
    //3.操作数据
    //获取orm对象
    o := orm.NewOrm()
    var user models.User
    user.Name = userName
    user.PassWord = pwd
    // 判断用户名是否存在
    existingUser := models.User{Name: userName}
    err := o.Read(&existingUser, "Name")
    if err == nil {
        // 用户名已存在,注册失败
        this.Data["errmsg"] = "用户名已存在,请重新注册"
        log.Printf("用户名已存在,请重新注册")
        this.TplName = "register.html"
        return
    }
    // 用户名不存在,插入新用户
    o.Insert(&user)
    log.Printf("注册成功")
    //4.返回页面
    //this.Ctx.WriteString("注册成功")
    this.Redirect("/login", 302)
}

3.2登陆

登陆和注册业务流程差不多,差别也就体现在一个是对数据的查询一个是数据的插入,代码如下:

路由文件修改

添加下面一行代码:

beego.Router("/login", &controllers.UserController{},"get:ShowLogin;post:HandleLogin")

后台代码修改

在控制器中添加展示登录页的函数ShowLogin和处理登陆数据的函数HandleLogin。完整代码如下:

// 显示登录页面
func (this *UserController) ShowLogin() {
    this.TplName = "login.html"
}

// 处理登录数据
func (this *UserController) HandleLogin() {
    //获取数据
    userName := this.GetString("userName")
    pwd := this.GetString("password")
    //校验数据
    if userName == "" || pwd == "" {
        this.Data["errmsg"] = "用户名或密码为空,请重新登录"
        log.Printf("用户名或密码为空,请重新登录")
        this.TplName = "login.html"
        return
    }
    //操作数据
    o := orm.NewOrm()
    var user models.User
    user.Name = userName
    err := o.Read(&user, "Name")
    if err != nil {
        this.Data["errmsg"] = "用户不存在"
        log.Printf("用户名不存在")
        this.TplName = "login.html"
        return
    }
    if user.PassWord != pwd {
        this.Data["errmsg"] = "密码错误"
        log.Printf("密码错误")
        this.TplName = "login.html"
        return
    }
    //返回页面
    this.Ctx.WriteString("登录成功")
}

添加视图文件

登陆界面和注册界面很相似,拷贝过来简单修改一下即可,代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>登录页面</title>
  </head>
  <body>
    <form method="post" action="/login">
      用户名:<input type="text" name="userName"></br>
      密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"></br>
      <input type="submit" value="登录"></br>
      {{.errmsg}}
    </form>
  </body>
</html>

这样我们的登陆和注册就算完成了,但是有一个问题,登陆注册还是各干各的,没有关联起来,前面等登陆页面实现完之后,注册成功就跳转到登陆页面。现在来实现一下跳转。

3.3页面之间的跳转

beego里面页面跳转的方式有两种,一种是重定向,一种是渲染,都能够在浏览器上显示新的页面

重定向

重定向用到的方法是this.Redirect()函数,有两个参数,第一个参数是请求路径,第二个参数是http状态码。

请求路径就不说了,就是和超链接一样的路径。

状态码一共分为五类:

1xx : 服务端已经接收到了客户端请求,客户端应当继续发送请求 。常见的请求:100

2xx :请求已成功 (已实现)常见的请求:200

3xx :请求的资源转换路径了,请求被跳转。常见的请求:300,302

4xx :客户端请求失败。常见的请求:404

5xx :服务器端错误。常见的请求:500

状态码详解:HTTP状态码详解

重定向的工作流程是:

  1. 当服务端向客户端响应 redirect后,并没有提供任何view数据进行渲染,仅仅是告诉浏览器响应为 redirect,以及重定向的目标地址
  2. 浏览器收到服务端 redirect 过来的响应,会再次发起一个 http 请求
  3. 由于是浏览器再次发起了一个新的 http 请求,所以浏览器地址栏中的 url 会发生变化
  4. 浏览中最终得到的页面是最后这个 重定向的url 请求后的页面
  5. 所以redirect("/register",302) 相当于你在浏览器中手动输入 localhost/register

渲染

渲染就是控制期把一些数据传递给视图,然后视图用这些输出组织成html界面。所以不会再给浏览器发请求,是服务器自己的行为,所以浏览器的地址栏不会改变,但是显示的页面可能会发生变化。用的函数是:this.TplName = "login.html"

两者之间的区别

区别

重定向

渲染

响应方式

告诉浏览器相应是redirect,然后返回一个新的地址给浏览器,让浏览器重新发起请求

直接给浏览器返回视图

地址栏显示

浏览器地址栏显示的是新的地址

浏览器地址栏显示的还是原来的地址

作用

不能给视图传递数据,但是能够获取到加载页面时的数据

能够给视图传递数据,但如果要获取加载页面时的数据,需要再次写相关代码

使用场景

页面跳转的时候

页面加载或者是登陆注册失败的时候

3.4完整代码及效果演示

项目目录结构:

models.go完整代码:

package models

import (
	"github.com/beego/beego/v2/adapter/orm"
	_ "github.com/go-sql-driver/mysql"
)

type User struct {
	Id       int
	Name     string
	PassWord string
}

func init() {
	//orm操作数据库
	orm.RegisterDataBase("default", "mysql", "root:wzj123456@tcp(127.0.0.1:3306)/test")
	orm.RegisterModel(new(User))
	orm.RunSyncdb("default", false, true)
}

user.go完整代码:

package controllers

import (
    "github.com/beego/beego/v2/adapter/orm"
    beego "github.com/beego/beego/v2/server/web"
    "log"
    "myapp/models"
)

type UserController struct {
    beego.Controller
}

// 显示注册页面
func (this *UserController) ShowRegister() {
    this.TplName = "register.html"
}

// 处理注册数据
func (this *UserController) HandlePost() {
    //1.获取数据
    userName := this.GetString("userName")
    pwd := this.GetString("password")
    //2.校验数据
    if userName == "" || pwd == "" {
        this.Data["errmsg"] = "用户名或密码为空,请重新注册"
        log.Printf("用户名或密码为空,请重新注册")
        this.TplName = "register.html"
        return
    }
    //3.操作数据
    //获取orm对象
    o := orm.NewOrm()
    var user models.User
    user.Name = userName
    user.PassWord = pwd
    // 判断用户名是否存在
    existingUser := models.User{Name: userName}
    err := o.Read(&existingUser, "Name")
    if err == nil {
        // 用户名已存在,注册失败
        this.Data["errmsg"] = "用户名已存在,请重新注册"
        log.Printf("用户名已存在,请重新注册")
        this.TplName = "register.html"
        return
    }
    // 用户名不存在,插入新用户
    o.Insert(&user)
    log.Printf("注册成功")
    //4.返回页面
    //this.Ctx.WriteString("注册成功")
    this.Redirect("/login", 302)
}

// 显示登录页面
func (this *UserController) ShowLogin() {
    this.TplName = "login.html"
}

// 处理登录数据
func (this *UserController) HandleLogin() {
    //获取数据
    userName := this.GetString("userName")
    pwd := this.GetString("password")
    //校验数据
    if userName == "" || pwd == "" {
        this.Data["errmsg"] = "用户名或密码为空,请重新登录"
        log.Printf("用户名或密码为空,请重新登录")
        this.TplName = "login.html"
        return
    }
    //操作数据
    o := orm.NewOrm()
    var user models.User
    user.Name = userName
    err := o.Read(&user, "Name")
    if err != nil {
        this.Data["errmsg"] = "用户不存在"
        log.Printf("用户名不存在")
        this.TplName = "login.html"
        return
    }
    if user.PassWord != pwd {
        this.Data["errmsg"] = "密码错误"
        log.Printf("密码错误")
        this.TplName = "login.html"
        return
    }
    //返回页面
    this.Ctx.WriteString("登录成功")
}

router.go完整代码:

package routers

import (
	beego "github.com/beego/beego/v2/server/web"
	"myapp/controllers"
)

func init() {
	beego.Router("/", &controllers.MainController{})
	beego.Router("/register", &controllers.UserController{}, "get:ShowRegister;post:HandlePost")
	beego.Router("/login", &controllers.UserController{}, "get:ShowLogin;post:HandleLogin")
}

 

register.html完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form method="post" action="/login">
    用户名:<input type="text" name="userName"></br>
    密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"></br>
    <input type="submit" value="登录"></br>
    {{.errmsg}}
</form>
</body>
</html>

login.html完整代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>登录页面</title>
  </head>
  <body>
    <form method="post" action="/login">
      用户名:<input type="text" name="userName"></br>
      密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"></br>
      <input type="submit" value="登录"></br>
      {{.errmsg}}
    </form>
  </body>
</html>

效果演示

先注册

注册后自动跳转登录界面

有关错误提示:

4.Beego案例-新闻发布系统

4.1注册

后台代码和上面案例代码一致。所以这里面只写一个注册的业务流程图。

业务流程图

4.2登陆

业务流程图

登陆和注册业务和我们上面的登陆和注册基本一样,所以就不再重复写这个代码

但是我们遇到的问题是如何做代码的迁移,把上面的登陆和注册拿过来直接用?

  • 首先,我们需要把静态页面拷贝到我们项目目录下面。
    • 进入项目目录,删除掉原来的static文件夹
    • 然后拷贝我们static.zip到这个目录,并解压

📎static.zip

    • 打开static文件夹,显示如下,则说明拷贝成功:

  • 把static文件夹中所有的html文件都拷贝到views文件夹下面,里面原有的文件也全部删除,拷贝之后views文件显示如下:

这时候用GoLand打开我们的项目,显示如下:

  • 打开register.html页面,修改页面中form表单的内容
    • <form>标签加上action="/register" method="post"属性
    • 给两个<input>标签的name分别改为name="userName"name = "passwd"
    • 表单相关代码如下:
<!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">
  </head>
  <body>
    <div class="login_logo">
      <img src="/static/img/logo.png" alt="">
    </div>
    <form  class="login_form" name = "login" method="post" action="/register">
      <h1 class="login_title">用户注册</h1>
      <input type="text" placeholder="用户名" class="input_txt" name="userName">
      <input type="password" placeholder="密码" class="input_txt" name = "password">
      <input type="submit" value="注 册" class="input_sub">
    </form>
    <div class="login_bg"></div>
  </body>
</html>
  • 打开login.html页面,修改form表单的内容
    • <form>标签加上action="/login" method="post"属性
    • 给两个<input>标签的name分别改为name="userName"name = "passwd"
    • 表单相关代码如下:
<!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">
  </head>
  <body>
    <div class="login_logo">
      <img src="/static/img/logo.png" alt="">
    </div>
    <form  class="login_form"  name = "login" method="post" action="/login">
      <h1 class="login_title">用户登录</h1>
      <input type="text"  class="input_txt" name = "userName">
      <input type="password" name = "password"  class="input_txt">
      <div class="remember"><input type="checkbox" name="remember" ><label>记住用户名</label></div>
      <input type="submit" value="登 录" class="input_sub">
    </form>
    <div class="login_bg"></div>
  </body>
</html>

(登陆界面多了个记住用户名标签,后面实现这个功能)

改完之后,运行项目,测试注册和登陆页面能够能唱显示,并且功能没有问题说明代码迁移成功。

注册页面:

点击注册按钮以后自动跳转登陆页面:

点击登录按钮显示登录完成:

4.3创建数据库

4.3.1数据库表的设计

接下来就要实现文章相关的操作,所以这里要在数据库中生成一个文章表。

之前在数据库中创建表的时候,会给字段加很多限制属性,比如非空,长度,默认值等等,在ORM中,创建表时也可以给各个字段添加相应的限制。那如何去加限制呢?先看例子:

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)"`
}

由上面的代码可以看出,要给哪个字段添加属性,需要在这个字段后面添加 `` 括起来的内容,格式为orm:"限制条件"。那这些限制条件都有哪些呢?我在这里给大家列了一个表格。

限制条件

作用

pk

设置该字段为主键

auto

这只该字段自增,但是要求该字段必须为整型

default(0)

设置该字段的默认值,需要注意字段类型和默认值类型一致

size(100)

设置该字段长度为100个字节,一般用来设置字符串类型

null

设置该字段允许为空,默认不允许为空

unique

设置该字段全局唯一

digits(12);decimals(4)

设置浮点数位数和精度。比如这个是说,浮点数总共12位,小数位为四位。

auto_now

针对时间类型字段,作用是保存数据的更新时间

auto_now_add

针对时间类型字段,作用是保存数据的添加时间

注意:当模型定义里没有主键时,符合int, int32, int64, uint, uint32, uint64 类型且名称为 Id 的 Field 将被视为主键,能够自增. "

Mysql中时间类型有date和datetime两种类型,但是我们go里面只有time.time一种类型,如果项目里面要求精确的话,就需要指定类型,指定类型用的是type(date)或者type(datetime)

4.3.2生成表

这时候注意,我们添加了结构体对象之后,并不能直接生成表,需要注册,注册的代码就是初始化数据库三行代码中的第二行,注册表结构,把要创建的表对应的结构体对象作为函数的参数,代码如下:

orm.RegisterModel(new(User),new(Article))

创建之后,我们可以在goland下方查看创建表过程,也可以进入数据库查看是否建表成功,成功的话,数据库显示如下:

登陆成功之后,访问新闻列表展示页面,但是现在还没有新闻,所以接下来实现插入文章界面。

4.4插入文章

业务流程图

插入页面用的视图是add.html,这里规定添加文章界面的请求路径为/addArticle

4.4.1修改路由文件

在router.go文件的init函数中添加下面这一行代码

beego.Router("/addArticle",&controllers.ArticleController{},"get:ShowAddArticle")

4.4.2添加文章界面的显示

  • 先创建一个article.go文件用来存放文章有关的业务代码
  • 然后在article.go文件中创建一个ArticleController控制器,并定义一个ShowAddArticle函数代码,并实现ShowAddArticle函数,这个函数只是用来展示页面的,所以只需要给他制定一个视图就可以,代码如下:
package controllers

import beego "github.com/beego/beego/v2/server/web"

type ArticleController struct {
    beego.Controller
}

func (this *ArticleController) ShowAddArticle() {
    this.TplName = "add.html"
}

写完代码之后,我们从浏览器发出一个请求http://192.168.110.71:8080/addArticle,如果能在浏览器中看到下面这个界面,表示页面展示成功:

4.4.3插入文章数据处理

上面显示了添加文章界面,观察界面可以发现,需要获取文章标题文章类型, 文章内容上传图片。其中文章类型牵涉到多表操作,之后再说。首先看一下插入页面的前端部分修改。

前端页面修改

由页面可知,这里面是要上传数据,所以这里需要一个form表单,打开前端界面add.html,能看到我们这里面确实有一个<form>标签,只是没有属性,我们需要给<form>标签添加action和method属性,这个请求还是添加文章,所以我们还可以用添加文章的请求路径,设置action属性action="/addArticle"。因为上传数据,我们这里用post方法,设置method属性method="post"。其他部分不用修改。form修改代码如下:

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

路由内容修改

我们在前端添加了addArticle请求的post方法,所以需要修改一下router.go,给addArticle的post请求指定一个函数,修改代码如下:

beego.Router("/addArticle",&controllers.ArticleController{},"get:ShowAddArticle;post:HandleAddArticle")

后台代码实现

有了函数名之后,就需要在后台中实现这个函数。

首先是获取数据

这时候看一下前端界面,我们需要获取文章标题,文章内容上传图片数据,文章标题和文章内容都是字符串,比较简单,直接通过GetString获取,所以我们先获取这两个内容。通过查看add.html代码我们发现,文章标题对应的<input>标签name等于articleName,文章内容对应的<textarea>标签name等于content(注意这里用的是textarea标签,不是用的input,但是获取数据方式一样)。获取数据的代码如下:

//获取数据
articleName := this.GetString("articleName")
content := this.GetString("content")

获取数据之后就做数据校验,我们这里还是做判空校验

	//2.校验数据
	if articleName == "" || content == "" {
		this.Data["errmsg"] = "添加数据不完整"
		this.TplName = "add.html"
		return
	}
	log.Printf("articleName:%s,content:%s", articleName, content)

测试一下

正常的添加流程,在校验完数据之后就要把数据插入数据库了,但是我们添加文章这个界面有点特殊,因为这里面牵涉到一个静态文件的上传,所以我们先处理静态文件上传功能。

静态文件上传(难点)

  • 前端代码

如果form表单中牵涉到文件上传,在form表单中就需要添加一个属性enctype="multipart/form-data"不然上传就是假上传,后台不能获取到上传的文件。<form>修改如下:

<form method="post" action="/addArticle" enctype="multipart/form-data">

  • 后台代码修改

后台接收上传文件有两个函数可以用。

GetFile(key string) (multipart.File, *multipart.FileHeader, error)

作用 是获取前端传递过来的文件。

参数 是input标签中的name值

返回值 有三个,一个是文件流(就是我们打开文件返回的内容),第二个是文件相关信息,包括文件头,文件大小,文件名字等,第三个是错误信息。示例代码如下:

file, head, err := this.GetFile("uploadname")
defer file.Close()
if err != nil {
    this.Data["errmsg"] = "上传图片失败"
    log.Printf("上传图片失败")
    this.TplName = "add.html"
    return
}

SaveToFile(fromfile, tofile string) error

作用直接保存前端出过来的文件。

参数 有两个参数,第一个参数是前端<input>标签的name属性值,第二个参数是文件在服务器端存储的位置。注意:这个位置字符串在前面需要加一个.

返回值是错误信息。示例代码如下:

err := this.SaveToFile("uploadname","./static/img/head.Filename")
if err != nil {
    this.Data["errmsg"] = "上传图片失败"
    log.Printf("上传图片失败")
    this.TplName = "add.html"
    return
}

在开发过程中,如果后台接收文件并存储需要做以下几种判断

文件格式判断

通过GetFile可以获取到文件名,然后通过path包,可以分离出文件的后缀,即文件格式,把你需要的文件格式过滤出来,不需要的返回即可。根据文件名获取文件后缀,代码如下:

	//2.文件格式
	ext := path.Ext(head.Filename)
	if ext != ".jpg" && ext != ".png" && ext != ".jpeg" {
		this.Data["errmsg"] = "上传图片格式不正确,请重新上传"
		log.Printf("上传图片格式不正确,请重新上传")
		this.TplName = "add.html"
		return
	}

文件大小的判断

获取文件之后,在存储之前,文件流一般是在内存中,所以文件不易过大,在这里做一个文件大小的判断。代码如下:

	//1.文件大小
	if head.Size > 5000000 {
		this.Data["errmsg"] = "上传图片过大,请重新上传"
		log.Printf("上传图片过大,请重新上传")
		this.TplName = "add.html"
		return
	}

避免文件重名

获取文件之后我们要把文件存储到服务器上,但是用户可能会上传同名的文件,如果文件同名的话,后来上传的文件就把之前上传的文件给覆盖了,所以我们要给上传的文件重新确定一个名字。这里我们以上传文件时的时间作为上传文件的文件名。默认的时间格式和我们常见的时间格式不一样,所以这里我们需要对事件做一个格式化。格式化字符串为"2006-01-02-15-04-05(规定的必须是这个,方便记忆可以用6-1-2-3-4-5来记)代码如下:

fileName := time.Now().Format("2006-01-02-15-04-05") + ext
//存储
this.SaveToFile("uploadname", "./static/upload/"+fileName)

保存数据到数据库,代码如下:

//3.处理数据
o := orm.NewOrm()
var article models.Article
article.ArtiName = articleName
article.Acontent = content
article.Aimg = "/staic/img/" + fileName
o.Insert(&article)

返回视图,代码如下:

this.Redirect("/showArticleList", 302)

这里忘记写了,具体实现方式在下面新闻信息展示里面有

完整代码如下:

package controllers

import (
    "github.com/beego/beego/v2/adapter/orm"
    beego "github.com/beego/beego/v2/server/web"
    "log"
    "myapp/models"
    "path"
    "time"
)

type ArticleController struct {
    beego.Controller
}

func (this *ArticleController) ShowArticleList() {
    this.TplName = "index.html"
}

func (this *ArticleController) ShowAddArticle() {
    this.TplName = "add.html"
}

// 获取添加文章数据
func (this *ArticleController) HandleAddArticle() {
    //1.获取数据
    articleName := this.GetString("articleName")
    content := this.GetString("content")
    //2.校验数据
    if articleName == "" || content == "" {
        this.Data["errmsg"] = "添加数据不完整"
        log.Printf("添加数据不完整")
        this.TplName = "add.html"
        return
    }
    log.Printf("articleName:%s,content:%s", articleName, content)
    //处理文件上传
    file, head, err := this.GetFile("uploadname")
    defer file.Close()
    if err != nil {
        this.Data["errmsg"] = "上传图片失败"
        log.Printf("上传图片失败")
        this.TplName = "add.html"
        return
    }
    //1.文件大小
    if head.Size > 5000000 {
        this.Data["errmsg"] = "上传图片过大,请重新上传"
        log.Printf("上传图片过大,请重新上传")
        this.TplName = "add.html"
        return
    }
    //2.文件格式
    ext := path.Ext(head.Filename)
    if ext != ".jpg" && ext != ".png" && ext != ".jpeg" {
        this.Data["errmsg"] = "上传图片格式不正确,请重新上传"
        log.Printf("上传图片格式不正确,请重新上传")
        this.TplName = "add.html"
        return
    }
    //3.防止重名
    fileName := time.Now().Format("2006-01-02-15-04-05") + ext
    //进行存储
    this.SaveToFile("uploadname", "./static/img/"+fileName)

    //3.处理数据
    o := orm.NewOrm()
    var article models.Article
    article.ArtiName = articleName
    article.Acontent = content
    article.Aimg = "/staic/img/" + fileName
    o.Insert(&article)

    //4.返回页面
    this.Redirect("/showArticleList", 302)
}

测试一下

选择一张图片进行添加

点击添加后自动跳转主界面

再查看一下图片是否真的添加到文件夹内

再查看一下数据库中有没有存入信息

5.新闻信息展示

添加文章之后我们回到文章显示界面,我们这里固定显示文章列表页的请求为/ShowArticleList,然后给这个请求指定控制器,以及相应的方法修改。

5.1修改路由文件

首先我们修改路由文件,代码如下:

beego.Router("/ShowArticleList",&controllers.ArticleController{},"get:ShowArticleList")

5.2后台代码

修改路由文件之后,再来实现ShowArticleList函数。

5.2.1获取所有文章

获取orm对象

o := orm.NewOrm()

定义一个对象数组,用来存储获取的所有对象

var articles []models.Article

指定要查询的数据库表,用QueryTable函数,参数是表名,返回值是queryseter,ORM 以 QuerySeter 来组织查询,每个返回 QuerySeter 的方法都会获得一个新的 QuerySeter 对象。

qs := o.QueryTable("Article")

获取所有数据,用all方法,参数是对象数组地址

_,err := qs.All(&articles)

获取数据之后把数据传递给视图,并且指定视图文件

this.Data["articles"] = articles
this.TplName = "index.html"

全部代码:

func (this *ArticleController) ShowArticleList() {
	//制定表
	o := orm.NewOrm()
	qs := o.QueryTable("article")//queryseter
	var articles []models.Article
	_,err := qs.All(&articles)
	if err != nil {
		log.Printf("获取数据失败")
	}
	//传递数据
	this.Data["articles"] = articles
	this.TplName = "index.html"
}

ORM高级查询(重点)

我们在后面项目开发中对数据库的查询,一般都是指定数据库表,用高级查询的方法进行查询。ORM支持如下几种高级查询。

函数名

作用

用法

Limit()

获取部分数据

有两个参数,第一个参数是指定获取几条数据,第二个参数指定从哪里获取qs.Limit(size,start)。返回值还是qs

OrderBy()

根据指定的字段排序

只有一个参数,参数作用是指定按照哪个字段排序,返回值是qs

Distinct()

去重

没有参数,返回值是qs

Count()

查询符合条件的数据条目数

没有参数,返回值是查询到的条目数和错误信息

All()

把查询到的数据全部存储到指定的容器里面

只有一个参数,指定存储查询对象的存储容器

RelatedSel()

多表查询的时候使用,指定关联的数据库表

参数长度不限,关联几个表,放几个参数

Filter()

过滤器,相当于SQL语句中的where

有两个参数,第一个参数是指定查询条件,第二个参数是值

...

...

...

还有其他很多高级查询,具体参考:https://beego.me/docs/mvc/model/query.md页面查看

5.3前端代码

5.3.1视图循环语法

后台传递给视图的数据是对象数组,要访问到每一个对象需要循环访问这个数组,那我们来看一下这个循环语法。循环语法有两种,一种格式如下:

{{range $index,$val := .articles}}
        {{$val}}        
{{end}}

$index表示的是下标,$val表示的数组元素,循环的内容放在range和end之间。

另外一种循环如下:

{{range .articles}}
    {{.Name}}
{{end}}

在range和end之间通过{{.}}直接获取数组元素的字段值。

5.3.2视图数据展示

了解了视图的循环语法之后,我们就可以在index.html文件中循环获取控制器传递过来的对象数组数据。代码如下:

{{range .articles}}
    <tr>
       <td>{{.ArtiName}}</td>
       <td><a href="#">查看详情</a></td>
       <td> {{.Atime.Format "2006-01-02-15:04:05"}}</td>
       <td>{{.Acount}}</td>
       <td><a href="#" class="dels">删除</a></td>
       <td><a href="#">编辑</a></td>
       <td>财经新闻</td>
    </tr>
{{end}}

此时进入文章显示页面显示如下界面即成功

5.4数据的分页显示(难点)

观察我们的列表页可以发现,我们文章里表下面是分页展示,接着我们来实现这个分页。这里先实现简单的分页功能

分页的好处:如果没有分页,我们访问完数据就要全部在页面显示,有分页之后我们可以显示部分数据,好处有一下两点。

  • 方便浏览,分页浏览可以更方便我们平常访问网页。
  • 提高访问网站速度。如果一次性把数据全部从数据库中取出来,效率没有一次取出部分数据块。

了解了上面的内容之后我们开始写代码实现分页的功能,一般开发中遇见这种大的功能模块,我们都是划分为几个小块,一点一点来实现。我们从简单到复杂来实现相应功能,首先我们先获取总页数和总记录数。

5.4.1获取总记录数和总页数

首页显示代码是ShowArticleList函数,所以我们分页的代码也在这个函数里面。

获取总记录数,orm是用count函数来获取数据的记录数,没有参数,返回值为记录数和错误信息,代码如下:

count,_ := qs.Count()

获取总页数

总页数 = 总记录数 / 每页显示的数据条数

总记录数我们已经获取了,所以需要我们自己设置每页显示多少条数据,然后相除就可以获得,代码如下:

//每页显示几条数据
pageSize := 2
//获取总页数
pageCount := count / int64(pageSize)
  • 把数据传递给视图,并在视图中显示。
this.Data["pageCount"] = pageCount
this.Data["count"] = count

这时候你会发现,当你的最后一页显示的数据不满的话,总页数会少计算一页,原因是我们求总页数的计算是两个整数相除,除不尽的时候会自动舍去小数位。

这和我们真实的业务不相符。所以我们需要修改获取总页数的代码。怎么修改呢?完全改成浮点数显然也不行,因为总页码不会是小数。这里面我们用函数Ceil()。Ceil()的作用是传递过来一个浮点数,获取比这个浮点数大的又离这个浮点数最近的整数,代码如下:

//获取总页数
pageCount :=math.Ceil(float64(count) / float64(pageSize))

页码这时候显示正确

5.4.2获取首页和末页数据

获取完总页数和总记录数之后,最简单的功能模块就是首页和末页内容的显示。首页和末页,我们需要把相应的页码传递过来才能知道获取哪些数据。那视图如何给后台传递数据呢?我们在平常浏览网页的时候经常会遇到类似于这样的URL地址

http://tieba.baidu.com/f?fr=index&fp=0&ie=utf-8&red_tag=m2329796506

我们重点关注?后面的内容,他们是成对出现的,每对之间用&连接,这种是URL传值的一种。我们在后台通过GetString函数可以获取到相应的值。

设置首页的超链接

我们可以通过URL传值的方式把页码传递过来。这里我们设置首页的<a>标签超链接为/ShowArticleList?pageIndex=1

获取首页数据

我们先通过GetString()获取到页码,然后通过页码获取相应的数据。这里介绍数据库获取部分数据的函数Limit()

Limit()
作用:获取数据库中部分数据
参数:第一个参数是获取多少数据,第二个参数是从哪里开始取数据
返回值是queryseter类型

示例代码如下

qs := qs.Limit(pageSize,start)

我们掌握了limit函数之后,现在要获取数据库中部分数据,pageSize我们已经知道了,这个start怎么去求呢?我们可以根据start的规律来找,比如说,第一页数据的起始位置是0,第二页的起始位置是2,第三页的起始位置是4,发现起始位置刚好是页码减一乘以pageSize,由此我们得出公式。start = (pageIndex - 1) * pageSize

那么我们获取首页的代码如下:

//获取页码
pageIndex,_ := this.GetInt("pageIndex")
//确定数据的起始位置
start := (pageIndex - 1) * pageSize
//查询数据库部分数据
qs.Limit(pageSize,start).All(&articles)

这时候有个问题,我们从其他页面跳转到首页的时候没有指定pageIndex,所以我们需要对获取不到pageIndex的情况进行处理,处理方案:当没有获取到pageIndex的时候默认pageIndex等于1,即默认访问首页内容。修改后的代码如下:

//获取页码
pageIndex,err := this.GetInt("pageIndex")
if err != nil{
    pageIndex = 1
}
//确定数据的起始位置
start := (pageIndex - 1) * pageSize
//查询数据库部分数据
qs.Limit(pageSize,start).All(&articles)

获取末页数据只要参考着首页,把传过来的pageIndex改为总页码数即可。设置末页的链接为/ShowArticleList?pageIndex={{.pageCount}}

这时候记得把页码也传递给视图

this.Data["pageIndex"] = pageIndex

全部代码

func (this *ArticleController) ShowArticleList() {
	//获取数据
	//高级查询
	//指定表
	o := orm.NewOrm()
	qs := o.QueryTable("Article") //queryseter
	var articles []models.Article
	//_, err := qs.All(&articles)
	//if err != nil {
	//	log.Printf("获取数据失败")
	//}
	//查询总记录数
	count, _ := qs.Count()
	//每页显示几条数据
	pageSize := 2
	//获取总页数
	pageCount := math.Ceil(float64(count) / float64(pageSize))
	//获取页码
	pageIndex, err := this.GetInt("pageIndex")
	if err != nil {
		pageIndex = 1
	}
	//获取数据
	//作用就是获取数据库中部分数据,第一个参数:获取几条,第二个参数:从那条数据开始获取,返回值还是qs
	//起始位置计算
	start := (pageIndex - 1) * pageSize
	qs.Limit(pageSize, start).All(&articles)

	//传递数据
	this.Data["pageIndex"] = pageIndex
	this.Data["pageCount"] = pageCount
	this.Data["count"] = count
	this.Data["articles"] = articles
	this.TplName = "index.html"
}

测试一下,点击首页

点击末页

5.4.2获取上一页和下一页数据

前面我们已经获取了首页和末页的数据,仿照着链接,我们可以把上一页下一页的链接也实现,设置上一页的超链接为/ShowArticleList?pageIndex={{.pageIndex}} - 1,但是你在index.html写了这个之后,编辑器会报错,html标签属性不能直接进行数学运算。这时候就要用到视图函数

视图函数(模板函数)

使用条件:beego支持用户定义视图函数,但是必须在beego.Run()调用之前。

设置如下:

先定义函数

func hello(in string)(out string){
    out = in + "world"
    return
}

添加映射

添加映射是把后台的函数名和视图中调用的函数名关联起来,两个名字可以不一样。用的方法是AddFuncMap(),第一个参数是视图中调用的函数名,第二个参数是后台的函数名

beego.AddFuncMap("hi",hello)这一步必须在beego.Run()之前调用

在视图中调用,有两种形式

第一种调用视图函数

{{.Content | hi}}

注意,这里面的.Content是传递给函数的参数,类型要一致,函数的返回值将在这里显示,只能传递一个参数

第二种调用视图函数

{{hi .Content}}

第二种方法刚好和第一种方法顺序反过来,是先写函数名,再写参数,如果参数比较多,可以一直往后写。这种方法在开发中也比较常用。

beego默认封装的视图函数

函数名

函数作用

使用方法

dateformat

实现了时间的格式化,返回字符串。

{{dateformat .Time “2006-01-02T15:04:05Z07:00”}}

date

实现了类似 PHP 的 date 函数,可以很方便的根据字符串返回时间 。

{{date .T “Y-m-d H:i:s”}}

compare

实现了比较两个对象的比较,如果相同返回 true,否者 false。

{{compare .A .B}}

substr

实现了字符串的截取,支持中文截取的完美截取

{{substr .Str 0 30}}

html2str

实现了把 html 转化为字符串,剔除一些 script、css 之类的元素,返回纯文本信息 。

{{html2str .Htmlinfo}}

str2html

实现了把相应的字符串当作 HTML 来输出,不转义

{{str2html .Strhtml}}

用视图函数实现获取上一页下一页页码

定义函数

因为函数要在beego.Run()之前执行,我们可以把函数直接定义在main.go中,定义函数如下:

// 获取上一页页码
func ShowPrePage(pageIndex int) int {
    return pageIndex - 1
}

// 获取下一页页码
func ShowNextPage(pageIndex int, pageCount int) int {
    return pageIndex + 1
}

添加映射

beego.AddFuncMap("pre", ShowPrePage)
beego.AddFuncMap("next", ShowNextPage)

在视图中调用

<li><a href="/showArticleList?pageIndex={{pre .pageIndex}}">上一页 </a> </li>
<li> <a href="/showArticleList?pageIndex={{next .pageIndex .pageCount}}">下一页</a></li>

问题:显示之后,我们点击上一页下一页发现功能实现了,但是有一个问题,一直点击上一页页码能出现负值,一直点击下一页页码能超过总页码,那我们怎么解决呢?

问题解决

页码超出范围的问题,思路:只需要在获取上一页下一页页码的时候对页码做一个判断即可,代码如下:

// 获取上一页页码
func ShowPrePage(pageIndex int) int {
	if pageIndex == 1 {
		return pageIndex
	}
	return pageIndex - 1
}

// 获取下一页页码
func ShowNextPage(pageIndex int, pageCount int) int {
	if pageIndex == pageCount {
		return pageIndex
	}
	return pageIndex + 1
}

同时这里还会出现一个问题,当进入文章页面时会出现错误

是因为我们使用math.Ceil()函数时返回的是float61类型的值,需要将pageCount定义为整型

修改显示第几页功能

到这里我们的分页功能就完全实现了

这里我才发现又有一个问题:

atime时间和实际时间相差八小时

不知道啥情况,解决了半天都没解决掉,暂且先不管了。。。。。

6.查看文章详情

业务流程图如下:

首先我们还是需要设计一下查看详情的请求路径。分析可知,我们查看文章详情必须指定要查看哪一篇文章,所以我们在点击查看详情的时候需要把能够标识具体哪一篇文章的数据传递给后台,这里我们通过URL传值的方式,传递文章ID给后台,设计路由为/ShowArticleDetail?id=article.Id

6.1文章详情页面显示

前端处理

修改查看详情的超链接代码如下:

<td><a href="/showArticleDetail?articleId={{.Id}}">查看详情</a></td>

修改路由文件,添加查看详情的路由匹配,然后指定控制和请求对应的方法,修改如下:

beego.Router("/showArticleDetail", &controllers.ArticleController{}, "get:ShowArticleDetail")

实现ShowArticleDetail()函数

首先呢,我们需要获取传递过来的文章id

id, er := this.GetInt("articleId")

然后做数据校验

//数据校验
if er != nil {
    log.Printf("获取文章id失败")
}

数据没问题的话,就根据文章id查询文章信息

o := orm.NewOrm()
var article models.Article
article.Id = id
o.Read(&article)

获取数据之后,指定视图,并给视图传递数据

//传递数据给视图,并指定视图
this.Data["article"] = article
this.TplName = "content.html"

完整代码如下

// 展示文章详情页面
func (this *ArticleController) ShowArticleDetail() {
    //获取数据
    id, er := this.GetInt("articleId")
    //数据校验
    if er != nil {
        log.Printf("获取文章id失败")
    }
    //操作数据
    o := orm.NewOrm()
    var article models.Article
    article.Id = id
    o.Read(&article)
    //修改阅读量
    article.Acount += 1
    o.Update(&article)
    //返回视图页面
    this.Data["article"] = article
    this.TplName = "content.html"
}

修改视图文件content.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">
  </head>
  <body>
    <div class="header">
      <a href="#" class="logo fl"><img src="/static/img/logo.png" alt="logo"></a>
      <a href="#" class="logout fr">退 出</a>
    </div>

    <div class="side_bar">
      <div class="user_info">
        <img src="/static/img/person.png " alt="张大山" width="68px" height="67px">
        <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="#" class="icon032">添加文章</a></li>
          <li><a href="#" class="icon034">添加分类</a></li>
        </ul>
      </div>
    </div>

    <div class="main_body" id="main_body">
      <div class="breadcrub">
        当前位置:文章管理>文章列表>文章详情
      </div>
      <div class="pannel">
        <h3 class="review_title">文章详情</h3>
        <div class="form_group">
          <label>文章标题:</label>
          <p class="detail"><b>{{.article.ArtiName}}</b></p>
        </div>
        <div class="form_group">
          <label>文章类型:</label>
          <p class="detail">体育新闻</p>
        </div>
        <div class="form_group">
          <label>文章内容:</label>
          <p class="detail"><img src="{{.article.Aimg}}"><br>{{.article.Acontent}}</p>
        </div>
        <div class="form_group">
          <label>阅读次数:</label>
          <p class="detail">{{.article.Acount}}</p>
        </div>
        <div class="form_group">
          <label>最近浏览:</label>
          <p class="detail">张三 | 李四 |</p>
        </div>
        <div class="form_group">
          <label>创建时间:</label>
          <p class="detail">{{.article.Atime.Format "2006-01-02-15:04:05"}}</p>
          <span>{{.errmsg}}</span>
        </div>
      </div>
    </div>

  </body>
</html>

我们查看详情页面的显示这部分就实现了。

演示如下:

这里又出现了一个问题就是我之前在把图片信息写入数据库的时候写成了"/staic/img/" + fileName,导致图片路径出错,现在做修改改为"/static/img/" + fileName

6.2阅读次数增加

每次查看详情其实就是阅读次数的增加,我们需要在查看详情函数里面给阅读次数加一,代码如下:

//给查询出来的文章阅读次数加一
article.Acount += 1
o.Update(&article)

7.编辑文章内容

7.1编辑页面显示

业务流程图如下:

修改路由文件,代码如下:

beego.Router("/updateArticle", &controllers.ArticleController{}, "get:ShowUpdateArticle")

然后在后台查询数据传递给视图,逻辑重复,就不详细分析了,代码如下:

// 显示编辑页面
func (this *ArticleController) ShowUpdateArticle() {
    //获取数据
    id, err := this.GetInt("articleId")
    //校验数据
    if err != nil {
        log.Printf("获取文章失败")
    }
    //数据处理
    //查询相应文章
    o := orm.NewOrm()
    var article models.Article
    article.Id = id
    o.Read(&article)
    //返回页面
    this.Data["article"] = article
    this.TplName = "update.html"
}

前端数据update.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">
  </head>
  <body>
    <div class="header">
      <a href="/showArticleList" class="logo fl"><img src="/static/img/logo.png" alt="logo"></a>
      <a href="#" 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="#" class="icon032">添加文章</a></li>
          <li><a href="#" class="icon034">添加分类</a></li>
        </ul>
      </div>
    </div>

    <div class="main_body" id="main_body">
      <div class="breadcrub">
        当前位置:文章管理>编辑文章
      </div>
      <div class="pannel">
        <form name = logon >
          <h3 class="review_title">编辑文章</h3>
          <div class="form_group">
            <label>文章标题:</label>
            <input type="text" class="input_txt2" name = "articleName" value="{{.article.ArtiName}}">
          </div>
          <div class="form_group">
            <label>文章内容:</label>
            <textarea class="input_multxt" name="content">{{.article.Acontent}}</textarea></textarea>
          </div>
          <div class="form_group">
            <label>上传图片:</label>
            <img src="{{.article.Aimg}}" width="240" height="150">
            <input type="file" name="uploadname" class="input_file">
          </div>
          <div class="form_group indent_group line_top">
            <input type="submit" value="添 加" class="confirm">
            <span>{{.errmsg}}</span>
          </div>
        </form>

      </div>

    </div>


  </body>
</html>

在index.html文件中修改编辑选项使其跳转编辑页面

<td><a href="/updateArticle?articleId={{.Id}}">编辑</a></td>

在浏览器进入主页面并点击编辑按钮跳转编辑页面

为了方便,点击logo图片就能跳转回到显示文章页面,在html文件里面修改href="/showArticleList"即可实现该功能

7.2编辑文章数据

这一步其实是对查询到的文章进行更新操作,我们还用获取页面时的请求路径UpdateArticle?id=article.id,但是请求改为post请求,form标签修改如下:

<form name = logon method="post" action="/updateArticle?articleId={{.article.Id}}" enctype="multipart/form-data">

这里需要上传图片,记得给form添加enctype属性

接着我们去路由文件里面给我们这个请求指定方法。

beego.Router("/updateArticle", &controllers.ArticleController{}, "get:ShowUpdateArticle;post:HandleUpdateArticle")

然后去实现HandleUpdate函数,过程仍然是获取数据,校验数据,更新数据,返回视图这几步,没有什么新的知识点,我们就不做详细分析,直接看代码:

// 封装上传文件函数
func UploadFile(this *beego.Controller, filePath string) string {
    //处理文件上传
    file, head, err := this.GetFile(filePath)
    if err != nil {
        this.Data["errmsg"] = "上传图片失败"
        log.Printf("上传图片失败")
        this.TplName = "add.html"
        return ""
    }
    defer file.Close()
    //1.文件大小
    if head.Size > 5000000 {
        this.Data["errmsg"] = "上传图片过大,请重新上传"
        log.Printf("上传图片过大,请重新上传")
        this.TplName = "add.html"
        return ""
    }
    //2.文件格式
    ext := path.Ext(head.Filename)
    if ext != ".jpg" && ext != ".png" && ext != ".jpeg" {
        this.Data["errmsg"] = "上传图片格式不正确,请重新上传"
        log.Printf("上传图片格式不正确,请重新上传")
        this.TplName = "add.html"
        return ""
    }
    //3.防止重名
    fileName := time.Now().Format("2006-01-02-15-04-05") + ext
    //存储
    this.SaveToFile(filePath, "./static/img/"+fileName)
    return "/static/img/" + fileName
}

// 处理编辑界面数据
func (this *ArticleController) HandleUpdateArticle() {
    //获取数据
    id, err := this.GetInt("articleId")
    articleName := this.GetString("articleName")
    content := this.GetString("content")
    filePath := UploadFile(&this.Controller, "uploadname")
    //数据校验
    if err != nil || articleName == "" || content == "" || filePath == "" {
        log.Printf("请求错误")
        return
    }
    //数据处理
    o := orm.NewOrm()
    var article models.Article
    article.Id = id
    err = o.Read(&article)
    if err != nil {
        log.Printf("更新的文章不存在")
        return
    }
    article.ArtiName = articleName
    article.Acontent = content
    article.Aimg = filePath
    o.Update(&article)
    //返回试图
    this.Redirect("/showArticleList", 302)
}

测试一下

8.删除文章

8.1删除功能实现

业务流程图如下:

删除功能相比较前面的功能算是比较简单的,只需要传递过来文章id值,然后删除文章即可。

首先我们还是要指定删除文章的请求路径:<td><a href="/deleteArticle?articleId={{.Id}}" class="dels">删除</a></td>

然后修改路由文件,为删除请求指定控制器,指定函数。

beego.Router("/deleteArticle", &controllers.ArticleController{}, "get:DeleteArticle")

然后在后台实现DeleteArticle函数,代码如下:

//删除文章处理
func (this *ArticleController) DeleteArticle() {
    //获取数据
    id, err := this.GetInt("articleId")
    //校验数据
    if err != nil {
        log.Printf("删除文章请求路径错误")
        return
    }
    //数据处理
    //删除操作
    o := orm.NewOrm()
    var article models.Article
    article.Id = id
    o.Delete(&article)
    //返回试图
    this.Redirect("/showArticleList", 302)
}

这时候你发现功能实现了,但是存在误删的可能,整个页面显的特别不友好,我们给页面加个js提示,防止误删。

8.2删除js提示

业务分析:当点击删除超链接的时候,弹出对话框,如果确认就发送请求,如果取消,就不发送请求:代码如下:

<script type="text/javascript">
  window.onload = function(ev){
    $(".dels").click(function (){
      if(!confirm("是否删除")){
        return false
      }
    })
  }
</script>

显示效果如下:

点击确认,成功删除

9.类型相关内容

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

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

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

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

package models

import (
    "github.com/beego/beego/v2/adapter/orm"
    _ "github.com/go-sql-driver/mysql"
    "time"
)

type User struct {
    Id       int
    Name     string
    PassWord string
    Articles []*Article `orm:"reverse(many)"` //设置多对多的反向关系
}

type Article struct {
    Id       int       `orm:"pk;auto"`
    ArtiName string    `orm:"size(20)"`
    Atime    time.Time `orm:"auto_now_add"`
    Acount   int       `orm:"default(0);null"`
    Acontent string    `orm:"size(500)"`
    Aimg     string    `orm:"size(100)"`

    ArticleType *ArticleType `orm:"rel(fk)"`  //设置一对多关系
    Users       []*User      `orm:"rel(m2m)"` //设置多对多关系
}

// 类型表
type ArticleType struct {
    Id       int
    TypeName string `orm:"size(20)"`

    Articles []*Article `orm:"reverse(many)"` //设置一对多的反向关系
}

func init() {
    //orm操作数据库
    orm.RegisterDataBase("default", "mysql", "root:wzj123456@tcp(127.0.0.1:3306)/test")
    orm.RegisterModel(new(User), new(Article), new(ArticleType))
    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作为文章表的外键。如图:

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

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。如下图:

多对多插入操作

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)//参数可以为对象,指针,对象数组,指针数组

多对多查询:

有两种方法:

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

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

优点是简单,快捷。

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

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

9.1添加类型

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

9.1.1添加类型页面显示

确定添加类型显示的请求路径为<li><a href="/addType" class="icon034">添加分类</a></li>

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

beego.Router("/addType", &controllers.ArticleController{}, "get:ShowAddType")

然后去控制器中实现ShowAddType函数,先简单的指定视图。代码如下:

// 展示添加类型页面
func (this *ArticleController) ShowAddType() {
	this.TplName = "addType.html"
}

然后在文章显示页面点击添加分类,显示如下:

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

9.1.2添加类型数据处理

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

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

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

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

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

// 处理添加类型数据
func (this *ArticleController) HandleAddType() {
    //获取数据
    typeName := this.GetString("typeName")
    //校验数据
    if typeName == "" {
        log.Printf("添加数据不完整")
        return
    }
    //处理数据
    //插入操作
    o := orm.NewOrm()
    var articleType models.ArticleType
    articleType.TypeName = typeName
    o.Insert(&articleType)
    //返回页面
    this.Redirect("/addType", 302)
}

9.1.3查询类型数据

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

后台代码

func (this *ArticleController) ShowAddType() {
    //查询
    o := orm.NewOrm()
    var types []models.ArticleType
    o.QueryTable("ArticleType").All(&types)

    //传递数据
    this.Data["types"] = types
    this.TplName = "addType.html"
}

视图代码

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

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

这时候我们点击添加分类,得到如下页面:

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

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

9.2.1添加带类型的文章

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

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

// 展示添加文章页面
func (this *ArticleController) ShowAddArticle() {
    //查询所有类型数据,并展示
    o := orm.NewOrm()
    var types []models.ArticleType
    o.QueryTable("ArticleType").All(&types)

    //传递数据
    this.Data["types"] = types
    this.TplName = "add.html"
}

视图展示数据

修改add.html文件循环获取数据,在下拉框中显示类型名称

<div class="form_group">
  <label>文章类型:</label>
  <select class="sel_opt" name="select">
    {{range .types}}
    <option>{{.TypeName}}</option>
    {{end}}
  </select>
</div>

添加文章的时候指定文章类型,代码如下:

//给文章添加类型
//获取类型数据
typeName := this.GetString("select")
//根据名称查询类型
var articleType models.ArticleType
articleType.TypeName = typeName
o.Read(&articleType, "TypeName")

article.ArticleType = &articleType

o.Insert(&article)

测试一下

添加文章

此时显示页面还没有改动显示内容

查看一下数据库里面有没有成功添加上

接下来便实现显示类型信息功能

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

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

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

显示的时候显示出来

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

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

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

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

//获取文章类型
var types []models.ArticleType
o.QueryTable("ArticleType").All(&types)
this.Data["types"] = types

视图代码:

<span class="sel_label">请选择文章分类:</span>
<select name="select" id="select" class="sel_opt">
  {{range .types}}
  <option selected="true">{{.TypeName}}</option>
  {{end}}
</select>

根据下拉框选中类型,获取相同类型的文章

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

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

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

为什么用get请求不用post请求

因为这个页面写了非常多的请求,如果用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() {
    //获取数据
    //高级查询
    //指定表
    o := orm.NewOrm()
    qs := o.QueryTable("Article") //queryseter
    var articles []models.Article
    //_, err := qs.All(&articles)
    //if err != nil {
    //	log.Printf("获取数据失败")
    //}
    //查询总记录数
    typeName := this.GetString("select")
    var count int64

    //每页显示几条数据
    pageSize := 5

    //获取页码
    pageIndex, err := this.GetInt("pageIndex")
    if err != nil {
        pageIndex = 1
    }
    //获取数据
    //作用就是获取数据库中部分数据,第一个参数:获取几条,第二个参数:从那条数据开始获取,返回值还是qs
    //起始位置计算
    start := (pageIndex - 1) * pageSize

    if typeName == "" {
        count, _ = qs.Count()
    } else {
        count, _ = qs.Limit(pageSize, start).RelatedSel("ArticleType").Filter("ArticleType__TypeName", typeName).Count()
    }
    //获取总页数
    pageCount := math.Ceil(float64(count) / float64(pageSize))

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

    //获取文章类型
    var types []models.ArticleType
    o.QueryTable("ArticleType").All(&types)
    this.Data["types"] = types

    //根据选中的类型查询相应类型的文章
    if typeName == "" {
        qs.Limit(pageSize, start).RelatedSel("ArticleType").All(&articles)
    } else {
        qs.Limit(pageSize, start).RelatedSel("ArticleType").Filter("ArticleType__TypeName", typeName).All(&articles)
    }
    //传递数据
    this.Data["count"] = count
    this.Data["pageIndex"] = pageIndex
    this.Data["pageCount"] = int(pageCount)
    this.Data["articles"] = articles
    this.TplName = "index.html"
}

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

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

//传递当下拉框选择的类型名给视图
this.Data["typeName"] = typeName

前端代码处理

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

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

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

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

还有一个问题就是

点击换页的时候会自动跳转到主界面

对index.html文件做修改

<ul class="pagenation">
  <li><a href="/showArticleList?pageIndex=1&select={{.typeName}}">首页</a></li>
  <li><a href="/showArticleList?pageIndex={{pre .pageIndex}}&select={{.typeName}}">上一页 </a> </li>
  <li> <a href="/showArticleList?pageIndex={{next .pageIndex .pageCount}}&select={{.typeName}}">下一页</a></li>
  <li><a href="/showArticleList?pageIndex={{.pageCount}}&select={{.typeName}}">末页</a></li>
  <li>共{{.count}}条记录/共{{.pageCount}}页/当前第{{.pageIndex}}页</li>
</ul>

问题便解决

10.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

数据存储位置

客户端

服务器

数据安全性(相比较而言)

生命周期

随着设置时间的结束,生命周期结束

当浏览器关闭的时候,生命周期结束

适用场景

对安全性要求不高的,需要存储时间较长的数据

安全性要求搞,不需要长期存储的数据

简单了解了这两个知识点之后,接下来实现项目剩余的四个功能。

10.1记住用户名

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

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

data := this.GetString("remember")
log.Printf(data)
if data == "on" {
    this.Ctx.SetCookie("userName", userName, 3600)
}else {
    this.Ctx.SetCookie("userName", userName, -1)
}

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

userName := this.Ctx.GetCookie("userName")
if userName == "" {
    this.Data["userName"] = ""
    this.Data["checked"] = ""
} else {
    this.Data["userName"] = userName
    this.Data["checked"] = "checked"
}

login.html视图中接收数据:

<form  class="login_form"  name = "login" method="post" action="/login">
  <h1 class="login_title">用户登录</h1>
  <input type="text"  class="input_txt" name = "userName" value="{{.userName}}">
  <input type="password" name = "password"  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就为选中状态

10.2登陆判断

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

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

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

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

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

//session判断
userName := this.GetSession("userName")
if userName == nil {
    this.Redirect("/login", 302)
    return
}

10.3退出登陆

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

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

<a href="/logout" class="logout fr">退 出</a>

接着我们在路由中指定请求对应的控制器和方法

beego.Router("/logout", &controllers.UserController{}, "get:Logout")

然后我们实现一个Logout函数,业务逻辑很简单,我们直接看代码

//退出登录
func (this *UserController) Logout()  {
    //删除session
    this.DelSession("userName")
    //重定向到登录页面
    this.Redirect("/login", 302)
}

10.4最近浏览

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

添加文章类型

首先进入文章闲情页面发现文章类型显示还没做修改

打开展示文章详情页面函数

此时可以换成高级查询了

o.QueryTable("Article").RelatedSel("ArticleType").Filter("Id", id).One(&article)

再修改视图文件即可

<div class="form_group">
  <label>文章类型:</label>
  <p class="detail">{{.article.ArticleType.TypeName}}</p>
</div>

添加浏览信息

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

//多对多插入浏览记录
m2m := o.QueryM2M(&article, "Users")
userName := this.GetSession("userName")
if userName == nil {
    this.Redirect("/login", 302)
    return
}
var user models.User
user.Name = userName.(string)
o.Read(&user, "Name")
//插入操作
m2m.Add(user)

显示浏览信息

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

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

o.LoadRelated(&article, "Users")

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

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

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

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

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

this.Data["users"] = users

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

再修改视图文件

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

查看页面是否成功

10.5删除分类

类型是与多表操作有关的,删除效果和单表的文章不一样

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

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

请求

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

那我们给这个<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_add"`
    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:"rel(m2m)"`                         //设置多对多关系
}

11.Beego总结

📎beego的快速体验.xmind

📎Beego框架.xmind

项目文件:

📎myapp.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值