美多商城项目:商品数据库表设计、准备商品数据、首页广告、商品列表页
一、商品数据库表设计
1.1 SPU和SKU
在电商中对于商品,有两个重要的概念:SPU和SKU
1. SPU介绍
- SPU = Standard Product Unit (标准产品单位)
- SPU是商品信息聚合的最小单位,是一组可服用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
- 通俗的讲,属性值、特性相同的商品就可以归类到一类SPU。
- 例如:
iPhone X
就是一个 SPU ,与商家、颜色、款式、规格、套餐等都无关。
2. SKU介绍
- SKU = Stock Keeping Unit (库存量单位)
- SKU即库存进出计量的单位,可以是以件、盒等为单位,是物理上不可分割的最小存货单元。
- 通俗的讲,SKU是指一款商品,每款都有一个SKU,便于电商品牌识别商品。
- 例如:
iPhone X 全网通 黑色 256G
就是一个 SKU ,表示了具体的规格、颜色等信息。
思考
SPU和SKU是怎样的对应关系?
- 一对一?
- 一对多?
1.2 首页广告数据库表分析
1. 首页广告数据库表分析
2. 定义首页广告模型类
将模型类定义在contents子应用中
from django.db import models
from utils.models import BaseModel
# Create your models here.
class ContentCategory(BaseModel):"""广告内容类别"""name = models.CharField(max_length=50, verbose_name='名称')key = models.CharField(max_length=50, verbose_name='类别键名')class Meta:db_table = 'tb_content_category'verbose_name = '广告内容类别'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass Content(BaseModel):"""广告内容"""category = models.ForeignKey(ContentCategory, on_delete=models.PROTECT, verbose_name='类别')title = models.CharField(max_length=100, verbose_name='标题')url = models.CharField(max_length=300, verbose_name='内容链接')image = models.ImageField(null=True, blank=True, verbose_name='图片')text = models.TextField(null=True, blank=True, verbose_name='内容')sequence = models.IntegerField(verbose_name='排序')status = models.BooleanField(default=True, verbose_name='是否展示')class Meta:db_table = 'tb_content'verbose_name = '广告内容'verbose_name_plural = verbose_namedef __str__(self):return self.category.name + ': ' + self.title
1.3 商品信息数据库表分析
1. 商品信息数据库表分析
2. 定义商品信息模型类
创建goods子应用
from django.db import models
from utils.models import BaseModelclass GoodsCategory(BaseModel):"""商品类别"""name = models.CharField(max_length=10, verbose_name='名称')parent = models.ForeignKey('self', related_name='subs', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父类别')class Meta:db_table = 'tb_goods_category'verbose_name = '商品类别'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass GoodsChannelGroup(BaseModel):"""商品频道组"""name = models.CharField(max_length=20, verbose_name='频道组名')class Meta:db_table = 'tb_channel_group'verbose_name = '商品频道组'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass GoodsChannel(BaseModel):"""商品频道"""group = models.ForeignKey(GoodsChannelGroup, on_delete=models.CASCADE, verbose_name='频道组名')category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')url = models.CharField(max_length=50, verbose_name='频道页面链接')sequence = models.IntegerField(verbose_name='组内顺序')class Meta:db_table = 'tb_goods_channel'verbose_name = '商品频道'verbose_name_plural = verbose_namedef __str__(self):return self.category.nameclass Brand(BaseModel):"""品牌"""name = models.CharField(max_length=20, verbose_name='名称')logo = models.ImageField(verbose_name='Logo图片')first_letter = models.CharField(max_length=1, verbose_name='品牌首字母')class Meta:db_table = 'tb_brand'verbose_name = '品牌'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass SPU(BaseModel):"""商品SPU"""name = models.CharField(max_length=50, verbose_name='名称')brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品牌')category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat1_spu', verbose_name='一级类别')category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat2_spu', verbose_name='二级类别')category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat3_spu', verbose_name='三级类别')sales = models.IntegerField(default=0, verbose_name='销量')comments = models.IntegerField(default=0, verbose_name='评价数')desc_detail = models.TextField(default='', verbose_name='详细介绍')desc_pack = models.TextField(default='', verbose_name='包装信息')desc_service = models.TextField(default='', verbose_name='售后服务')class Meta:db_table = 'tb_spu'verbose_name = '商品SPU'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass SKU(BaseModel):"""商品SKU"""name = models.CharField(max_length=50, verbose_name='名称')caption = models.CharField(max_length=100, verbose_name='副标题')spu = models.ForeignKey(SPU, on_delete=models.CASCADE, verbose_name='商品')category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, verbose_name='从属类别')price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价')market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价')stock = models.IntegerField(default=0, verbose_name='库存')sales = models.IntegerField(default=0, verbose_name='销量')comments = models.IntegerField(default=0, verbose_name='评价数')is_launched = models.BooleanField(default=True, verbose_name='是否上架销售')default_image = models.ImageField(max_length=200, default='', null=True, blank=True, verbose_name='默认图片')class Meta:db_table = 'tb_sku'verbose_name = '商品SKU'verbose_name_plural = verbose_namedef __str__(self):return '%s: %s' % (self.id, self.name)class SKUImage(BaseModel):"""SKU图片"""sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku')image = models.ImageField(verbose_name='图片')class Meta:db_table = 'tb_sku_image'verbose_name = 'SKU图片'verbose_name_plural = verbose_namedef __str__(self):return '%s %s' % (self.sku.name, self.id)class SPUSpecification(BaseModel):"""商品SPU规格"""spu = models.ForeignKey(SPU, on_delete=models.CASCADE, related_name='specs', verbose_name='商品SPU')name = models.CharField(max_length=20, verbose_name='规格名称')class Meta:db_table = 'tb_spu_specification'verbose_name = '商品SPU规格'verbose_name_plural = verbose_namedef __str__(self):return '%s: %s' % (self.spu.name, self.name)class SpecificationOption(BaseModel):"""规格选项"""spec = models.ForeignKey(SPUSpecification, related_name='options', on_delete=models.CASCADE, verbose_name='规格')value = models.CharField(max_length=20, verbose_name='选项值')class Meta:db_table = 'tb_specification_option'verbose_name = '规格选项'verbose_name_plural = verbose_namedef __str__(self):return '%s - %s' % (self.spec, self.value)class SKUSpecification(BaseModel):"""SKU具体规格"""sku = models.ForeignKey(SKU, related_name='specs', on_delete=models.CASCADE, verbose_name='sku')spec = models.ForeignKey(SPUSpecification, on_delete=models.PROTECT, verbose_name='规格名称')option = models.ForeignKey(SpecificationOption, on_delete=models.PROTECT, verbose_name='规格值')class Meta:db_table = 'tb_sku_specification'verbose_name = 'SKU规格'verbose_name_plural = verbose_namedef __str__(self):return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value)
二、准备商品数据
2.1 文件存储方案FastDFS
1. FastDFS介绍
- 用
c语言
编写的一款开源的轻量级分布式文件系统。 - 功能包括:文件存储、文件访问(文件上传、文件下载)、文件同步等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
- 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标。
- 可以帮助我们搭建一套高性能的文件服务器集群,并提供文件上传、下载等服务。
- FastDFS架构 包括
Client
、Tracker server
和Storage server
。Client
请求Tracker
进行文件上传、下载,Tracker
再调度Storage
完成文件上传和下载。
- Client: 客户端,业务请求的发起方,通过专有接口,使用TCP/IP协议与
Tracker
或Storage
进行数据交互。FastDFS提供了upload
、download
、delete
等接口供客户端使用。 - Tracker server:跟踪服务器,主要做调度工作,起负载均衡的作用。在内存中记录集群中所有存储组和存储服务器的状态信息,是客户端和数据服务器交互的枢纽。
- Storage server:存储服务器(存储节点或数据服务器),文件和文件属性都保存到存储服务器上。Storage server直接利用OS的文件系统调用管理文件。
- Storage群中的横向可以扩容,纵向可以备份。
2. FastDFS上传和下载流程
3. FastDFS文件索引
FastDFS上传和下载流程可以看出都涉及到一个数据叫文件索引(file_id)。
- 文件索引(file_id)是客户端上传文件后Storage返回给客户端的一个字符串,是以后访问该文件的索引信息。
- 文件索引(file_id)信息包括:组名、虚拟磁盘路径、数据两级目录、文件名等信息。
- 组名:文件上传后所在的 Storage 组名称。
- 虚拟磁盘路径:Storage 配置的虚拟路径,与磁盘选项
store_path*
对应。如果配置了store_path0
则是M00
,如果配置了store_path1
则是M01
,以此类推。 - 数据两级目录:Storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
- 文件名:由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
2.2 容器化方案Docker
思考:
- FastDFS的安装步骤非常的多,涉及的依赖包也很多,当新的机器需要安装FastDFS时,是否需要从头开始安装。
- 我们在学习时拿到ubuntu系统的镜像,在VM虚拟机中运行这个镜像后,为什么就可以直接进行开发,而不需要重新搭建开发环境。
- 在工作中,如何高效的保证开发人员写代码的开发环境与应用程序要部署的生产环境一致性。如果要部署一台新的机器,是否需要从头开始部署。
结论:
- 上述思考的问题,都涉及到相同的工作是否需要重复做。
- 避免相同的工作重复做是容器化技术应用之一。
容器化方案:
- Docker
- Docker的目标之一就是缩短代码从开发、测试到部署、上线运行的周期,让我们的应用程序具备可移植性、易于构建、并易于协作。
1. Docker介绍
- Docker中文社区文档
- Docker 是一个开源的软件部署解决方案。
- Docker 也是轻量级的应用容器框架。
- Docker 可以打包、发布、运行任何的应用。
- Docker 就像一个盒子,里面可以装很多物件,如果需要某些物件,可以直接将该盒子拿走,而不需要从该盒子中一件一件的取。
- Docker 是一个
客户端-服务端(C/S)
架构程序。- 客户端只需要向服务端发出请求,服务端处理完请求后会返回结果。
Docker 包括三个基本概念:
镜像(Image)
- Docker的镜像概念类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,包括运行容器所需的数据,可以用来创建新的容器。
- 例如:一个镜像可以包含一个完整的 ubuntu 操作系统环境,里面仅安装了MySQL或用户需要的其它应用程序。
容器(Container)
- Docker容器是由Docker镜像创建的运行实例,类似VM虚拟机,支持启动,停止,删除等。
- 每个容器间是相互隔离的,容器中会运行特定的应用,包含特定应用的代码及所需的依赖文件。
仓库(Repository)
- Docker的仓库功能类似于Github,是用于托管镜像的。
2. Docker安装(不需要安装已经安装好)
1.源码安装Docker CE
$ cd docker源码目录
$ sudo apt-key add gpg
$ sudo dpkg -i docker-ce_17.03.2~ce-0~ubuntu-xenial_amd64.deb
2.检查Docker CE是否安装正确
$ sudo docker run hello-world
出现如下信息,表示安装成功
3.启动与停止
- 安装完成Docker后,默认已经启动了docker服务。
# 启动docker$ sudo service docker start# 重启docker$ sudo service docker restart# 停止docker$ sudo service docker stop
3. Docker镜像操作
1.镜像列表
$ sudo docker image ls
* REPOSITORY:镜像所在的仓库名称
* TAG:镜像标签
* IMAGEID:镜像ID
* CREATED:镜像的创建日期(不是获取该镜像的日期)
* SIZE:镜像大小
2.从仓库拉取镜像
# 官方镜像$ sudo docker image pull 镜像名称 或者 sudo docker image pull library/镜像名称
$ sudo docker image pull ubuntu 或者 sudo docker image pull library/ubuntu
$ sudo docker image pull ubuntu:16.04
或者 sudo docker image pull library/ubuntu:16.04
# 个人镜像$ sudo docker image pull 仓库名称/镜像名称
$ sudo docker image pull itcast/fastdfs
3.删除镜像
$ sudo docker image rm 镜像名或镜像ID
$ sudo docker image rm hello-world
$ sudo docker image rm fce289e99eb9
4. Docker容器操作
1.容器列表
# 查看正在运行的容器$ sudo docker container ls# 查看所有的容器$ sudo docker container ls --all
2.创建容器
$ sudo docker run [option] 镜像名 [向启动容器中传入的命令]
常用可选参数说明:
* -i 表示以《交互模式》运行容器。
* -t 表示容器启动后会进入其命令行。加入这两个参数后,容器创建就能登录进去。即分配一个伪终端。
* --name 为创建的容器命名。
* -v 表示目录映射关系,即宿主机目录:容器中目录。注意:最好做目录映射,在宿主机上做修改,然后共享到容器上。
* -d 会创建一个守护式容器在后台运行(这样创建容器后不会自动登录容器)。
* -p 表示端口映射,即宿主机端口:容器中端口。
* --network=host 表示将主机的网络环境映射到容器中,使容器的网络与主机相同。
3.交互式容器
$ sudo docker run -it --name=ubuntu1 ubuntu /bin/bash
在容器中可以随意执行linux命令,就是一个ubuntu的环境。
当执行 exit 命令退出时,该容器随之停止。
4.守护式容器
# 开启守护式容器$ sudo docker run -dit --name=ubuntu2 ubuntu
# 进入到容器内部交互环境$ sudo docker exec -it 容器名或容器id 进入后执行的第一个命令
$ sudo docker exec -it ubuntu2 /bin/bash
如果对于一个需要长期运行的容器来说,我们可以创建一个守护式容器。
在容器内部执行 exit 命令退出时,该容器也随之停止。
5.停止和启动容器
# 停止容器$ sudo docker container stop 容器名或容器id# kill掉容器$ sudo docker container
kill 容器名或容器id# 启动容器$ sudo docker container start 容器名或容器id
6.删除容器
- 正在运行的容器无法直接删除。
$ sudo docker container rm 容器名或容器id
7.容器制作成镜像
- 为保证已经配置完成的环境可以重复利用,我们可以将容器制作成镜像。
# 将容器制作成镜像$ sudo docker commit 容器名 镜像名
# 镜像打包备份$ sudo docker save -o 保存的文件名 镜像名
# 镜像解压$ sudo docker load -i 文件路径/备份文件
2.3 Docker和FastDFS上传和下载文件
1. Docker安装运行FastDFS
1.获取FastDFS镜像
# 从仓库拉取镜像$ sudo docker image pull delron/fastdfs# 解压教学资料中本地镜像$ sudo docker load -i 文件路径/fastdfs_docker.tar
2.开启tracker容器
- 我们将 tracker 运行目录映射到宿主机的
/var/fdfs/tracker
目录中。
$ sudo docker run -dit --name tracker --network=host -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker
3.开启storage容器
- TRACKER_SERVER=Tracker的ip地址:22122(Tracker的ip地址不要使用127.0.0.1)
- 我们将 storage 运行目录映射到宿主机的
/var/fdfs/storage
目录中。
$ sudo docker run -dti --name storage --network=host -e TRACKER_SERVER=192.168.103.158:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage
4.查看宿主机映射路径
注意:如果无法重启storage容器,可以删除/var/fdfs/storage/data
目录下的fdfs_storaged.pid
文件,然后重新运行storage。
2. FastDFS客户端上传文件
- Python版本的FastDFS客户端使用参考文档
1.安装FastDFS客户端扩展
- 安装准备好的
fdfs_client-py-master.zip
到虚拟环境中
$ pip install fdfs_client-py-master.zip
$ pip install mutagen
$ pip install requests
2.准备FastDFS客户端扩展的配置文件
utils.fastdfs.client.conf
base_path=FastDFS客户端存放日志文件的目录
tracker_server=运行Tracker服务的机器ip:22122
3.FastDFS客户端实现文件存储
# 使用 shell 进入 Python交互环境
$ python manage.py shell
上传文件需要先创建fdfs_client.client.Fdfs_client的对象,并指明配置文件,如
from fdfs_client.client import Fdfs_client
client = Fdfs_client('utils/fastdfs/client.conf')
通过创建的客户端对象执行上传文件的方法
client.upload_by_filename(文件名)
或
client.upload_by_buffer(文件bytes数据)
如:
>>> from fdfs_client.client import Fdfs_client
>>> client=Fdfs_client('utils/fastdfs/client.conf')
>>> client.upload_by_filename('/home/python/Desktop/images/0.jpg')
getting connection
<fdfs_client.connection.Connection object at 0x7f25174eb940>
<fdfs_client.fdfs_protol.Tracker_header object at 0x7f25174eb908>
{'Remote file_id': 'group1/M00/00/00/wKjlhFsTgJ2AJvG_AAAyZgOTZN0850.jpg', 'Uploaded size': '12.00KB','Local file name': '/home/python/Desktop/images/0.jpg', 'Storage IP': '192.168.229.132','Group name': 'group1', 'Status': 'Upload successed.'}
- 'Group name': 'Storage组名',
- 'Remote file_id': '文件索引,可用于下载',
- 'Status': '文件上传结果反馈',
- 'Local file name': '上传文件全路径',
- 'Uploaded size': '文件大小',
- 'Storage IP': 'Storage地址'
3. 浏览器下载并渲染图片
思考:如何才能找到在Storage中存储的图片?
- 协议:
http
- IP地址:
192.168.103.158
Nginx
服务器的IP地址。- 因为 FastDFS 擅长存储静态文件,但是不擅长提供静态文件的下载服务,所以我们一般会将 Nginx 服务器绑定到 Storage ,提升下载性能。
- 端口:
8888
Nginx
服务器的端口。
- 路径:
group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg
- 文件在Storage上的文件索引。
- 完整图片下载地址
http://192.168.103.158:8888/group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg
编写测试代码:
utils.fdfs.html
<img src="http://192.168.103.158:8888/group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg" width="320" height="480">
2.4 录入商品数据和图片数据
1. SQL脚本录入商品数据
导入数据之前,务必确保已经对goods和contents子应用的模型进行了迁移
python manage.py makemigrations python manage.py migrate
$ mysql -h127.0.0.1 -uroot -pmysql meiduo_mall < 文件路径/goods_data.sql
2. FastDFS服务器录入图片数据
1.准备新的图片数据压缩包
2.删除 Storage 中旧的
data
目录
3.拷贝新的图片数据压缩包到 Storage,并解压
# 解压命令sudo tar -zxvf data.tar.gz
4.查看新的
data
目录
三、首页广告
3.1 展示首页商品频道分类
1. 分析首页商品频道分类数据结构
{"1":{"channels":[{"id":1, "name":"手机", "url":"http://shouji.jd.com/"},{"id":2, "name":"相机", "url":"http://www.itcast.cn/"}],"sub_cats":[{"id":38, "name":"手机通讯", "sub_cats":[{"id":115, "name":"手机"},{"id":116, "name":"游戏手机"}]},{"id":39, "name":"手机配件", "sub_cats":[{"id":119, "name":"手机壳"},{"id":120, "name":"贴膜"}]}]},"2":{"channels":[],"sub_cats":[]}
}
2. 查询首页商品频道分类(分析思路,代码复制过来,查看效果没问题就可以)
from django.views import View
from collections import OrderedDict
from apps.goods.models import GoodsChannelclass IndexView(View):"""首页广告"""def get(self, request):"""提供首页广告界面"""# 查询商品频道和分类categories = OrderedDict()channels = GoodsChannel.objects.order_by('group_id', 'sequence')for channel in channels:group_id = channel.group_id # 当前组if group_id not in categories:categories[group_id] = {'channels': [], 'sub_cats': []}cat1 = channel.category # 当前频道的类别# 追加当前频道categories[group_id]['channels'].append({'id': cat1.id,'name': cat1.name,'url': channel.url})# 构建当前类别的子类别for cat2 in cat1.subs.all():cat2.sub_cats = []for cat3 in cat2.subs.all():cat2.sub_cats.append(cat3)categories[group_id]['sub_cats'].append(cat2)
4. 封装首页商品频道分类
1.封装首页商品频道分类到
utils.goods.py
文件
def get_categories():"""提供商品频道和分类:return 菜单字典"""# 查询商品频道和分类categories = OrderedDict()channels = GoodsChannel.objects.order_by('group_id', 'sequence')for channel in channels:group_id = channel.group_id # 当前组if group_id not in categories:categories[group_id] = {'channels': [], 'sub_cats': []}cat1 = channel.category # 当前频道的类别# 追加当前频道categories[group_id]['channels'].append({'id': cat1.id,'name': cat1.name,'url': channel.url})# 构建当前类别的子类别for cat2 in cat1.subs.all():cat2.sub_cats = []for cat3 in cat2.subs.all():cat2.sub_cats.append(cat3)categories[group_id]['sub_cats'].append(cat2)return categories
2.
contents.view.py
中使用utils.goods.py
文件
class IndexView(View):"""首页广告"""def get(self, request):"""提供首页广告界面"""# 查询商品频道和分类categories = get_categories()# 渲染模板的上下文context = {'categories': categories,}
3.2 展示首页商品广告
1. 分析首页商品广告数据结构
结论:
- 首页商品广告数据由广告分类和广告内容组成。
- 广告分类带有标识符
key
,可以利用它确定广告展示的位置。- 确定广告展示的位置后,再查询和渲染出该位置的广告内容。
- 广告的内容还有内部的排序字段,决定了广告内容的展示顺序。
2. 查询首页商品广告
class IndexView(View):"""首页广告"""def get(self, request):"""提供首页广告界面"""# 查询商品频道和分类......# 广告数据contents = {}content_categories = ContentCategory.objects.all()for cat in content_categories:contents[cat.key] = cat.content_set.filter(status=True).order_by('sequence')# 渲染模板的上下文context = {'categories': categories,'contents': contents,}return render(request, 'index.html', context)
3.3 自定义Django文件存储类
思考:
- 下图首页页面中图片无法显示的原因。
结论:
- 通过FastDFS上传文件后返回的
'Remote file_id'
字段是文件索引。- 文件索引会被我们存储到MySQL数据库 。所以将来读取出来的也是文件索引,导致界面无法下载到图片。
解决:
- 重写Django文件存储类的url()方法。
- 在重写时拼接完整的图片下载地址(协议、IP、端口、文件索引)
1. Django文件存储类url()方法介绍
结论:
- 文件存储类
url()
方法的作用:返回name
所代表的文件内容的URL。- 文件存储类
url()
方法的触发:content.image.url
- 虽然表面上调用的是
ImageField
的url
方法。但是内部会去调用文件存储类的url()
方法。- 文件存储类
url()
方法的使用:
- 我们可以通过自定义Django文件存储类达到重写
url()
方法的目的。- 自定义Django文件存储类必须提供
url()
方法。- 返回name所指的文件对应的绝对URL。
2. 自定义Django文件存储类
自定义文件存储类的官方文档
class FastDFSStorage(Storage):"""自定义文件存储系统"""def _open(self, name, mode='rb'):"""用于打开文件:param name: 要打开的文件的名字:param mode: 打开文件方式:return: None"""# 打开文件时使用的,此时不需要,而文档告诉说明必须实现,所以passpassdef _save(self, name, content):"""用于保存文件:param name: 要保存的文件名字:param content: 要保存的文件的内容:return: None"""# 保存文件时使用的,此时不需要,而文档告诉说明必须实现,所以passpass
3. 重写Django文件存储类url()方法
1.重写
url()
方法
class FastDFSStorage(Storage):"""自定义文件存储系统,修改存储的方案"""def __init__(self, fdfs_base_url=None):"""构造方法,可以不带参数,也可以携带参数:param base_url: Storage的IP"""self.fdfs_base_url = fdfs_base_url or settings.FDFS_BASE_URLdef _open(self, name, mode='rb'):......def _save(self, name, content):......def url(self, name):"""返回name所指文件的绝对URL:param name: 要读取文件的引用:group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg:return: http://192.168.103.158:8888/group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg"""# return 'http://192.168.103.158:8888/' + name# return 'http://image.meiduo.site:8888/' + namereturn self.fdfs_base_url + name
2.相关配置参数
# 指定自定义的Django文件存储类
DEFAULT_FILE_STORAGE = 'utils.fastdfs.storage.FastDFSStorage'# FastDFS相关参数
# FDFS_BASE_URL = 'http://192.168.103.158:8888/'
FDFS_BASE_URL = 'http://image.meiduo.site:8888/'
3.添加访问图片的域名
- 在
/etc/hosts
中添加访问Storage的域名
$ Storage的IP 域名
$ 192.168.103.158 image.meiduo.site
四、商品列表页
4.1 商品列表页分析
1. 商品列表页组成结构分析
1.商品频道分类
- 已经提前封装在
contents.utils.py
文件中,直接调用即可。
2.面包屑导航
- 可以使用三级分类ID,查询出该类型商品的三级分类数据。
3.排序和分页
- 无论如何排序和分页,商品的分类不能变。
- 排序时需要知道当前排序方式。
- 分页时需要知道当前分页的页码,且每页五条商品记录。
4.热销排行
- 热销排行中的商品分类要和排序、分页的商品分类一致。
- 热销排行是查询出指定分类商品销量前二的商品。
- 热销排行使用Ajax实现局部刷新的效果。
2. 商品列表页接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /list/(?P<category_id>\d+)/skus/ |
# 排序依据:
# 按照商品创建时间排序
http://www.meiduo.site:8000/list/115/skus/?page=1&page_size=5&ordering=-create_time# 按照商品价格由低到高排序
http://www.meiduo.site:8000/list/115/skus/?page=1&page_size=5&ordering=price# 按照商品销量由高到低排序
http://www.meiduo.site:8000/list/115/skus/?page=1&page_size=5&ordering=-sales
2.请求参数:路径参数 和 查询参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
category_id | string | 是 | 商品分类ID,第三级分类 |
page_num | string | 是 | 当前页码 |
sort | string | 否 | 排序方式 |
4.2 列表页面包屑导航
重要提示:路径参数category_id是商品第三级分类
1. 查询列表页面包屑导航数据
提示: 对包屑导航数据的查询进行封装,方便后续直接使用。
goods.utils.py
def get_breadcrumb(category):"""获取面包屑导航:param category: 商品类别:return: 面包屑导航字典"""breadcrumb = dict(cat1='',cat2='',cat3='')if category.parent is None:# 当前类别为一级类别breadcrumb['cat1'] = category.nameelif category.subs.count() == 0:# 当前类别为三级breadcrumb['cat3'] = category.namebreadcrumb['cat2'] = category.parent.namebreadcrumb['cat1'] = category.parent.parent.nameelse:# 当前类别为二级breadcrumb['cat2'] = category.namebreadcrumb['cat1'] = category.parent.namereturn breadcrumb
4.3 列表页分页和排序
# 按照商品创建时间排序
http://www.meiduo.site:8000/list/115/skus/?sort=default
# 按照商品价格由低到高排序
http://www.meiduo.site:8000/list/115/skus/?sort=price
# 按照商品销量由高到低排序
http://www.meiduo.site:8000/list/115/skus/?sort=hot
1. 查询列表页分页和排序数据
分页文档
from django.core.paginator import Paginator, EmptyPage
from django.views import View
from apps.goods.models import SKU, GoodsCategory
from django.http import JsonResponse
from utils.goods import get_breadcrumbclass ListView(View):"""商品列表页"""def get(self, request, category_id):"""提供商品列表页"""# 获取参数:page = request.GET.get('page')page_size = request.GET.get('page_size')ordering = request.GET.get('ordering')# 判断category_id是否正确try:# 获取三级菜单分类信息:category = GoodsCategory.objects.get(id=category_id)except Exception as e:return JsonResponse({'code':400,'errmsg':'获取mysql数据出错'})# 查询面包屑导航(函数在下面写着)breadcrumb = get_breadcrumb(category)# 排序方式:try:skus = SKU.objects.filter(category=category,is_launched=True).order_by(ordering)except Exception as e:return JsonResponse({'code':400,'errmsg':'获取mysql数据出错'})paginator = Paginator(skus, page_size)# 获取每页商品数据try:page_skus = paginator.page(page)except EmptyPage:# 如果page_num不正确,默认给用户400return JsonResponse({'code':400,'errmsg':'page数据出错'})# 获取列表页总页数total_page = paginator.num_pages# 定义列表:list = []# 整理格式:for sku in page_skus:list.append({'id':sku.id,'default_image_url':sku.default_image.url,'name':sku.name,'price':sku.price})# 把数据变为 json 发送给前端return JsonResponse({'code':0,'errmsg':'ok','breadcrumb': breadcrumb,'list':list,'count':total_page})
4.4 列表页热销排行
根据路径参数
category_id
查询出该类型商品销量前二的商品。使用Ajax实现局部刷新的效果。
1. 查询列表页热销排行数据
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /hot/(?P<category_id>\d+)/ |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
category_id | string | 是 | 商品分类ID,第三级分类 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
hot_skus[ ] | 热销SKU列表 |
id | SKU编号 |
default_image_url | 商品默认图片 |
name | 商品名称 |
price | 商品价格 |
{"code":"0","errmsg":"OK","hot_skus":[{"id":6,"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998","name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机","price":"7988.00"},{"id":14,"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364","name":"华为 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移动联通电信4G手机 双卡双待","price":"3788.00"}]
}
4.接口定义和实现
class HotGoodsView(View):"""商品热销排行"""def get(self, request, category_id):"""提供商品热销排行JSON数据"""# 根据销量倒序skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]# 序列化hot_skus = []for sku in skus:hot_skus.append({'id':sku.id,'default_image_url':sku.default_image.url,'name':sku.name,'price':sku.price})return JsonResponse({'code':0, 'errmsg':'OK', 'hot_skus':hot_skus})
美多商城项目:商品数据库表设计、准备商品数据、首页广告、商品列表页相关推荐
- 美多商城之商品(商品数据库表设计)
一.商品数据库表设计 1.1 SPU和SKU 在电商中对于商品,有两个重要的概念:SPU和SKU 1. SPU介绍 [即为商品的一个概述,一种商品的统称] SPU = Standard Pr ...
- Python项目实战 6.1 商品:商品数据库表设计
目录 一.SPU和SKU 二.首页广告数据库表分析 三.商品信息数据库表分析 一.SPU和SKU 待更新 二.首页广告数据库表分析 待更新 三.商品信息数据库表分析 待更新
- 【精品】电商项目 中 基于SPU与SKU的 商品 数据库表设计
简介 一般情况下我们使用5张表就可以解决基本的需求了: 商品分类表:category 商品表(即SPU表):表:product 商品规格表(即sku表):product_specs 属性key表:at ...
- 电商项目的数据库表设计(MySQL版)
简介: 目的: 电商常用功能模块的数据库设计 常见问题的数据库解决方案 环境: MySQL5.7 图形客户端,SQLyog Linux 模块: 用户:注册.登陆 商品:浏览.管理 订单:生成.管理 仓 ...
- Python3,网站搭建之数据库表设计及数据存储!文末的彩蛋,我酸了~
搭建自己的网站,是作为一个码农成功标志之一, 那其他成功标志有啥呢, 嘿- 左手搂着白富美,右手撸着小烧烤,脚底踩着桑塔纳- 嗯~ 这么潇洒的人生,就从数据库表设计及数据存储开始吧! 数据库表设计及存 ...
- 商城 商品模块 数据库 表设计
商城 商品模块 数据库 表设计 要实现一个商城,对于商品模块中的数据库表设计不懂,主要是:相同类别的产品的产品参数相同,不同类别的不同,这里就不懂要怎么设计了,所以上网找几篇博客了解. 什么是SPU. ...
- 【美多商城项目01】了解主要需求和架构设计,创建配置工程
一.美多商城项目介绍 1.1 项目需求分析 需求分析原因 项目中,需求驱动开发,即开发人员需要以需求为目标来实现业务逻辑. 需求分析方式 企业中,借助产品原型图分析需求. 需求分析完后,前端按照产品原 ...
- 网上书城项目的需求分析、数据库表设计及前端界面的编写(项目进度一)
转载请标明出处:https://blog.csdn.net/men_ma/article/details/106847165. 本文出自 不怕报错 就怕不报错的小猿猿 的博客 网上书城项目的需求分析. ...
- python美多商城项目百度网盘_美多商城项目(七)
正文共: 7620字 4图 预计阅读时间: 20分钟 每日分享 If you can change your mind, you can change your life. 如果你愿意改变你的想法,你 ...
最新文章
- 强化学习入门教程(附学习大纲)
- Redhat5中discuz 7论坛的搭建
- 倾城之恋 “樱“爱而美2020-04-20
- usr/bin/expect方式免密码登录和发送文件脚本
- [Abp vNext 源码分析] - 19. 多租户
- C语言指针(1)嵌入式linux
- Java测试代码及原理
- 数字电视 frontend tuner demod
- java实现房屋出租系统
- 面试中可以问面试官的问题
- jsp实现文件下载,out = pageContext.pushBody();out.close();不用写到jsp中
- Android 11源码 修改系统App后进行编译
- 大二Git-Branching学习
- SCU2016-05 I题 trie图 + 大数dp
- Linux软件手动添加到桌面或启动栏
- 各种有意思的Github项目收集,不断更新
- 手机百度浏览器ua标识在哪里_浏览器标识(ua)的那些事
- QT--HTTP图片下载器
- FFMPEG API函数根据SDP文件接收RTP媒体流
- 怎么把几个pdf文件合并到一起?
热门文章
- 基于阿里云盘二次开发实现磁盘同步,双向同步
- 高压功率放大器在微孔压电超声雾化研究中的应用
- CSS3之2D转换、3D转换
- 感谢关注(*^_^*)
- 互联网晚报 | 1月19日 星期三 | 支付宝“集五福”活动正式开启;星巴克全面上线美团外卖;微软正式收购动视暴雪...
- android系统精简,高通支持Android 8.1精简版,专为512MB或1GB内存设计
- 当前行情下,真的还能“跳进”进大厂吗?
- 2022年全球市场纸浆模塑包装总体规模、主要生产商、主要地区、产品和应用细分研究报告
- HTB-Obscurity writeup
- 在国产麒麟操作系统上如何安装ONLYOFFICE桌面编辑器V7.3