GO语言开发天天生鲜项目第四天 商品后台管理

商品模块

一:商品后台管理

我们添加商品的后台和我们前面做的案例后台管理很类似,这里我们就直接拿我们前面的案例来改一下,直接当我们商品后台管理

1.代码迁移

  • 先把我们案例文件夹下的views文件夹删除。

  • 把课堂资料中的views.zip解压到我们的classOne下面

    GO语言开发天天生鲜项目第四天 商品后台管理_第1张图片

  • 在GoLand中打开项目

  • 把项目中model.go文件里面的代码改为我们天天生鲜的model.go内容(修改数据库)

  • 修改user.go和article.go中报红的地方

    • 控制页面显示的函数留下修改,处理业务的函数都注释掉(因为需要处理的业务可能会发生变化,所以先注释掉)

      GO语言开发天天生鲜项目第四天 商品后台管理_第2张图片

    • 需要做的修改就是把原来表的名字和字段修改为新的表的名字和字段

      GO语言开发天天生鲜项目第四天 商品后台管理_第3张图片

2.登陆和注册

注册业务没有什么改变,需要注意的是一些字段改变的不一样啦,还有就是我们在后台注册和在天天生鲜界面注册的权限不一致。

GO语言开发天天生鲜项目第四天 商品后台管理_第4张图片

登陆的时候判断也基本不变,需要注意的是要多添加一块权限验证:

//5.判断是否权限登陆后台管理,如果不等于1说明是普通用户,不能登陆后台
		if user.Power != 1{
			beego.Info("没有权限登陆后台")
			this.TplName = "login.html"
			return
		}

登陆成功之后跳转到列表页:

GO语言开发天天生鲜项目第四天 商品后台管理_第5张图片

进入到后台之后我们先来实现增加类型业务。

3.添加类型

添加类型界面分两块,一块是类型信息的展示,一块是类型的添加

GO语言开发天天生鲜项目第四天 商品后台管理_第6张图片

3.1.添加类型

3.1.1获取数据

我们获取的时候发现,添加类型需要上传两张图片,上传图片功能,我们在添加文章函数中用过。但是如果每有一处上传图片的业务就得写一次这个代码,代码就重复了,针对重复代码,我们可以抽离出来一个上传图片的函数

3.1.2抽离上传图片函数

把添加文章中关于上传文件的代码抽离出来,先不指定参数和返回值:

//抽象上传图片函数
func UploadImage(){
	//1.那数据
	//那标题
	f,h,err:=this.GetFile(filePath)

	defer f.Close()
	//上传文件处理
	//1.判断文件格式
	ext := path.Ext(h.Filename)
	if ext != ".jpg" && ext != ".png"&&ext != ".jpeg"{
		beego.Info("上传文件格式不正确")
		return 
	}

	//2.文件大小
	if h.Size>5000000{
		beego.Info("文件太大,不允许上传")
		return 
	}

	//3.不能重名
	fileName := time.Now().Format("2006-01-02 15:04:05")


	err2:=this.SaveToFile(filePath,"./static/img/"+fileName+ext)
	if err != nil{
		beego.Info("上传文件失败")
		return 
	}

	if err2 != nil{
		beego.Info("上传文件失败",err2)
		return 
	}
}

然后发现,函数功能里面需要两个参数,一个是控制器本身,因为很多beego的函数需要有beego的控制器才能调用。另外一个是上传文件时,需要提供的name属性。因为不同的文件上传对应的name值也可能不一样。多以我们给这个函数添加两个参数。上传完文件之后,我们还需要获取到文件的 存储路径,所以需要函数返回一个路径,这里我们给函数加一个string类型的返回值。函数完整代码如下:

func UploadImage(this*beego.Controller,filePath string)string{
	//1.那数据
	//那标题
	f,h,err:=this.GetFile(filePath)

	defer f.Close()
	//上传文件处理
	//1.判断文件格式
	ext := path.Ext(h.Filename)
	if ext != ".jpg" && ext != ".png"&&ext != ".jpeg"{
		beego.Info("上传文件格式不正确")
		return ""
	}

	//2.文件大小
	if h.Size>5000000{
		beego.Info("文件太大,不允许上传")
		return ""
	}

	//3.不能重名
	fileName := time.Now().Format("2006-01-02 15:04:05")


	err2:=this.SaveToFile(filePath,"./static/img/"+fileName+ext)
	if err != nil{
		beego.Info("上传文件失败")
		return ""
	}

	if err2 != nil{
		beego.Info("上传文件失败",err2)
		return ""
	}
	return "/static/img/"+fileName+ext
}

有了上传图片函数之后,添加类型获取数据代码如下:

//1.获取数据
	typename:=this.GetString("typeName")
	logoImage := UploadImage(&this.Controller,"uploadlogo")
	typeImage :=UploadImage(&this.Controller,"uploadTypeImage")
3.1.2.数据校验

对获取的数据判断一下,看是否为空:

//判断数据
	if typename == ""||logoImage == "" || typeImage == ""{
		beego.Info("添加类型数据为空")
		return
	}

#####3.1.3.插入数据库

数据没问题的话,就获取一个GoodsType对象并给它赋值,然后插入数据库:

