美多商城之商品
1.商品数据库表设计
1.1SPU和SKU
在电商中对于商品,有两个重要的概念:SPU和SKU
-
SPU介绍(一个商品的总称)
- SPU = Standard Product Unit (标准产品单位)
- SPU是商品信息聚合的最小单位,是一组可服用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
- 通俗的讲,属性值、特性相同的商品就可以归类到一类SPU。
例如:
iPhone X 就是一个SPU,与商家、颜色、款式、规格、套餐等都无关。
-
SKU介绍(一个商品的详细信息)
- SKU = Stock Keeping Unit (库存量单位)
- SKU即库存进出计量的单位,可以是以件、盒等为单位,是物理上不可分割的最小存货单元。
- 通俗的讲,SKU是指一款商品,每款都有一个SKU,便于电商品牌识别商品。
例如:
iPhone X 全网通 黑色 256G 就是一个SKU,表示了具体的规格、颜色等信息。
-
思考
-
SPU和SKU是怎样的对应关系?
- 一对一?
- 一对多?
-
1.2.首页广告数据库表分析
-
首页广告数据库表分析
-
定义首页广告模型类
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_name def __str__(self): return self.name class 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_name def __str__(self): return self.category.name + ': ' + self.title
1.3.商品信息数据库表分析
-
商品信息数据库表分析
-
定义商品信息模型类
定义在首页广告模块子应用中class 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_name def __str__(self): return self.name class GoodsChannelGroup(BaseModel): """商品频道组""" name = models.CharField(max_length=20, verbose_name='频道组名') class Meta: db_table = 'tb_channel_group' verbose_name = '商品频道组' verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsChannel(BaseModel): """商品频道""" group = models.ForeignKey(GoodsChannelGroup, 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_name def __str__(self): return self.category.name class 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_name def __str__(self): return self.name class 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_name def __str__(self): return self.name class 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_name def __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_name def __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_name def __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_name def __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_name def __str__(self): return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value)
2.准备商品数据
提示:
数据库表有了以后,我们现在需要准备商品信息数据和商品图片数据,以便查询和展示。
商品信息数据:比如商品编号等都是字符串类型的,可以直接存储在MySQL数据库。
商品图片数据:MySQL通常存储的是图片的地址字符串信息。
所以图片数据需要进行其他的物理存储。
图片物理存储思考:
需要提供图片上传和下载的机制。
需要解决图片备份和扩容的问题。
需要解决图片重名的问题等等。
图片物理存储方案:
FastDFS
2.1.文件存储方案FastDFS
- 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群中的横向可以扩容,纵向可以备份。
-
FastDFS上传和下载流程
-
FastDFS文件索引
- FastDFS上传和下载流程 可以看出都涉及到一个数据叫文件索引(file_id)。
- 文件索引(file_id)是客户端上传文件后Storage返回给客户端的一个字符串,是以后访问该文件的索引信息。
- 文件索引(file_id)信息包括:组名、虚拟磁盘路径、数据两级目录、文件名等信息。
- 组名:文件上传后所在的 Storage 组名称。
- 虚拟磁盘路径:Storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1则是M01,以此类推。
- 数据两级目录:Storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
- 文件名:由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
2.2容器化方案Docker
思考:
1. FastDFS的安装步骤非常的多,涉及的依赖包也很多,当新的机器需要安装FastDFS时,是否需要从头开始安装。
2. 在学习时拿到ubuntu系统的镜像,在VM虚拟机中运行这个镜像后,为什么就可以直接进行开发,而不需要重新搭建开发环境。
3. 在工作中,如何高效的保证开发人员写代码的开发环境与应用程序要部署的生产环境一致性。如果要部署一台新的机器,是否需要从头开始部署。
结论:
1. 上述思考的问题,都涉及到相同的工作是否需要重复做。
2. 避免相同的工作重复做是容器化技术应用之一。
容器化方案:
Docker:
Docker的目标之一就是缩短代码从开发、测试到部署、上线运行的周期,让我们的应用程序具备可移植性、易于构建、并易于协作。
-
Docker介绍
-
Docker 是一个开源的软件部署解决方案。
-
Docker 也是轻量级的应用容器框架。
-
Docker 可以打包、发布、运行任何的应用。
Docker 就像一个盒子,里面可以装很多物件,如果需要某些物件,可以直接将该盒子拿走,而不需要从该盒子中一件一件的取。 -
Docker 是一个客户端-服务端(C/S)架构程序。
-
客户端只需要向服务端发出请求,服务端处理完请求后会返回结果。
-
Docker 包括三个基本概念:
-
Docker安装(ubuntu 16.04)
-
源码安装Docker CE
$ cd docker源码目录 $ sudo apt-key add gpg $ sudo dpkg -i docker-ce_17.03.2~ce-0~ubuntu-xenial_amd64.deb
-
检查Docker CE是否安装正确
$ sudo docker run hello-world
出现如下信息,表示安装成功
-
启动与停止
安装完成Docker后,默认已经启动了docker服务。
# 启动docker $ sudo service docker start # 重启docker $ sudo service docker restart # 停止docker $ sudo service docker stop
-
-
Docker镜像操作
-
镜像列表
$ sudo docker image ls
- REPOSITORY:镜像所在的仓库名称
- TAG:镜像标签
- IMAGEID:镜像ID
- CREATED:镜像的创建日期(不是获取该镜像的日期)
- SIZE:镜像大小
-
从仓库拉取镜像
# 官方镜像 $ 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
-
删除镜像
$ sudo docker image rm 镜像名或镜像ID $ sudo docker image rm hello-world $ sudo docker image rm fce289e99eb9
-
-
Docker容器操作( Docker镜像创建的运行实例,相当于一间教室)
-
容器列表
# 查看正在运行的容器 $ sudo docker container ls # 查看所有的容器 $ sudo docker container ls --all
-
创建容器
$ sudo docker run [option] 镜像名 [向启动容器中传入的命令]
常用可选参数说明:
- -i 表示以《交互模式》运行容器。
- -t 表示容器启动后会进入其命令行。加入这两个参数后,容器创建就能登录进去。即分配一个伪终端。
- –name 为创建的容器命名。
- -v 表示目录映射关系,即宿主机目录:容器中目录。注意:最好做目录映射,在宿主机上做修改,然后共享到容器上。
- -d 会创建一个守护式容器在后台运行(这样创建容器后不会自动登录容器)。
- -p 表示端口映射,即宿主机端口:容器中端口。
- –network=host 表示将主机的网络环境映射到容器中,使容器的网络与主机相同。
-
交互式容器
$ sudo docker run -it --name=ubuntu1 ubuntu /bin/bash
在容器中可以随意执行linux命令,就是一个ubuntu的环境。
当执行 exit 命令退出时,该容器随之停止。 -
守护式容器
# 开启守护式容器 $ sudo docker run -dit --name=ubuntu2 ubuntu # 进入到容器内部交互环境 $ sudo docker exec -it 容器名或容器id 进入后执行的第一个命令 $ sudo docker exec -it ubuntu2 /bin/bash
如果对于一个需要长期运行的容器来说,我们可以创建一个守护式容器。
在容器内部执行 exit 命令退出时,该容器也随之停止。 -
停止和启动容器
# 停止容器 $ sudo docker container stop 容器名或容器id # kill掉容器 $ sudo docker container kill 容器名或容器id # 启动容器 $ sudo docker container start 容器名或容器id
-
删除容器
正在运行的容器无法直接删除。
$ sudo docker container rm 容器名或容器id
-
容器制作成镜像
为保证已经配置完成的环境可以重复利用,我们可以将容器制作成镜像。
# 将容器制作成镜像 $ sudo docker commit 容器名 镜像名 # 镜像打包备份 $ sudo docker save -o 保存的文件名 镜像名 # 镜像解压 $ sudo docker load -i 文件路径/备份文件
-
2.3.Docker和FastDFS上传和下载文件
-
Docker安装运行FastDFS
-
获取FastDFS镜像
# 从仓库拉取镜像 $ sudo docker image pull delron/fastdfs # 解压本地镜像 $ sudo docker load -i 文件路径/fastdfs_docker.tar
-
开启tracker容器
我们将 tracker 运行目录映射到宿主机的 /var/fdfs/tracker目录中。
$ sudo docker run -dit --name tracker --network=host -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker
-
开启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
-
查看宿主机映射路径
注意:如果无法重启storage容器,可以删除/var/fdfs/storage/data目录下的fdfs_storaged.pid 文件,然后重新运行storage。
-
-
FastDFS客户端上传文件
Python版本的FastDFS客户端使用参考文档-
安装FastDFS客户端扩展
安装准备好的fdfs_client-py-master.zip到虚拟环境中
$ pip install fdfs_client-py-master.zip $ pip install mutagen $ pip isntall requests
-
准备FastDFS客户端扩展的配置文件
meiduo_mall.utils.fastdfs.client.conf base_path=FastDFS客户端存放日志文件的目录 tracker_server=运行Tracker服务的机器ip:22122
-
FastDFS客户端实现文件存储
# 使用 shell 进入 Python交互环境 $ python manage.py shell # 1. 导入FastDFS客户端扩展 from fdfs_client.client import Fdfs_client # 2. 创建FastDFS客户端实例 client = Fdfs_client('meiduo_mall/utils/fastdfs/client.conf') # 3. 调用FastDFS客户端上传文件方法 ret = client.upload_by_filename('/Users/zhangjie/Desktop/kk.jpeg') #反馈结果 ret = { 'Group name': 'group1', 'Remote file_id': 'group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg', 'Status': 'Upload successed.', 'Local file name': '/Users/zhangjie/Desktop/kk.jpeg', 'Uploaded size': '69.00KB', 'Storage IP': '192.168.103.158' } #说明 ret = { 'Group name': 'Storage组名', 'Remote file_id': '文件索引,可用于下载', 'Status': '文件上传结果反馈', 'Local file name': '上传文件全路径', 'Uploaded size': '文件大小', 'Storage IP': 'Storage地址' }
-
-
浏览器下载并渲染图片
思考:如何才能找到在Storage中存储的图片?协议:
- http
IP地址:192.168.103.158
1. Nginx服务器的IP地址。 2. 因为 FastDFS 擅长存储静态文件,但是不擅长提供静态文件的下载服务,所以我们一般会将 Nginx 服务器绑定到 Storage , 提升下载性能。
端口:8888
1. Nginx服务器的端口。
路径:group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg
1. 文件在Storage上的文件索引。
完整图片下载地址
http://192.168.103.158:8888/group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg
编写测试代码:meiduo_mall.utils.fdfs_t.html
<img src="http://192.168.103.158:8888/group1/M00/00/00/wKhnnlxw_gmAcoWmAAEXU5wmjPs35.jpeg" width="320" height="480">
2.4.录入商品数据和图片数据
-
SQL脚本录入商品数据
$ mysql -h127.0.0.1 -uroot -pmysql meiduo_mall < 文件路径/goods_data.sql
-
FastDFS服务器录入图片数据
-
准备新的图片数据压缩包
-
删除 Storage 中旧的data目录
-
拷贝新的图片数据压缩包到 Storage,并解压
# 解压命令 sudo tar -zxvf data.tar.gz
-
查看新的data目录
-
3.首页广告
3.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":[] } }
-
查询首页商品频道分类
class 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) # 渲染模板的上下文 context = { 'categories': categories, } return render(request, 'index.html', context)
-
渲染首页商品频道分类
index.html <ul class="sub_menu"> {% for group in categories.values() %} <li> <div class="level1"> {% for channel in group.channels %} <a href="{{ channel.url }}">{{ channel.name }}</a> {% endfor %} </div> <div class="level2"> {% for cat2 in group.sub_cats %} <div class="list_group"> <div class="group_name fl">{{ cat2.name }} ></div> <div class="group_detail fl"> {% for cat3 in cat2.sub_cats %} <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a> {% endfor %} </div> </div> {% endfor %} </div> </li> {% endfor %} </ul>
-
封装首页商品频道分类
-
封装首页商品频道分类到contents.utils.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
-
contents.view.py中使用contents.utils.py文件
class IndexView(View): """首页广告""" def get(self, request): """提供首页广告界面""" # 查询商品频道和分类 categories = get_categories() # 广告数据 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.2展示首页商品广告
-
分析首页商品广告数据结构
结论:- 首页商品广告数据由广告分类和广告内容组成。
- 广告分类带有标识符key,可以利用它确定广告展示的位置。
- 确定广告展示的位置后,再查询和渲染出该位置的广告内容。
- 广告的内容还有内部的排序字段,决定了广告内容的展示顺序。
-
查询首页商品广告
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)
-
渲染首页商品广告
-
轮播图广告
<ul class="slide"> {% for content in contents.index_lbt %} <li><a href="{{ content.url }}"><img src="{{ content.image }}" alt="{{ content.title }}"></a></li> {% endfor %} </ul>
-
快讯和页头广告
<div class="news"> <div class="news_title"> <h3>快讯</h3> <a href="#">更多 ></a> </div> <ul class="news_list"> {% for content in contents.index_kx %} <li><a href="{{ content.url }}">{{ content.title }}</a></li> {% endfor %} </ul> {% for content in contents.index_ytgg %} <a href="{{ content.url }}" class="advs"><img src="{{ content.image }}"></a> {% endfor %} </div>
-
楼层广告(一楼)
<div class="list_model"> <div class="list_title clearfix"> <h3 class="fl" id="model01">1F 手机通讯</h3> <div class="subtitle fr"> <a @mouseenter="f1_tab=1" :class="f1_tab===1?'active':''">时尚新品</a> <a @mouseenter="f1_tab=2" :class="f1_tab===2?'active':''">畅想低价</a> <a @mouseenter="f1_tab=3" :class="f1_tab===3?'active':''">手机配件</a> </div> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"> <img src="{{ contents.index_1f_logo.0.image}}"> <div class="channel"> {% for content in contents.index_1f_pd %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> <div class="key_words"> {% for content in contents.index_1f_bq %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> </div> <div class="goods_list_con"> <ul v-show="f1_tab===1" class="goods_list fl"> {% for content in contents.index_1f_ssxp %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> <ul v-show="f1_tab===2" class="goods_list fl"> {% for content in contents.index_1f_cxdj %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> <ul v-show="f1_tab===3" class="goods_list fl"> {% for content in contents.index_1f_sjpj %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> </div> </div> </div>
-
楼层广告(二楼)
<div class="list_model model02"> <div class="list_title clearfix"> <h3 class="fl" id="model01">2F 电脑数码</h3> <div class="subtitle fr"> <a @mouseenter="f2_tab=1" :class="f2_tab===1?'active':''">加价换购</a> <a @mouseenter="f2_tab=2" :class="f2_tab===2?'active':''">畅享低价</a> </div> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"> <img src="{{ contents.index_2f_logo.0.image}}"> <div class="channel"> {% for content in contents.index_2f_pd %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> <div class="key_words"> {% for content in contents.index_2f_bq %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> </div> <div class="goods_list_con"> <ul v-show="f2_tab===1" class="goods_list fl"> {% for content in contents.index_2f_cxdj %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> <ul v-show="f2_tab===2" class="goods_list fl"> {% for content in contents.index_2f_jjhg %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> </div> </div> </div>
-
楼层广告(三楼)
<div class="list_model model03"> <div class="list_title clearfix"> <h3 class="fl" id="model01">3F 家居家装</h3> <div class="subtitle fr"> <a @mouseenter="f3_tab=1" :class="f3_tab===1?'active':''">生活用品</a> <a @mouseenter="f3_tab=2" :class="f3_tab===2?'active':''">厨房用品</a> </div> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"> <img src="{{ contents.index_3f_logo.0.image }}"> <div class="channel"> {% for content in contents.index_3f_pd %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> <div class="key_words"> {% for content in contents.index_3f_bq %} <a href="{{ content.url }}">{{ content.title }}</a> {% endfor %} </div> </div> <div class="goods_list_con"> <ul v-show="f3_tab===1" class="goods_list fl"> {% for content in contents.index_3f_shyp %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> <ul v-show="f3_tab===2" class="goods_list fl"> {% for content in contents.index_3f_cfyp %} <li> <a href="{{ content.url }}" class="goods_pic"><img src="{{ content.image }}"></a> <h4><a href="{{ content.url }}" title="{{ content.title }}">{{ content.title }}</a></h4> <div class="price">{{ content.text }}</div> </li> {% endfor %} </ul> </div> </div> </div>
-
3.3.自定义Django文件存储类
思考:
下图首页页面中图片无法显示的原因。
结论:
通过FastDFS上传文件后返回的'Remote file_id'字段是文件索引。
文件索引会被我们存储到MySQL数据库。所以将来读取出来的也是文件索引,导致界面无法下载到图片。
解决:
重写Django文件存储类的url()方法。
在重写时拼接完整的图片下载地址(协议、IP、端口、文件索引)
- Django文件存储类url()方法介绍
结论:
- 文件存储类url()方法的作用:返回name所代表的文件内容的URL。
- 文件存储类url()方法的触发:content.image.url
- 虽然表面上调用的是ImageField的url方法。但是内部会去调用文件存储类的url()方法。
- 文件存储类url()方法的使用:
- 我们可以通过自定义Django文件存储类达到重写url()方法的目的。
- 自定义Django文件存储类必须提供url()方法。
- 返回name所指的文件对应的绝对URL。
-
自定义Django文件存储类
自定义文件存储类官方文档class FastDFSStorage(Storage): """自定义文件存储系统""" def _open(self, name, mode='rb'): """ 用于打开文件 :param name: 要打开的文件的名字 :param mode: 打开文件方式 :return: None """ # 打开文件时使用的,此时不需要,而文档告诉说明必须实现,所以pass pass def _save(self, name, content): """ 用于保存文件 :param name: 要保存的文件名字 :param content: 要保存的文件的内容 :return: None """ # 保存文件时使用的,此时不需要,而文档告诉说明必须实现,所以pass pass
-
重写Django文件存储类url()方法
-
重写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_URL def _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/' + name return self.fdfs_base_url + name
-
相关配置参数
# 指定自定义的Django文件存储类 DEFAULT_FILE_STORAGE = 'meiduo_mall.utils.fastdfs.fdfs_storage.FastDFSStorage' # FastDFS相关参数 # FDFS_BASE_URL = 'http://192.168.103.158:8888/' FDFS_BASE_URL = 'http://image.meiduo.site:8888/'
-
添加访问图片的域名
在/etc/hosts中添加访问Storage的域名
$ Storage的IP 域名 $ 192.168.103.158 image.meiduo.site
-
文件存储类url()方法的使用
以图片轮播图为例:content.image.url
<ul class="slide"> {% for content in contents.index_lbt %} <li><a href="{{ content.url }}"><img src="{{ content.image.url }}" alt="{{ content.title }}"></a></li> {% endfor %} </ul>
-
- 文化也许产生不了利润,但文化是精神上的原子弹