



如下转载于http://www.javashuo.com/article/p-zmycnkvk-g.html 仅供本人学习和研究html
商城商业模式:
C2B模式(消费者到企业的商业模式),相相似网站包括:京东,淘宝,海尔商城,尚品宅配等。前端
商城需求分析
1,用户部分
2,商品部分
3,购物车部分
4,商品订单备份
5,用户支付部分
6,上线程序的配置python
用户部分模块:
基本功能:用户注册,登陆,密码的重置,第三方登陆
用户注册
1,图片验证码
流程分析:
1,前端生成uuid随机字符串
2,后端生成图片验证码发送给前端,将图形验证码的存入到redis中
2,短信验证码
1,检查图片的验证码
2,检验是不是在60s内是否已经发送过
3,生成短信验证码
4,保存发送的短信验证码
5,发送短信(第三方平台发送:云通信)
3,判断用户名是否存在
1,用户输入用户名以后ajax局部刷新页面
2,后台查询数据库用户是否存在
3,返回数据给前端
4,判断手机号码是否已经存在
同3git
技术点:先后端的域名不相同,涉及到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的相关了解
-
cookies的使用目的ajax
- http协议本生是一种无状态的协议,假如用户每次发送请求过来将用户名和密码在后端进行验证后才能够登陆到某些界面才能够进行操做,当客户端再次请求服务器时,又要从新进行认证;
- 解决方法:在客户端设置cookie并在本地设置session存储用户的敏感信息,从而来记录当前用户登陆的状态,若是用户量再次请求服务器,将cookie带给服务器,服务器查询session获取用户信息,进行下一步操做。
- 客户端比较多的状况下,seession中默认会存在内存,随着用户量的增长服务器的压力就会变大;
-
传统的cookies显示出来的问题
- 在如今的市场各类不一样的浏览器,对同一个网站进行,用户的每种设备都须要维护相关的session在服务器端,会形成服务器资源的浪费,相关网站采起单点登陆来记录用户的状态状态来解决以上传统cookies带来的问题redis
-
单点登陆的概念sql
- 用户在不一样的设备中进行登陆,服务器端不用维护用户的相关信息,在每次登陆的过程当中都由客户端将本身的用户信息发送过来,服务端进行解析来获取用户的相关信息
-
token认证的机制docker
- 用户携带用户名和密码来后端进行验证
- 服务器端验证经过后对为当前用户生成token
- 将token返回给前端,记录用户的信息
- 用户再次请求服务器的时候,服务端解析token相关的信息,验证用户
- 肯定用户状态,进行 相关操做
-
备注:数据库
- jwt的组成第一部分咱们称它为头部(header),第二部分咱们称其为载荷(payload, 相似于飞机上承载的物品),第三部分是签证(signature).
- secretkey是存储在服务器端的,若是secret key
-
用户登陆JWT认证的的流程源代码(待继续理解)django
-
qq登陆
-
qq登陆流程oauth2的认证流程分析
附件0.00KB
- 用户向美多网站发送qq注册的请求
- 美多网站向用户返回qq登陆的页面
- 用户在qq登陆的界面向qq的服务器发送qq用户名和密码发起登陆的请求
- qq服务器认证成功以后将用户引导到回调的网址中,并返回给用户qq服务器的token值
- 用户重定向到美多页面并携带了qq服务器发送的token
- 后端接收到token后向qq服务器请求access token
- qq服务器返回access token
- 美多服务器经过access token来向qq服务器来获取用户的openid
- 经过id来查询用户是否已经在美多商城注册
- 用户已经注册直接返回用户的access token值
- 用户没有帐号,生成注册的access token,(载荷openid)从新注册信息发送给后端
- 后端接收到数据以后建立对象并将qq用户的openid和帐号进行绑定
-
忘记密码的功能的实现
- 用户发送过来请求,携带用户名和图片验证码
- 后端验证图片验证码,经过帐号查询用户,将用户的电话号码部分返回给前端,并生成发送短信的access token(载荷mobile)值
- 前端填写手机号码验证码并携带access token到后端
- 后端接收到手机号码校验(正确性,发送时间间隔),经过手机号码站到用户对象,生成密码修改的access token(载荷uer obj)值
- 前端用户填写新的密码以后,携带access token到后端重置密码
技能点
- 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有本身的私钥才能够打开最外层的包裹信息)