//执行插入操作
	o := orm.NewOrm()
	var goodsType models.GoodsType
	goodsType.Name = typename
	goodsType.Image = typeImage
	goodsType.Logo = logoImage
	_,err:=o.Insert(&goodsType)
	if err != nil{
		beego.Info("插入失败")
		return
	}
3.1.4.返回视图

业务执行完之后还是返回到本页面,看一下是否添加成功

this.Redirect("/Article/AddArticleType",302)

3.2.利用fastDFS存储图片

3.2.1什么是FastDFS

FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制, 充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

优点:

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文 件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些 方法找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务 器。

Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上, Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将 storage 称为存储服务器。

GO语言开发天天生鲜项目第四天 商品后台管理_第7张图片

服务端两个角色:

Tracker:管理集群,tracker 也可以实现集群。每个 tracker 节点地位平等。收集 Storage 集群的状态。

Storage:实际保存文件

Storage 分为多个组,每个组之间保存的文件是不同的。每 个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有 主从的概念。

3.2.2文件上传流程

GO语言开发天天生鲜项目第四天 商品后台管理_第8张图片

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文 件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

1538157690533

组名: 文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回, 需要客户端自行保存。

虚拟磁盘路径: storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了 store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

**数据两级目录 **:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据 文件。

文件名 :与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储 服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

3.2.3文件下载流程

GO语言开发天天生鲜项目第四天 商品后台管理_第9张图片

3.2.4简易FastDFS架构

GO语言开发天天生鲜项目第四天 商品后台管理_第10张图片

3.2.5FastDFS安装
3.2.5.1安装FastDFS依赖包
  1. 解压缩libfastcommon-master.zip
  2. 进入到libfastcommon-master的目录中
  3. 执行**./make.sh**
  4. 执行sudo ./make.sh install
3.2.5.2安装FastDFS
  1. 解压缩fastdfs-master.zip
  2. 进入到 fastdfs-master目录中
  3. 执行 ./make.sh
  4. 执行 sudo ./make.sh install
3.2.5.3配置跟踪服务器tracker
  1. sudo cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
    
  2. 在/home/itcast/目录中创建目录 fastdfs/tracker

    mkdir –p /home/itcast/fastdfs/tracker
    
  3. 编辑/etc/fdfs/tracker.conf配置文件 sudo vim /etc/fdfs/tracker.conf

​ 修改 base_path=/home/itcast/fastdfs/tracker

3.2.5.4配置存储服务器storage
  1. sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
    
  2. 在/home/itcast/fastdfs/ 目录中创建目录 storage

    mkdir –p /itcast/python/fastdfs/storage
    
  3. 编辑/etc/fdfs/storage.conf配置文件 sudo vim /etc/fdfs/storage.conf

    修改内容:

    base_path=/home/itcast/fastdfs/storage
    store_path0=/home/itcast/fastdfs/storage
    tracker_server=自己ubuntu虚拟机的ip地址:22122
    
3.2.5.5启动tracker和storage

进入到/etc/fdfs/下面执行以下两条指令

sudo  fdfs_trackerd  /etc/fdfs/tracker.conf
sudo fdfs_storaged  /etc/fdfs/storage.conf
3.2.5.6测试是否安装成功
  1. **sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf **
  2. 编辑/etc/fdfs/client.conf配置文件 sudo vim /etc/fdfs/client.conf

修改内容:

base_path=/home/python/fastdfs/tracker
tracker_server=自己ubuntu虚拟机的ip地址:22122
  1. 上传文件测试(fastDHT)

    sudo fdfs_upload_file /etc/fdfs/client.conf 要上传的图片文件

    如果返回类似**group1/M00/00/00/rBIK6VcaP0aARXXvAAHrUgHEviQ394.jpg **的文件id则说明文件上传成功

3.2.5.7安装fastdfs-nginx-module
  1. 解压缩 nginx-1.8.1.tar.gz

  2. 解压缩 fastdfs-nginx-module-master.zip

  3. 进入nginx-1.8.1目录中

  4. 执行

    sudo ./configure  --prefix=/usr/local/nginx/ --add-module=fastdfs-nginx-module-master解压后的目录的绝对路径/src
    

    注意:这时候会报一个错,说没有PCRE库

    GO语言开发天天生鲜项目第四天 商品后台管理_第11张图片

    下载缺少的库

    sudo apt-get install libpcre3 libpcre3-dev
    
    • 首先你需要去更换源,因为ubuntu自带的源没有这个库

    • 更换下载源为阿里的源

    • 先把原来的源文件备份

      sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
      
    • 编辑源文件

      sudo vim /etc/apt/sources.list
      

      把原来的内容全部删掉,粘贴一下内容:

      deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
       
      deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
       
      deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
       
      deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
       
      deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
      

      更换完源之后执行

      sudo apt-get  update
      sudo apt-get install libpcre3 libpcre3-dev
      

      然后进入nginx-1.8.1目录中,再次执行:

      sudo ./configure  --prefix=/usr/local/nginx/ --add-module=fastdfs-nginx-module-master解压后的目录的绝对路径/src
      

      这时候还会报一个错(错误还真多),错误原因是因为nginx编译的时候把警告当错误处理,事实上这个警告并不影响(程序员忽略警告):

      GO语言开发天天生鲜项目第四天 商品后台管理_第12张图片

      解决方法:

      找到objs目录下的Makefile

      vim Makefile

      删掉里面的-Werror(如果没有修改权限,修改一下这个文件的权限,chmod 777 Makefile)

      GO语言开发天天生鲜项目第四天 商品后台管理_第13张图片

      然后回到nginx-1.8.1目录中,再次执行:

      sudo ./configure  --prefix=/usr/local/nginx/ --add-module=fastdfs-nginx-module-master解压后的目录的绝对路径/src
      

      执行完成后执行sudo make

      执行sudo make install

  5. sudo cp fastdfs-nginx-module-master解压后的目录中src下mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf

  6. sudo vim /etc/fdfs/mod_fastdfs.conf

    修改内容:

    connect_timeout=10
    tracker_server=自己ubuntu虚拟机的ip地址:22122
    url_have_group_name=true
    store_path0=/home/python/fastdfs/storage
    
  7. sudo cp 解压缩的fastdfs-master目录中的conf中的http.conf /etc/fdfs/http.conf

  8. sudo cp 解压缩的fastdfs-master目录中的mime.types /etc/fdfs/mime.types

  9. sudo vim /usr/local/nginx/conf/nginx.conf

    在http部分中添加配置信息如下:

    server {
                listen       8888;
                server_name  localhost;
                location ~/group[0-9]/ {
                    ngx_fastdfs_module;
                }
                error_page   500 502 503 504  /50x.html;
                location = /50x.html {
                root   html;
                }
            }
    
    
  10. 启动nginx

