商品模块

一:商品后台管理

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

1.代码迁移

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

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

  • 在GoLand中打开项目

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

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

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

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

2.登陆和注册

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

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

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

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

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

3.添加类型

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

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.GoodsTypegoodsType.Name = typenamegoodsType.Image = typeImagegoodsType.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 称为存储服务器。

服务端两个角色:

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

Storage:实际保存文件

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

3.2.2文件上传流程

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

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

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

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

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

3.2.3文件下载流程

3.2.4简易FastDFS架构

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库

    下载缺少的库

    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 multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
      deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiversedeb 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编译的时候把警告当错误处理,事实上这个警告并不影响(程序员忽略警告):

      解决方法:

      找到objs目录下的Makefile

      vim Makefile

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

      然后回到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
    

    这时候会报一个错:

    这是因为我们的网络有防火墙,不能直接去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"] = goodsTypesthis.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

  • 解压00.zip到当前目录

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

二.商品信息展示

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

1.首页内容的展示

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

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

请求

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

路由

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

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

**控制器 **

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

  • 获取类型

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

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

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

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

    分析之后我们发现,只能用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"] = goodsTypegoods[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
    }
    
  • 数据处理

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

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

    //获取商品类型var goodsTypes []models.GoodsTypeo.QueryTable("GoodsType").All(&goodsTypes)this.Data["types"] = goodsTypes
    //获取商品详情var goods models.GoodsSKUo.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.Useruser.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.Useruser.Name = userName.(string)o.Read(&user,"Name")conn,_:=redis.Dial("tcp",":6379")//先清空以前的记录1reply,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.GoodsSKUconn,_ :=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.GoodsSKUo.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
    }
    
  • 处理数据

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

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

    • 类型数据

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

      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">1</a><a href="#">下一页></a></div></div></div>

分页处理

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

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

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

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

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

这种显示的都是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 := 2pageIndex ,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 []intif 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 - 1if pageIndex == 1{pagePre = 1}pageNext := pageIndex + 1if pageIndex == pageCount{pageNext = pageIndex}//把数据返回pageData := make(map[string]interface{})pageData["pagePre"] = pagePrepageData["pageNext"] = pageNextpageData["pageIndex"] = pageIndexBufferreturn 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>

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

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

由图可知,我们这个页面还要根据不同的选择,显示不同的商品排序,分别是默认排序,价格排序,人气(销量)排序。那就需要在我们查询的时候再加上排序的函数,这里我们用的排序函数是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 index,index,index,val := .pages}}
{{if compare $.pageIndex KaTeX parse error: Expected 'EOF', got '}' at position 4: val}̲} <a href="/g….typeId}}&pageIndex={{KaTeX parse error: Expected 'EOF', got '}' at position 4: val}̲}" class="activ…val}}
{{else}}
{{$val}}
{{end}}
{{end}}
下一页


