本人转载于https://www.cnblogs.com/cerofang/p/9457875.html 仅供本人学习和研究
商城商业模式:
C2B模式(消费者到企业的商业模式),相类似网站包括:京东,淘宝,海尔商城,尚品宅配等。
商城需求分析
1,用户部分
2,商品部分
3,购物车部分
4,商品订单备份
5,用户支付部分
6,上线程序的配置
用户部分模块:
基本功能:用户注册,登录,密码的重置,第三方登录
用户注册
1,图片验证码
流程分析:
1,前端生成uuid随机字符串
2,后端生成图片验证码发送给前端,将图形验证码的存入到redis中
2,短信验证码
1,检查图片的验证码
2,检验是否是在60s内是否已经发送过
3,生成短信验证码
4,保存发送的短信验证码
5,发送短信(第三方平台发送:云通讯)
3,判断用户名是否存在
1,用户输入用户名之后ajax局部刷新页面
2,后台查询数据库用户是否存在
3,返回数据给前端
4,判断手机号码是否已经存在
同3
技术点:前后端的域名不相同,涉及到csrf跨站请求伪造的问题;
- Csrf相关概念:
1,域=协议+域名+端口,在两个域中,以上三者中任意一个条件不同,均涉及到跨域的问题;
2,浏览器的策略
1,对于简单的请求,浏览器发送请求,但是得到请求之后会检验响应头中是否有当前的域中,如果没有则会在浏览器中报错;
2,对于复杂的请求,浏览器会先发送一个option请求,询问服务器是否支持跨域,如果响应头中的域名允许,才会发送相对应的请求来获取数据,并交给js进行处理。
3,Python的django中的跨域处理的相关模块django-cors-headers
技术点:前端用户将图片验证码发送给后台之后,第三方平台发送短信的过程中会有网络的阻塞程序继续往下执行,进而影响用户体验效果;
- 解决方案:采用celery进行短信验证码的异步发送;
Celery概念:分布式异步任务队列调度框架:
1,支持定时任务和异步任务两种方式
2,组成:大概分为四个部分client客户端发送数据,broker中间件(redis数据库,消息队列),worker(任务的执行者),backend(执行worker任务的执行结果)
3,可以开启多进程也可以是多线程
4,应用场景:在某一个任务的执行过程中,会涉及到耗时的操作,但是这个耗时操作并不会影响后续的程序的执行,此时就可以用celery来异步执行这些任务;
用户登录
JWTtoken的相关了解
技能点
- djangorestframework中的实现JWT token的模块itsdangerous的使用
用户中心
技术点:
- django中发送邮件的配置信息
- 利用celery来实现异步的邮件发送
用户收货地址的设置
from rest_framework import serializers
from .models import Area
class AreaSerializer(serializers.ModelSerializer):
"""
行政区划信息序列化器
"""
class Meta:
model = Area
fields = (‘id‘, ‘name‘)
class SubAreaSerializer(serializers.ModelSerializer):
"""
子行政区划信息序列化器
"""
subs = AreaSerializer(many=True, read_only=True)
class Meta:
model = Area
fields = (‘id‘, ‘name‘, ‘subs‘)
- DRF中的ReadOnlyModelViewSet中将请求方式与资源状态进行了绑定,在这里我们只需要从数据库中去数据所以直接就可以继承ModelSerializer这个类
- view视图中的action==‘list‘(即前端url不带参数)说明前端要获取所有的省份
- view视图中的action!=‘list‘(即前端url带参数)说明前端要获取所有的省份底下的行政区划
- 在这个返回的过程中如果前端页面返回的url中返回的带有参数则返回省份
技术点
- DRF的扩展类中的选择以及序列化器的嵌套调用方法
- 对DRF的扩展集的理解
- Views django 中的原始的Views
- APIView继承类django中的Views,同时提供了用户认证,权限认证,权限认证,节流认证,分页,序列化等方法
- GenericAPIView 继承了APIView:在这个类中实现了两个类实行和三个类方法
- ListModelMixin 实现了list方法与get_queryset(),paginate_queryset,get_serializer
- ListAPIView 可用的子类GenericAPIView、ListModelMixin 是上面两种方法的子类
- ViewSetMixin 实现了view = MyViewSet.as_view({‘get‘: ‘list‘, ‘post‘: ‘create‘})
订单模块:
基本功能:提交订单,我的订单,订单评价
FastDFS分布式文件系统
- FastDFS分布式文件系统,数据冗余,数据的备份,数据量的存储扩展
- tracker server的作用是负载均衡和调度,可以实现集群,每个reacker节点的地位平等,收集storage的状态;
- storage server的作用是存储,不同的组内保存的内容是不同的,相同的组内保存的内容是相同的,这样的设计数据会相对比较安全安全;
- 无论是tracker还是storage都支持集群的方式扩展,数据的扩展比较方便
- 文件上传的流程
- storage server定时向tracker server的上传存储状态信息
- 客户端上传链接请求
- 请求会先到达tracker server,tracker server查询可以调用的storage;
- 返回storage server 的ip和port
- 上传文件到指定的storage server中
- srorage server生成file id,并将文件存入到磁盘中
- 返回file id给客户端
- 存储文件信息
docker的理解
- docker是一种虚拟化的技术,我们可以将docker视为一种容器,在容器的内部可以运行服务,
- Docker本身是一种C/S架构的程序,Docker客户端需要向服务器发送请求,服务器完成所有的工作之后返回给客户端结果;
- 优点
- 加速本地开发和构建的流程,在本地可以自己轻松的构建,运行,分享所配置好的docker环境
- 能够在不同的操作系统的环境中获取相同的docker容器中的环境,减少了在部署环节中的环境问题待来的麻烦
- docker可以创建虚拟化的沙箱环境可以供测试使用
- docker可以让开发者在最开始的开发过程中在测试的环境中运行,而并非一开始就在生产环境中开发,部署和测试
首页静态化的技术
-
电商类型的网站首页的访问评率较大,每次获取首页过程中去对数据库进行查询显然不太合理,除了使用传统意义上的缓存实现之外,我们可以使用首页静态化的技术,即将动态生成的html页面生成保存成静态文件,在用户访问的过程中直接将静态文件发送给用户
-
优点:可以缓解数据库压力,并且可以提高用户访问的网站的速度,提高用户体验
-
带来的问题是:用户在页面中动态生成的部分数据如何处理
- 在静态页面加载完成之后,通过js的代码将动态的请求发送到后天请求数据,但是大部分数据均是静态页面中的数据,
- 静态生成的页面因为并没有实时更新,会出现部分商品的静态化页面中的数据和数据库中实时更新的数据有差异
-
应用场景:经常容易访问但是,数据的变动并不是太大的一些页面可以考虑使用静态化技术
-
难点GoodsCategory,GoodsChannel两个表格之间的关系设计以及获取商城商品分类的菜单
- 首先是GoodsChannel,将所有的商品的频道取出按照组号和组内顺序排序
- 排序后将数据以categories[group_id] = {‘channels‘: [], ‘sub_cats‘: []}的形式存入到有个有序的字典中
- channels的值为存储的是频道的相关信息(例如手机,相机,数码)
- sub_cats中存储的值是该频道下的GoodsCategory相关信息(例如手机通讯,手机配件...相关,根据GoodsChannel数据结构表中顶级类别来查询子类别)
- 分别为顶级类别下的子类别对象添加一个sub_cats的列表,来存储此类别下的所有GoodsCategory的queryset对象
-
难点 商品详情页面的数据结构
当前商品的规格键[1,4,7] [1, 4, 7]
获取当前商品的所在的SPU下的所有SKU对象 <QuerySet [<SKU: 1: Apple MacBook Pro 13.3英寸笔记本 银色>, <SKU: 2: Apple MacBook Pro 13.3英寸笔记本 深灰色>]>
{(1, 4, 7): 1, (1, 3, 7): 2}构造出的不同规格的参数 {(1, 4, 7): 1, (1, 3, 7): 2}
当前商品所有的规格选项,屏幕,颜色,,, <QuerySet [<GoodsSpecification: Apple MacBook Pro 笔记本: 屏幕尺寸>, <GoodsSpecification: Apple MacBook Pro 笔记本: 颜色>, <GoodsSpecification: Apple MacBook Pro 笔记本: 版本>]>
当前的规格选项 屏幕尺寸
options规格信息的选项 <QuerySet [<SpecificationOption: Apple MacBook Pro 笔记本: 屏幕尺寸 - 13.3英寸>, <SpecificationOption: Apple MacBook Pro 笔记本: 屏幕尺寸 - 15.4英寸>]>
001 [1, 4, 7]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 1
002 [1, 4, 7]
1
001 [1, 4, 7]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 2
002 [2, 4, 7]
None
当前的规格选项 颜色
options规格信息的选项 <QuerySet [<SpecificationOption: Apple MacBook Pro 笔记本: 颜色 - 深灰色>, <SpecificationOption: Apple MacBook Pro 笔记本: 颜色 - 银色>]>
001 [1, 4, 7]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 3
002 [1, 3, 7]
2
001 [1, 3, 7]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 4
002 [1, 4, 7]
1
当前的规格选项 版本
options规格信息的选项 <QuerySet [<SpecificationOption: Apple MacBook Pro 笔记本: 版本 - core i5/8G内存/256G存储>, <SpecificationOption: Apple MacBook Pro 笔记本: 版本 - core i5/8G内存/128G存储>, <SpecificationOption: Apple MacBook Pro 笔记本: 版本 - core i5/8G内存/512G存储>]>
001 [1, 4, 7]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 5
002 [1, 4, 5]
None
001 [1, 4, 5]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 6
002 [1, 4, 6]
None
001 [1, 4, 6]
固定不变的数据库中只有这两种商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 7
002 [1, 4, 7]
1
specs = [
{
‘name‘: ‘屏幕尺寸‘,
‘options‘: [
{‘value‘: ‘13.3寸‘, ‘sku_id‘: xxx},
{‘value‘: ‘15.4寸‘, ‘sku_id‘: xxx},
]
},
{
‘name‘: ‘颜色‘,
‘options‘: [
{‘value‘: ‘银色‘, ‘sku_id‘: xxx},
{‘value‘: ‘黑色‘, ‘sku_id‘: xxx}
]
},
...
]
- 通过sku id来取出此sku商品的SPU对应的所有存在的商品组合
- 循环数据库中所有的商品选项,将商品的选项与sku id来做对应,返回上面的数据类型
- 相同的SPU对应的不同的SKU,返回的specs是相同的,例如如果同属于手机这个SPU的Iphone6手机和Iphone7手机,返回的specs是相同的,若假设手机品牌只有屏幕大小不相同,则返回的数据类型如下
specs = [
{
‘name‘: ‘屏幕尺寸‘,
‘options‘: [
{‘value‘: ‘13.3寸‘, ‘sku_id‘: Iphone7的sku_id},
{‘value‘: ‘15.4寸‘, ‘sku_id‘: Iphone6的sku_id},
{‘value‘: ‘15.4寸‘, ‘sku_id‘: Iphone7的sku_id2},
]
},
{
‘name‘: ‘颜色‘,
‘options‘: [
{‘value‘: ‘银色‘, ‘sku_id‘: Iphone7的sku_id},
{‘value‘: ‘银色‘, ‘sku_id‘: Iphone6的sku_id},
{‘value‘: ‘金色‘, ‘sku_id‘: Iphone7的sku_id2},
]
},
...
]
-
前端通过传入的sku id来取值生成的详情页面,从同一份数据数据中拿的值,就会避免重复,例如Iphone6的只是取出所有的Iphone6的所有的信息生成静态页面,传入的sku id不同得到的页面效果不同,通过不同的id也可以找到不同的商品的详情页面
-
细节完善在运营人员相关修改商品信息,要在后端实现,自动刷新详情的页面的数据,自动触发静态页面的生成,利用了django中的ModelAdmin,在数据发生变动之后自动进行更新相关数据
class SKUSpecificationAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save()
from celery_tasks.html.tasks import generate_static_sku_detail_html
generate_static_sku_detail_html.delay(obj.sku.id)
def delete_model(self, request, obj):
sku_id = obj.sku.id
obj.delete()
from celery_tasks.html.tasks import generate_static_sku_detail_html
generate_static_sku_detail_html.delay(sku_id)
获取热销商品
- 技术点
- 详情页面中的热销商品每个页面加载完成之后都会来向后端请求数据,但是热销商品却不经常发生变化或者是在一段时间内根据相关字段统计生成返回给前端即可,所有使用缓存的方式存储热销商品是最合理的方式,避免了数据的链接,减少了服务器的压力,充分的利用了缓存的响应速度也比较快可以提高用户的体验
商品列表页面的展示
- 商品数据动态的从后端获取,其他部分生成静态化页面
- 技术点:
- DRF框架中过滤,排序,分页,序列化器数据的返回
- 适当使用到了DRF提供的扩展类ListAPIView来简化代码
商品搜索的功能实现
- 技术点
- Elasticsearch搜索引擎的原理:通过搜索引擎进行搜索数据查询的过程中,搜索引擎并不是直接去数据库中去进行数据库的查询,而是搜素引擎会对数据库中所有的数据进行一遍的预处理,单独的建立一份索引表,在进行数据库查询的过程中,会在索引表中进行查询,然后返回相应的数据。
- Elasticsearch 不支持对中文进行分词建立索引,需要配合扩展elasticsearch-analysis-ik来实现中文分词处理
- 使用haystack对接Elasticsearch的流程
- 安装
- 在配置文件中配置haystack使用的搜索引擎后端
- SKU索引数据模型类
- 在templates目录中创建text字段使用的模板文件
- 手动生成初始索引
- 创建序列化器
- 创建视图
- 定义路由
购物车模块
class CartMixin():
def str2dict(self, redis_data):
def merge_cart_cookie_to_redis(request, response, user):
""" 合并购物车"""
cookies_cart = request.COOKIES.get(‘cart‘)
if cookies_cart is not None:
cookies_dict = pickle.loads(base64.b64decode(cookies_cart))
- 数据类型的设计原则:
- 尽量将cookies中的数据类型格式与redis数据库中数据类型设计成形同的
- 对redis数据库的相关操作
- {sku id :[count,True]}数据中sku id:商品的id,count:购物车中数据的商品的个数;True或者False代表是否被选中
- 将对数据的操作封装成一个扩展集,在视图中继承扩展类
订单相关的操作
- 订单数据库表的设计
- 订单的字段分析
- 首先将订单分为两个表格
- 1,订单的基本信息表;
- 2,订单的商品信息,两者之间的关系是一对多的关系
- 订单的基本信息表中主要存储这笔订单的相关信息(订单的id,下单的用户,用户的默认地址,邮费的状态,订单的支付方式,订单的状态)
- 订单商品中保存(订单的id,用户商品的id,商品的数量,商品的价格)
- 在前端中展示还需要的字段有此次订单的总金额,以及该订单中商品的数量,这两个字段虽然经过表格之间的关联可以查询出来,在这里可以将这两个字段一起定义在订单的基本信息的表格中,避免在后续查询订单的过程中对数据库的操作;
具体字段的定义:
from django.db import models
from meiduo_mall.utils.models import BaseModel
from users.models import User, Address
from goods.models import SKU
获取购物车商品逻辑
- 用户必须在登录的状态下才可以进入到购物车商品结算的页面
- 查询到当前订单的用户;
- 在redis数据库中将当前用户的购物车中所有商品查询出来
- 过滤筛选出用户选中的商品信息的id
- 查询出当前订单的运费的
- 将相关信息(例如运费和查询出来的商品的skus对象)传递给序列化器,序列化器将数据从数据库中序列化后返回给前端
{
"freight":"10.00",
"skus":[
{
"id":10,
"name":"华为 HUAWEI P10 Plus 6GB+128GB 钻雕金 移动联通电信4G手机 双卡双待",
"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRchWAMc8rAARfIK95am88158618",
"price":"3788.00",
"count":1
},
{
"id":16,
"name":"华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651",
"price":"3788.00",
"count":1
}
]
}
保存订单
- id的字段定义,默认情况下采用sql数据库中的自增的id作为主键,但是在考虑到id的可读性和扩展性将主键设置为具有特定格式的CharField字段,自己定义的id的格式的;
- 保存订单的逻辑实现
- 获取当前下单的用户
- 获取用户的基本信息(用户的默认地址,用户选择的支付方式)
- 创建事物,在以下的任何一个操作不成功的情况下就会返回到当前这个保存点
- 组织订单的id
- 创建一个订单基本信息的对象,进行保存
- redis中取出所有的商品,过滤出用户选中的商品
- 给订单的中设计的冗余的字段赋初值
- 从数据库中查询商品信息,并判断用户购买的商品库存状态和销量的状态
- 更新数据库中库存和销量的相关信息。在这个地方用乐观锁的方式来判断在事物保存的过程中是否有其他用户来操作过商品的,如果有则重新返回到事物保存点,没有则继续
- 在这里来查询用户的商品信息表中将订单基本信息的表格中的相关冗余字段计算出来一起保存进入到数据库中
class SaveOrderSerializer(serializers.ModelSerializer):
""" 用户支付订单的创建序列化器"""
class Meta:
model = OrderInfo
fields = ("order_id", "address", "pay_method")
read_only_fields = ("order_id",)
extra_kwargs = {
"address": {
"write_only": True,
"required": True
},
"pay_method": {
"write_only": True,
"required": True
}
}
def create(self, validated_data):
""" 保存订单序列化器"""
- 数据库的事物
- Django中对于数据库的操作,默认在每一次的数据库操作之后都会自动提交
- 在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法
- 使用方法一:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
from django.db import transaction
支付模块:
import os
from alipay import AliPay
from django.conf import settings
from django.shortcuts import render
-
数据加密的过程
- 在双方进行通信的过程中,若A要给B发送消息
- 互相交换公钥密码
- 在数据包的发送过程中,A先用自己的私钥加密(保证数据的安全的,至少不会明文显示)。
- 再用B交给A的公钥进行加密(只有B有自己的私钥才可以打开最外层的包裹信息)

商城项目的总结
原文:https://www.cnblogs.com/Jack666/p/9775478.html