sudo /usr/local/nginx/sbin/nginx

3.2.6使用go客户端上传文件测试
  • 下载包

    go get github.com/weilaihui/fdfs_client
    

    这时候会报一个错:

    GO语言开发天天生鲜项目第四天 商品后台管理_第14张图片

    这是因为我们的网络有防火墙,不能直接去google下载相应的包,所以就失败啦

    解决办法:

    • ~/workspace/go/src目录下面创建一个golang.org/x目录

      cd  ~/workspace/go/src
      mkdir -p golang.org/x
      
    • 进入golang.org/x下载两个包

      cd golang.org/x
      git clone https://github.com/golang/crypto.git
      git clone https://github.com/golang/sys.git
      
    • 然后再执行最初的下载命令

      go get github.com/weilaihui/fdfs_client
      
  • go操作fastDFS的方法

    • 先导包,把我们下载的包导入

      import "github.com/weilaihui/fdfs_client"
      
    • 导包之后,我们需要指定配置文件生成客户端对象

      client,_:=fdfs_client.NewFdfsClient("/etc/fdfs/client.conf")
      
    • 接着我们就可以通过client对象执行文件上传,上传有两种方法,一种是通过文件名,一种是通过字节流

      • 通过文件名上传**UploadByFilename **,参数是文件名(必须通过文件名能找到要上传的文件),返回值是fastDFS定义的一个结构体,包含组名和文件ID两部分内容

        fdfsresponse,err := client.UploadByFilename("flieName")
        
      • 通过字节流上传UploadByBuffer,参数是字节数组和文件后缀,返回值和通过文件名上传一样。

        fdfsresponse,err := client.UploadByBuffer(fileBuffer,ext)
        

3.3.改用fastDFS上传文件

​ 我们以前保存视图传递过来的文件方法是先用GetFile()获取文件相关信息,然后再用SaveToFile()把文件保存到 文件夹下面。以前保存图片的代码如下:

func UploadImage(this*beego.Controller,filePath string)string{
	//1.那数据
	//那标题
	f,h,err:=this.GetFile(filePath)

	defer f.Close()
	//上传文件处理
	//1.判断文件格式
	ext := path.Ext(h.Filename)
	if ext != ".jpg" && ext != ".png"&&ext != ".jpeg"{
		beego.Info("上传文件格式不正确")
		return ""
	}

	//2.文件大小
	if h.Size>5000000{
		beego.Info("文件太大,不允许上传")
		return ""
	}

	//3.不能重名
	fileName := time.Now().Format("2006-01-02 15:04:05")


	err2:=this.SaveToFile(filePath,"./static/img/"+fileName+ext)
	if err != nil{
		beego.Info("上传文件失败")
		return ""
	}

	if err2 != nil{
		beego.Info("上传文件失败",err2)
		return ""
	}
	return "/static/img/"+fileName+ext
}

我们现在要用fastDFS来存储图片,第一步也是先用GetFile拿到文件,但是第二步,我们用UploadByBuffer()把静态文件存到我们fastDFS文件系统中。

//先导包
import "github.com/weilaihui/fdfs_client"

//通过GetFile获取文件信息
f,h,err := this.GetFile(filePath)
defer f.Close()
//然后对上传的文件进行格式和大小判断
//1.判断文件格式
ext := path.Ext(h.Filename)
if ext != ".jpg" && ext != ".png"&&ext != ".jpeg"{
	beego.Info("上传文件格式不正确")
	return ""
}

//2.文件大小
if h.Size>5000000{
	beego.Info("文件太大,不允许上传")
	return ""
}
//3.上传文件
//先获取一个[]byte
fileBuffer := make([]byte,h.Size)
//把文件数据读入到fileBuffer中
f.Read(fileBuffer)
//获取client对象
client := fdfs_client.NewFdfsClient("/etc/fdfs/client.conf")
//上传
fdfsresponse,_:=client.UploadByBuffer(fileBuffer,ext)
//返回文件ID
return fdfsresponse.RemoteFileId