这时候,分页的功能就实现了 。**根据不同的选项获取不同排序内容**[外链图片转存中...(img-S5MOappk-1650597137422)]由图可知,我们这个页面还要根据不同的选择,显示不同的商品排序,分别是默认排序,价格排序,人气(销量)排序。那就需要在我们查询的时候再加上排序的函数,这里我们用的排序函数是**OrderBy()**,也是高级查询函数,参数是要排序的字段名。那这时候,我们给这三个超链接就需要添加一个sort的值。代码如下:```html
<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.搜索页面商品内容展示

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

  1. GO语言开发天天生鲜项目第三天 用户模块开发

    项目分析 一.项目架构 二.数据库表设计 用户模块 ##一.用户注册 用户通过我们的网站购买商品前,必须要登录.如果,该用户在我们的网站上没有账号,那么必须进行注册.下面我们看一下注册的具体实现过程( ...

  2. 天天生鲜项目开发笔记

    天天生鲜项目开发笔记 说在前面的话 大学四年,忙忙碌碌,什么都学了,又好像什么都没学,总之要毕业了,毕设题目是"生鲜配送系统",B站上找到了一个天天生鲜的项目开发教程,之后文章记录 ...

  3. Django通过celery 异步发送邮件 : django开发之天天生鲜项目知识总结【5】

    这里初次学习celery,只简单讲解一下如何使用celery 异步发送邮件,在以后的总结中还会,多次提到celery,因为后面很多任务都需要用到celery执行任务,后面再专门针对celery做具体的 ...

  4. 【Django 天天生鲜项目05】订单(Mysql事务、并发处理、支付宝支付、评论)

    本部分涉及订单的生成.并发处理.支付.评论等 关键:MySQL事务.并发处理的悲观锁/乐观锁.支付宝SDK 的使用...... 仅作为个人笔记! 目录 2.创建订单 3.订单生成 3.1. MySQL ...

  5. 天天生鲜项目从0开始

    天天生鲜 一.环境准备 因为此文章是实践项目文章,所以对于基础的python安装就不多做赘述,这里只交代系统环境是ubuntu操作系统,python3.5 安装虚拟环境软件 安装虚拟环境: sudo ...

  6. 天天生鲜项目 python邮箱_python3 之 天天生鲜 项目(初学者)1

    简单第一步 假设: 你已学过 python基础.高级:了解并简单使用linux操作系统:mysql数据库.redis数据库的简单使用:掌握Django框架的使用 这是一个 很多培训机构 讲解的项目 r ...

  7. 天天生鲜项目——注册页面

    7.register.html 注册页面,已加入了初步的表单验证效果,此效果在课程中已讲述如何制作. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...

  8. 天天生鲜项目——用户订单页

    9.user_center_order.html 用户中心-用户订单页 用户中心功能二,查看用户的全部订单 <!DOCTYPE html PUBLIC "-//W3C//DTD XHT ...

  9. 天天生鲜项目——用户信息页

    8.user_center_info.html 用户中心-用户信息页 用户中心功能一,查看用户的基本信息 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTM ...

  10. 天天生鲜项目——我的购物车页

    4.cart.html 我的购物车页,列出已放入购物车上的商品 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//E ...

最新文章

  1. 牛妹吃豆子(二维前缀和模板,修改+求和)
  2. 2018html游戏引擎,跨平台三维游戏引擎Unity Pro 2018.1 Win x64
  3. Extensible Messaging and Presence Protocol (XMPP): Core
  4. java课程设计日历记事本代码,已开源
  5. 《It's All Upside Down》作者访谈录
  6. 看完这篇文章之后,终于明白了编译到底怎么回事。
  7. python cgi打印html代码
  8. mysql limitorderby
  9. 安装face_recognition(ModuleNotFoundError: No module named 'face_recognition')
  10. 编码的奥秘:字节与十六进制
  11. 443端口与80端口
  12. 智能建造与建筑工业化协同发展主战场之一:攻克核心工业软件
  13. 斯皮尔 皮尔森 肯德尔_科学网-在SPSS软件相关分析中,pearson(皮尔逊), kendall(肯德尔) 和spearman(斯伯曼/斯皮尔曼)三种相关分析方法有什么异同(转)-刘斌的博文...
  14. 一个有意思的需求——中文匹配度
  15. Python 如何将视频文件的语音转换为文字,良心之作!
  16. note GAN model
  17. python分苹果问题_蓝桥杯--算法提高--VIP--分苹果题目(差分数组)
  18. 真无线蓝牙耳机哪个牌子好?最适合打游戏的无线耳机
  19. 2022-2028年全球与中国热电堆和微测辐射热计红外探测器行业发展趋势及投资战略分析
  20. iOS 常用动画第三方

热门文章

  1. swapidc卡密充值插件
  2. foobar2000_为MP3/flac嵌入歌词文件/封面编辑(提供eslyric插件下载)/信息标签编辑
  3. UWB信号对服务器有没有干扰,uwb定位技术原理及应用分析
  4. java还原混淆代码_飘云阁安全论坛如何还原混淆加密的JAVA代码 - Powered by Discuz!...
  5. 取消计算机触摸板,笔记本电脑触摸板如何打开和关闭
  6. 网吧服务器ip地址修改,BXP服务器下的网吧ip地址怎么更改
  7. 51单片机lcd1602显示(模块)
  8. Android视频播放器demo
  9. 勉强算是面经——1.诺瓦科技
  10. Unity3D资源分享