上传图片方式修改完成之后我们添加类型就业务就结束了。

3.4 类型展示

类型展示和案例中的展示业务一样,只是一些表名和字段名需要简单修改,代码如下:

//1.读取类型表,显示数据
	o := orm.NewOrm()
	var goodsTypes[]models.GoodsType
	//查询
	_,err:=o.QueryTable("GoodsType").All(&goodsTypes)
	if err != nil{
		beego.Info("查询类型错误")
	}


	this.Data["title"] = "添加类型"
	this.Data["goodsTypes"] = goodsTypes
	this.Layout = "layout.html"
	this.TplName = "addType.html"

到这里,我们添加类型界面就算完成了。但是我们后面还需要添加goodsSPU,添加商品,删除商品,修改商品等功能和我们以前写的业务基本一样,老师就不给你们写了,你们自己去实现一下相关业务。

有的学生觉得我们这块和以前写的一样,觉得没有意思,我们这里就直接导入数据,不再一步步的添加数据

3.5导入商品有关数据

3.5.1导入数据库数据

​ 导入数据库数据,老师给你们的资料中,有一个文件是 dailyfresh.sql,就是我以前导入的数据,你们可以把这个sql语句导入到你们的数据库中,直接拿来使用。

  • 先进入数据库中

    mysql -uroot -p123456
    
  • 选中项目中用到的数据库

    use dailyfresh
    
  • 导入文件(保证dailyfresh.sql文件在你当前目录下面)

    source dailyfresh.sql
    
  • 查看数据是否导入成功

    select * from goods_type;
    
3.5.2导入图片数据

​ 导入数据库数据之后我们还有很多图片内容是存在fastDFS中的,也需要我们手动导入

  • 删除 ~/fdfs/storage/data/00目录下的00文件夹

  • 把课堂资料中的00.zip拷贝到我们存放图片的路径下面 ~/fdfs/storage/data/00

    GO语言开发天天生鲜项目第四天 商品后台管理_第15张图片

  • 解压00.zip到当前目录

这时候我们就把数据全部导入到了我们的开发环境当中。

二.商品信息展示

前面我们导入了全部商品有关的数据,接着我们就来实现,商品展示有关的内容。

1.首页内容的展示

首先我们来看一下首页的页面。

GO语言开发天天生鲜项目第四天 商品后台管理_第16张图片

商品页面分为如图所示的四部分,那我们就来分别获取这四部分的值,仍旧按照我们的四步骤来走。

请求

这里我们设定首页的请求路径为\

路由

然后我们去router.go中修改相应的路由适配

 //显示主页面
 beego.Router("/",&controllers.GoodsController{},"get:ShowIndex")

**控制器 **

这里我们指定首页对应GoodsController控制器,获取首页的方法为ShowIndex,接着我们就来实现这个方法。

  • 获取类型

    //查询商品类型
    	var goodsTypes []models.GoodsType
    	o.QueryTable("GoodsType").All(&goodsTypes)
    	this.Data["types"] = goodsTypes
    
  • 获取轮播商品数据

    //查询轮播商品图片
    	var goodsBanner []models.IndexGoodsBanner
    	o.QueryTable("IndexGoodsBanner").OrderBy("Index").All(&goodsBanner)
    	this.Data["goodsBanners"] = goodsBanner
    
  • 查询促销商品数据

    //查询促销商品
    	var promotionBanner []models.IndexPromotionBanner
    	o.QueryTable("IndexPromotionBanner").OrderBy("Index").All(&promotionBanner)
    	this.Data["proBanner"] = promotionBanner
    
  • 查询首页商品

    GO语言开发天天生鲜项目第四天 商品后台管理_第17张图片

    这个业务比较复杂,我们观察页面发现我们查询的时候要把类型对象和相应的商品对象切片放在一起,那我们用什么容器呢?分析如下图:

    GO语言开发天天生鲜项目第四天 商品后台管理_第18张图片

    分析之后我们发现,只能用interface这种类型,还需要给不同类型的数据加一个key值,所以我们需要用map[string]interface{},这是一个 类型的内容,那所有类型的内容,我们应该给这个类型放到切片中,所以我们这里用**[]map[string]interface{}**这种类型来存储首页商品。

    确定了存储容器之后,我们开始查询数据来存储,首先是把商品类型存储到我们的容器当中:

    goods := make([]map[string]interface{},len(goodsTypes))
    	for index,goodsType := range goodsTypes{
    		temp := make(map[string]interface{})
    		temp["type"] = goodsType
    		goods[index] = temp
    	}
    

    接着把商品存储到我们的容器中,注意这里是分为图片商品和文字商品,需要分开来存储:

    var goodsImage []models.IndexTypeGoodsBanner
    var goodsText []models.IndexTypeGoodsBanner
    for _,temp := range goods{
      o.QueryTable("IndexTypeGoodsBanner").RelatedSel("GoodsSKU","GoodsType").Filter("GoodsType",temp["type"]).Filter("Display_Type",1).OrderBy("Index").All(&goodsImage)
      o.QueryTable("IndexTypeGoodsBanner").RelatedSel("GoodsSKU","GoodsType").Filter("GoodsType",temp["type"]).Filter("Display_Type",0).OrderBy("Index").All(&goodsText)
        
    temp["goodsText"] = goodsText
    temp["goodsImage"] = goodsImage
    }
    

    然后再把数据传递给视图

    this.Data["goods"] = goods
    

**视图 **

​ 获取完数据就需要在页面里面显示,这里注意,我们获取的图片数据是FastDFS返回的ID值,这里我们访问的是通过nginx获取的图片数据,所以需要在图片地址上需要加上相应的ip和端口号,代码如下:

	<div class="center_con clearfix">
		<ul class="subnav fl">
            
			{{range .types}}
				<li><a href="" class="{{.Logo}}">{{.Name}}a>li>
			{{end}}
		ul>
		<div class="slide fl">
			<ul class="slide_pics">
                
				{{range .goodsBanners}}
					<li><img src="http://192.168.110.81:8888/{{.Image}}" alt="幻灯片">li>
				{{end}}
			ul>
			<div class="prev">div>
			<div class="next">div>
			<ul class="points">ul>
		div>
		<div class="adv fl">
            
			{{range .proBanner}}
				<a href="{{.Url}}"><img src="http://192.168.110.81:8888/{{.Image}}">a>
			{{end}}
		div>
	div>

{{range .goods}}
	<div class="list_model">
		<div class="list_title clearfix">
			<h3 class="fl" id="model01">{{.type.Name}}h3>
			<div class="subtitle fl">
				<span>|span>
                
				{{range .goodsText}}
				<a href="">{{.GoodsSKU.Name}}a>
				{{end}}
			div>
			<a href="" class="goods_more fr" id="fruit_more">查看更多 >a>
		div>

		<div class="goods_con clearfix">
			<div class="goods_banner fl"><img src="http://192.168.110.81:8888/{{.type.Image}}">div>
			<ul class="goods_list fl">
                
				{{range .goodsImage}}
				<li>
					<h4><a href="">{{.GoodsSKU.Name}}a>h4>
					<a href=""><img src="http://192.168.110.81:8888/{{.GoodsSKU.Image}}">a>
					<div class="prize">¥ {{.GoodsSKU.Price}}div>
				li>
				{{end}}
			ul>
		div>
	div>
{{end}}

2.商品详情页的展示

首页展示之后,我们接着来实现商品详情页的展示!仍然是我们你的四步骤

请求

商品详情页的请求应该在首页展示的相应页面添加,不管是文字商品还是图片商品都应该添加。商品详情和我们实现案例中文章详情一样,都需要传递相应的Id,所以我们设计商品详情的路由为

/goodsDetail?id={{.GoodsSKU.Id}}

路由

然后到router.go中添加对应的路由

beego.Router("/goodsDetail",&controllers.GoodsController{},"get:ShowDetail")

控制器

接着我们来实现ShowDetail方法。仍然是我们的四步骤。

  • 获取数据

    id,err:= this.GetInt("id")
    
  • 校验数据

    if err != nil{
    	beego.Error("获取数据不存在")
    	this.Redirect("/",302)
    	return
    }
    
  • 数据处理

    首先我们看一下,我们这个页面:

    GO语言开发天天生鲜项目第四天 商品后台管理_第19张图片

    由页面可知,我们要获取类型数据和商品数据。代码如下:

    //获取商品类型
    	var goodsTypes []models.GoodsType
    	o.QueryTable("GoodsType").All(&goodsTypes)
    	this.Data["types"] = goodsTypes
    //获取商品详情
    	var goods models.GoodsSKU
    	o.QueryTable("GoodsSKU").RelatedSel("GoodsType","Goods").Filter("Id",id).One(&goods)
    	this.Data["goods"] = goods
    

视图

我们把获取到的商品详细信息添加在视图中显示,代码如下:

<div class="goods_detail_con clearfix">
	<div class="goods_detail_pic fl"><img src="http://192.168.110.81:8888/{{.goods.Image}}">div>

	<div class="goods_detail_list fr">
		<h3>{{.goods.Name}}h3>
		<p>{{.goods.Desc}}p>
		<div class="prize_bar">
			<span class="show_pirze">¥<em>{{.goods.Price}}em>span>
			<span class="show_unit">单  位:{{.goods.Unite}}span>
		div>
		<div class="goods_num clearfix">
			<div class="num_name fl">数 量:div>
			<div class="num_add fl">
				<input type="text" class="num_show fl" value="1" name="goodsCount">
				<a href="javascript:;" class="add fr">+a>
				<a href="javascript:;" class="minus fr">-a>
			div> 
		div>
		<div class="total">总价:<em>{{.goods.Price}}em>div>
		<div class="operate_btn">
			<a href="javascript:;" class="buy_btn">立即购买a>
			<a href="javascript:;" skuid="{{.goods.Id}}" class="add_cart" id="add_cart">加入购物车a>
		div>
	div>
div>

历史记录的添加和显示

  • 获取详情的时候我们需要添加历史浏览记录,我们介绍项目的时候就说过,我们的历史浏览记录存储在redis中。这里我们就需要做一个历史浏览记录的存储。在实现相关代码之前,我们先来分析一下,我们如何去存储 历史浏览记录。这里有以下几个问题。

    • 1.什么时候添加历史浏览记录

      登陆的情况下查看商品详情的时候添加

    • 2.什么时候获取历史浏览记录

      在用户中心页获取历史浏览记录

    • 3.用什么格式来存储用户浏览记录

      这里我们要考虑一下历史浏览记录都存储哪些内容,历史浏览记录需要存储哪个用户浏览了哪些内容,而且还有先后顺序,所以要存储用户,商品内容,而且用户和商品是对应存储,并且有顺序,这里我们用redis数据库中的list来存储。存入的key值设定为history_用户id,value值为商品id,存储代码如下:

      //添加历史浏览记录,需要先查询有没有登陆,只有登陆之后可以添加历史浏览记录
      	userName := this.GetSession("userName")
      	if userName != nil{
      		//查询用户信息
      		var user models.User
      		user.Name = userName.(string)
      		o.Read(&user,"Name")
      		conn,_:=redis.Dial("tcp",":6379")
      		//插入历史纪录
      		reply,err:=conn.Do("lpush","history"+strconv.Itoa(user.Id),id)
      		reply,_ = redis.Bool(reply,err)
      		if reply == false{
      			beego.Info("插入浏览数据错误")
      		}
      	}
      

      这时候需要注意,当用户多次浏览某个商品的时候,我们添加历史浏览记录只添加一条,所以在添加历史浏览记录之前需要把原来的记录给清空。修改之后的代码如下:

      //添加历史浏览记录,需要先查询有没有登陆,只有登陆之后可以添加历史浏览记录
      	userName := this.GetSession("userName")
      	if userName != nil{
      		//查询用户信息
      		var user models.User
      		user.Name = userName.(string)
      		o.Read(&user,"Name")
      		conn,_:=redis.Dial("tcp",":6379")
      		//先清空以前的记录1
      		reply,err:=conn.Do("lrem","history"+strconv.Itoa(user.Id),0,id)
      		reply,_ = redis.Bool(reply,err)
      		if reply == false{
      			beego.Info("插入浏览数据错误")
      		}
      		//插入历史纪录
      		conn.Do("lpush","history"+strconv.Itoa(user.Id),id)
      	}
      

      添加完历史浏览记录,我们在用户中心页获取一下历史浏览记录。这里需要说明的是,我们只获取前五条历史浏览记录。代码如下:

      //获取历史浏览记录
      	var goods []models.GoodsSKU
      	conn,_ :=redis.Dial("tcp",":6379")
      	reply,err := conn.Do("lrange","history"+strconv.Itoa(user.Id),0,4)
      	replyInts,_ := redis.Ints(reply,err)
      	for _,val := range replyInts{
      		var temp models.GoodsSKU
      		o.QueryTable("GoodsSKU").Filter("Id",val).One(&temp)
      		goods = append(goods, temp)
      	}
      	this.Data["goods"] = goods
      

      显示历史纪录的视图代码如下:

      {{if .goods}}
          {{range .goods}}
               <li>
                   <a href="detail.html"><img src="http://192.168.110.81:8888/{{.Image}}">a>
                   <h4><a href="detail.html">{{.Name}}a>h4>
                   <div class="operate">
                   	<span class="prize">¥{{.Price}}span>
                   	<span class="unit">{{.Price}}/{{.Unite}}span>
                   	<a href="#" class="add_goods" title="加入购物车">a>
                   div>
               li>
          {{end}}
      {{else}}
          <li>无历史浏览记录li>
      {{end}}
      

3.列表页商品内容展示

请求

列表页展示的是相应类型的所有数据,所以请求应该是点击类型的时候跳转的页面,需要传递类型ID,所以我们要在类型超链接上添加请求路径为/goodsList?typeId=1

路由

有了请求之后需要我们在路由文件中添加相对应的控制器和方法,代码如下:

//列表页展示
beego.Router("/goodsList",&controllers.GoodsController{},"get:ShowGoodsList")

控制器

接着我们来实现ShowGoodsList方法.

  • 获取数据

    首先我们要获取到路由传递过来的类型ID

    //获取类型id
    typeId,err := this.GetInt("typeId")
    
  • 校验数据

    对获取的数据进行校验

    //校验数据
    if err != nil{
    	beego.Info("获取类型ID错误")
    	this.Redirect("/",302)
    	return 
    }
    
  • 处理数据

    我们 这个页面主要是去查询相关的内容

    GO语言开发天天生鲜项目第四天 商品后台管理_第20张图片

    由图可知,我们需要获取三部分数据

    • 类型数据

      获取类型数据业务就比较简单,直接把所有类型数据从数据库中获取到。代码如下:

      var types []models.GoodsType
      o.QueryTable("GoodsType").All(&types)
      this.Data["types"] = types
      
    • 获取当前类型的商品数据

      //获取当前类型的商品
      var goodsSKus []models.GoodsSKU	o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).All(&goodsSKus)
      this.Data["goods"] = goodsSKus
      
    • 获取新品数据

      我们这里获取的是同类型,时间靠前的两个商品数据,代码如下:

      //获取两个新品
      var goodsNew []models.GoodsSKU
      o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).OrderBy("Time").Limit(2,0).All(&goodsNew)
      this.Data["goodsNew"] = goodsNew
      

视图

后台获取的数据,要在视图中循环显示:

<div class="navbar_con">
		<div class="navbar clearfix">
			<div class="subnav_con fl">
				<h1>全部商品分类h1>	
				<span>span>			
				<ul class="subnav">
					{{range .types}}
						<li><a href="/goodsList?id={{.Id}}" class="{{.Logo}}">{{.Name}}a>li>
					{{end}}
				ul>
			div>
			<ul class="navlist fl">
				<li><a href="">首页a>li>
				<li class="interval">|li>
				<li><a href="">手机生鲜a>li>
				<li class="interval">|li>
				<li><a href="">抽奖a>li>
			ul>
		div>
	div>

	<div class="breadcrumb">
		<a href="#">全部分类a>
		<span>>span>
		<a href="#">新鲜水果a>
	div>

	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>新品推荐h3>
				<ul>
					{{range .goodsNew}}
						<li>
							<a href="/goodsDetail?id={{.Id}}"><img src="http://192.168.110.81:8888/{{.Image}}">a>
							<h4><a href="/goodsDetail?id={{.Id}}">{{.Name}}a>h4>
							<div class="prize">¥{{.Price}}div>
						li>
					{{end}}
				ul>
			div>
		div>

		<div class="r_wrap fr clearfix">
			<div class="sort_bar">
					<a href="/goodsList?id={{.typeId}}" class="active">默认a>
					<a href="#">价格a>
					<a href="#">人气a>
			div>

			<ul class="goods_type_list clearfix">
				{{range .goods}}
					<li>
						<a href="/goodsDetail?id={{.Id}}"><img src="http://192.168.110.81:8888/{{.Image}}">a>
						<h4><a href="/goodsDetail?id={{.Id}}">{{.Name}}a>h4>
						<div class="operate">
							<span class="prize">¥{{.Price}}span>
							<span class="unit">{{.Price}}/{{.Unite}}span>
							<a href="#" class="add_goods" title="加入购物车">a>
						div>
					li>
				{{end}}
			ul>

			<div class="pagenation">
				<a href="#">上一页a>
						<a href="#" class="active">1a>
				<a href="#">下一页>a>
			div>
		div>
	div>

分页处理

我们这里实现的分页是如下的效果:

GO语言开发天天生鲜项目第四天 商品后台管理_第21张图片

所以这里我们还需要获取相应页码,而且这个页码数,无论页码怎么变化,都显示的五条,所以这里获取五条页码就成了实现分页重要的一个环节。

这个可以分四种情况分析。
第一种:总页码数不到五页

这种情况下,要显示所有页码

第二种:显示的是前三页的内容

这种显示的都是1 2 3 4 5

第三种:显示的是最后三页的内容

显示的都是后五页的页码

第四种:处于中间的页码

分析出来的代码如下:

if pageCount < 5{
		pageIndexBuffer = make([]int,pageCount)
		for index,_ := range  pageIndexBuffer{
			pageIndexBuffer[index] = index + 1
		}
	}else if pageIndex < 3  {
		pageIndexBuffer = make([]int,5)
		for index,_ := range pageIndexBuffer{
			pageIndexBuffer[index] = index + 1
		}
	}else if pageIndex >= pageCount -3{
		pageIndexBuffer = make([]int,page)
		for index,_ := range  pageIndexBuffer{
			pageIndexBuffer[index] = pageCount - 5 + index
		}
	}else {
		pageIndexBuffer = make([]int, 5)
		for index,_ := range pageIndexBuffer{
			pageIndexBuffer[index] = pageIndex - 3 + index
		}
	}

由代码分析可知,这里我们需要获取总页码说pageCount,需要获取当前页码pageIndex。那我们需要先获取pageIndex和pageCount。

获取总页码和当前页码

获取总页码和当前页码的业务逻辑和以前我们获取总页码和当前页码的逻辑一样,代码如下:

//分页处理
	//指定每页显示多少数据
	pageSize := 2
	pageIndex ,err:=this.GetInt("pageIndex")
	if err !=nil{
		pageIndex = 1
	}
	start := pageSize * (pageIndex - 1)
	//处理页码
	count,_ := o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).Count()

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

获取显示的页码

这里我们可以封装一个获取相应页码的函数,需要把总页码,当前页码,和要显示多少个页码当参数传递,代码如下:

//指定显示多少个页码
page := 5
//函数定义如下

//分页助手函数
func PageTool(pageCount int,page int,pageIndex int)map[string]interface{}{
	//获取应该显示的页码
	var pageIndexBuffer []int
	if pageCount < page{
		pageIndexBuffer = make([]int,pageCount)
		for index,_ := range  pageIndexBuffer{
			pageIndexBuffer[index] = index + 1
		}
	}else if pageIndex < ( page + 1)/2  {
		pageIndexBuffer = make([]int,page)
		for index,_ := range pageIndexBuffer{
			pageIndexBuffer[index] = index + 1
		}
	}else if pageIndex >= pageCount -( page + 1)/2{
		pageIndexBuffer = make([]int,page)
		for index,_ := range  pageIndexBuffer{
			pageIndexBuffer[index] = pageCount - page + index
		}
	}else {
		pageIndexBuffer = make([]int, page)
		for index,_ := range pageIndexBuffer{
			pageIndexBuffer[index] = pageIndex - (page - 1)/2 + index
		}
	}

	//上一页页码
	pagePre := pageIndex - 1
	if pageIndex == 1{
		pagePre = 1
	}
	pageNext := pageIndex + 1
	if pageIndex == pageCount{
		pageNext = pageIndex
	}
	//把数据返回
	pageData := make(map[string]interface{})
	pageData["pagePre"] = pagePre
	pageData["pageNext"] = pageNext
	pageData["pageIndex"] = pageIndexBuffer

	return pageData
}

注意:这里上一页和下一页的处理也放到这个函数中实现

获取到数据之后,我们要把相应的数据传递给视图:

//把数据传递给视图
	this.Data["pagePre"] = pageData["pagePre"]
	this.Data["pageNext"] = pageData["pageNext"]
	this.Data["pages"] = pageData["pageIndex"].([]int)
	this.Data["pageIndex"] = pageIndex

在视图中显示:

<a href="/goodsList?pageIndex={{.pagePre}}">上一页a>
{{range $index,$val := .pages}}
	{{if compare $.pageIndex $val}}
		<a href="/goodsList?pageIndex={{$val}}" class="active">{{$val}}a>
	{{else}}
		<a href="/goodsList?pageIndex={{$val}}">{{$val}}a>
	{{end}}
{{end}}
<a href="/goodsList?pageIndex={{.pageNext}}">下一页>a>

感觉好像成功了,有没有问题呢?我们接着往下看

获取分页数据

这时候我们可以去获取分页的数据了,获取分页的数据依旧用高级查询中的Limit函数,代码如下:

o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).Limit(pageSize,start).All(&goodsSKus)

注意:这时候你会遇见一个问题,当直接访问列表页的时候,数据正确,但是点击上一页,下一页的时候,你会发现,获取数据不正确。什么原因呢?其实根据错误提示也能发现,我们点击上一页下一页以及相应页码的时候,并没有传递typeId值过来,所以对于后台来说,在新的请求中并不知道这次你要获取哪个类型的数据。这时候我们需要在上一次的访问中把typeId传递给视图。然后在分页的标签传递回来。具体代码实现如下:

先把typeId传递回视图

this.Data["typeId"] = typeId

在视图中拼接标签,传递回来typeId内容

<a href="/goodsList?typeId={{.typeId}}&pageIndex={{.pagePre}}">上一页a>
{{range $index,$val := .pages}}
	{{if compare $.pageIndex $val}}
		<a href="/goodsList?typeId={{$.typeId}}&pageIndex={{$val}}" class="active">{{$val}}a>
	{{else}}
		<a href="/goodsList?typeId={{$.typeId}}&pageIndex={{$val}}">{{$val}}a>
	{{end}}
{{end}}
<a href="/goodsList?typeId={{.typeId}}&pageIndex={{.pageNext}}">下一页a>

这时候,分页的功能就实现了 。

根据不同的选项获取不同排序内容

GO语言开发天天生鲜项目第四天 商品后台管理_第22张图片

由图可知,我们这个页面还要根据不同的选择,显示不同的商品排序,分别是默认排序,价格排序,人气(销量)排序。那就需要在我们查询的时候再加上排序的函数,这里我们用的排序函数是OrderBy(),也是高级查询函数,参数是要排序的字段名。那这时候,我们给这三个超链接就需要添加一个sort的值。代码如下:

<a href="/goodsList?typeId={{.typeId}}" class="active">默认a>
<a href="/goodsList?typeId={{.typeId}}&sort=price">价格a>
<a href="/goodsList?typeId={{.typeId}}&sort=sale">人气a>

在后台,我们需要根据不同的sort值获取不同顺序的数据,代码如下:

//根据不同的选项获取不同的数据
sort := this.GetString("sort")
//如果sort等于空,就按照默认排序
if sort == ""{	o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).Limit(pageSize,start).All(&goodsSKus)
}else if sort == "price"{	o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).OrderBy("Price").Limit(pageSize,start).All(&goodsSKus)
}else{
o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).OrderBy("Sales").Limit(pageSize,start).All(&goodsSKus)
}

4.搜索页面商品内容展示

ePre}}">上一页
{{range i n d e x , index, index,val := .pages}}
{{if compare $.pageIndex KaTeX parse error: Expected 'EOF', got '}' at position 4: val}̲} KaTeX parse error: Expected 'EOF', got '}' at position 4: val}̲}" class="activ…val}}
{{else}}
{{$val}}
{{end}}
{{end}}
下一页


这时候,分页的功能就实现了 。

**根据不同的选项获取不同排序内容**

[外链图片转存中...(img-S5MOappk-1650597137422)]

由图可知,我们这个页面还要根据不同的选择,显示不同的商品排序,分别是默认排序,价格排序,人气(销量)排序。那就需要在我们查询的时候再加上排序的函数,这里我们用的排序函数是**OrderBy()**,也是高级查询函数,参数是要排序的字段名。那这时候,我们给这三个超链接就需要添加一个sort的值。代码如下:

```html
默认
价格
人气

在后台,我们需要根据不同的sort值获取不同顺序的数据,代码如下:

//根据不同的选项获取不同的数据
sort := this.GetString("sort")
//如果sort等于空,就按照默认排序
if sort == ""{	o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).Limit(pageSize,start).All(&goodsSKus)
}else if sort == "price"{	o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).OrderBy("Price").Limit(pageSize,start).All(&goodsSKus)
}else{
o.QueryTable("GoodsSKU").RelatedSel("GoodsType").Filter("GoodsType__Id",typeId).OrderBy("Sales").Limit(pageSize,start).All(&goodsSKus)
}

4.搜索页面商品内容展示

你可能感兴趣的:(go,教程,go,架构,后端)