网上商城之购物车

购物车存储方案

在这里插入图片描述

  • 用户登录与未登录状态下,都可以保存购物车数据。
  • 用户对购物车数据的操作包括:增、删、改、查、全选等等
  • 每个用户的购物车数据都要做唯一性的标识。

1. 登录用户购物车存储方案(比市面上的难度要大)

  1. 存储数据说明
  • 如何描述一条完整的购物车记录?
    • 用户itcast,选择了两个 iPhone8 添加到了购物车中,状态为勾选
  • 一条完整的购物车记录包括:用户、商品、数量、勾选状态。
  • 存储数据:user_id、sku_id、count、selected
  1. 存储位置说明
  • 购物车数据量小,结构简单,更新频繁,所以我们选择内存型数据库Redis进行存储。
  • mysql性能比较差,擅长存复杂数据结构
  • 存储位置:Redis数据库 4号库
"carts": {
    "BACKEND": "django_redis.cache.RedisCache",
    "LOCATION": "redis://127.0.0.1:6379/4",
    "OPTIONS": {
        "CLIENT_CLASS": "django_redis.client.DefaultClient",
    }
},
  1. 存储类型说明
  • 提示:我们很难将用户、商品、数量、勾选状态存放到一条Redis记录中。所以我们要把购物车数据合理的分开存储。

如果第一时间找不到方案,可以采用遍历的方法

  • 用户、商品、数量:hash
    • carts_user_id: {sku_id1: count, sku_id3: count, sku_id5: count, …}
  • 勾选状态:set(购物车对顺序没有要求,集合有自动去重的功能)
    • 只将已勾选商品的sku_id存储到set中,比如,1号和3号商品是被勾选的。
    • selected_user_id: [sku_id1, sku_id3, …]
  1. 存储逻辑说明
    在这里插入图片描述
  • 当要添加到购物车的商品已存在时,对商品数量进行累加计算。
  • 当要添加到购物车的商品不存在时,向hash中新增field和value即可。
笔记

mset k1 v1 k2 v2 …

2. 未登录用户购物车存储方案

  1. 存储数据说明

存储数据:user_id、sku_id、count、selected
2. 存储位置说明

  • 由于用户未登录,服务端无法拿到用户的ID,所以服务端在生成购物车记录时很难唯一标识该记录。
  • 我们可以将未登录用户的购物车数据缓存到用户浏览器的cookie中,每个用户自己浏览器的cookie中存储属于自己的购物车数据。
  • 存储位置:用户浏览器的cookie
  1. 存储类型说明
  • 提示:浏览器的cookie中存储的数据类型是字符串。
  • 思考:如何在字符串中描述一条购物车记录?
  • 结论:JSON字符串可以描述复杂结构的字符串数据,可以保证一条购物车记录不用分开存储。
{
    "sku_id1":{
        "count":"1",
        "selected":"True"
    },
    "sku_id3":{
        "count":"3",
        "selected":"True"
    },
    "sku_id5":{
        "count":"3",
        "selected":"False"
    }
}

在这里插入图片描述
4. 存储逻辑说明

  • 当要添加到购物车的商品已存在时,对商品数量进行累加计算。

  • 当要添加到购物车的商品不存在时,向JSON中新增field和value即可。
    提示

  • 浏览器cookie中存储的是字符串明文数据。

    • 我们需要对购物车这类隐私数据进行密文存储。
  • 解决方案:pickle模块base64模块

  1. pickle模块介绍
  • pickle模块是Python的标准模块,提供了对Python数据的序列化操作,可以将数据转换为bytes类型,且序列化速度快。(性能比json,isdangerous性能哈)
  • pickle模块使用:
    • pickle.dumps()将Python数据序列化为bytes类型数据。
    • pickle.loads()将bytes类型数据反序列化为python数据。
>>> import pickle

>>> dict = {'1': {'count': 10, 'selected': True}, '2': {'count': 20, 'selected': False}}
>>> ret = pickle.dumps(dict)
>>> ret
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'
>>> pickle.loads(ret)
{'1': {'count': 10, 'selected': True}, '2': {'count': 20, 'selected': False}}
  1. base64模块介绍
  • 提示:pickle模块序列化转换后的数据是bytes类型,浏览器cookie无法存储。
  • base64模块是Python的标准模块,可以对bytes类型数据进行编码,并得到bytes类型的密文数据。
  • base64模块使用:
    • base64.b64encode()将bytes类型数据进行base64编码,返回编码后的bytes类型数据。
    • base64.b64deocde()将base64编码后的bytes类型数据进行解码,返回解码 后的bytes类型数据。
>>> import base64
>>> ret
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'
>>> b = base64.b64encode(ret)
>>> b
b'gAN9cQAoWAEAAAAxcQF9cQIoWAUAAABjb3VudHEDSwpYCAAAAHNlbGVjdGVkcQSIdVgBAAAAMnEFfXEGKGgDSxRoBIl1dS4='
>>> base64.b64decode(b)
b'\x80\x03}q\x00(X\x01\x00\x00\x001q\x01}q\x02(X\x05\x00\x00\x00countq\x03K\nX\x08\x00\x00\x00selectedq\x04\x88uX\x01\x00\x00\x002q\x05}q\x06(h\x03K\x14h\x04\x89uu.'

在这里插入图片描述

笔记
  1. xml存储同样的数据比json占得空间大得多
  2. cookie 尽量不存明文数据
  3. 签名好像是外人读不懂,自己可以读懂,加密好像是都读不懂
  4. 这里使用这两个模块,是因为isdangerous用过了,还有就是这两个模块的性能更好
  5. 标准库模块直接import就好
  6. 操作购物串的增删改查,是通过字典来做的,这样比直接操作字符串绝对简单
  7. 二进制数据不是最后签名的结果,还需要转换为字符串
  8. bytes64 能对任何类型的数据进行编码和解码,能编成你不认识的,也能解成你认识的
    在这里插入图片描述
  9. 二进制数据还需要一步解码
  10. 还原:首先通过encode 转换为byte类型 ,在通过b64decode转换为 上一种二进制数据,再通过pickle.loads 转换为原始字典
  11. pickle 和 base64 多用几次

购物车管理

创建子应用
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

添加购物车

提示:在商品详情页添加购物车使用局部刷新的效果。

1. 添加购物车接口设计和定义

  1. 请求方式(ajax)
选项方案
请求方法POST
请求地址/carts/
  1. 请求参数:JSON(前端向后端发送ajax请求)
参数名类型是否必传说明
sku_idint商品SKU编号
countint商品数量
selectedbool是否勾选
  1. 响应结果:JSON
字段说明
code状态码
errmsg错误信息
  1. 后端接口定义
class CartsView(View):
    """购物车管理"""

    def post(self, request):
        """添加购物车"""
        # 接收和校验参数
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,操作redis购物车
            pass
        else:
            # 用户未登录,操作cookie购物车
            pass

2. 添加购物车后端逻辑实现

  1. 接收和校验参数
class CartsView(View):
    """购物车管理"""

    def post(self, request):
        """添加购物车"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        count = json_dict.get('count')
        selected = json_dict.get('selected', True)

        # 判断参数是否齐全
        if not all([sku_id, count]):
            return http.HttpResponseForbidden('缺少必传参数')
        # 判断sku_id是否存在
        try:
            models.SKU.objects.get(id=sku_id)
        except models.SKU.DoesNotExist:
            return http.HttpResponseForbidden('商品不存在')
        # 判断count是否为数字
        try:
            count = int(count)
        except Exception:
            return http.HttpResponseForbidden('参数count有误')
        # 判断selected是否为bool值
        if selected:
            if not isinstance(selected, bool):
                return http.HttpResponseForbidden('参数selected有误')

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,操作redis购物车
            pass
        else:
            # 用户未登录,操作cookie购物车
            pass
  1. 添加购物车到Redis
class CartsView(View):
    """购物车管理"""

    def post(self, request):
        """添加购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,操作redis购物车
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()
            # 新增购物车数据,第一个数据为key
            # 有数据做count累加,没有就新增,count为指定值
            pl.hincrby('carts_%s' % user.id, sku_id, count)
            # 新增选中的状态,select默认为true
            if selected:
                pl.sadd('selected_%s' % user.id, sku_id)
            # 执行管道
            pl.execute()
            # 响应结果
            return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加购物车成功'})
        else:
            # 用户未登录,操作cookie购物车
            pass
  1. 添加购物车到cookie
class CartsView(View):
    """购物车管理"""

    def post(self, request):
        """添加购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,操作redis购物车
            ......
        else:
            # 用户未登录,操作cookie购物车,得到的是一个字符串
            cart_str = request.COOKIES.get('carts')
            # 如果用户操作过cookie购物车
            if cart_str:
                # 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:  # 用户从没有操作过cookie购物车
                cart_dict = {}

            # 判断要加入购物车的商品是否已经在购物车中,如有相同商品,累加求和,反之,直接赋值
            if sku_id in cart_dict:
                # 累加求和
                origin_count = cart_dict[sku_id]['count']
                count += origin_count
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }
            # 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
            cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()

            # 创建响应对象
            response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加购物车成功'})
            # 响应结果并将购物车数据写入到cookie
            # 先构造响应,再返回数据
            response.set_cookie('carts', cookie_cart_str)
            return response
笔记
  1. View 位于Django.views
  2. render 位于 Django.shortcuts
  3. 这里不使用LoginRequiredMixin ,因为无法对未登录用户操作,当登录用户和未登录用户都可进入视图,并且对两种用户的响应不同,需要手动判断用户是否登录
  4. 在这里插入图片描述
    解析之后得到个字典
  5. 在这里插入图片描述
    判断是否传了这些参数
  6. 在这里插入图片描述
    应该用id = sku_id
    使用哪个类用哪个类下的异常
  7. 在这里插入图片描述
    看第一个对象是不是第二个类或其子类的实例对象

在这里插入图片描述
如果没有取到值的话,取第二个参数的值

  1. 在这里插入图片描述
    用户登录 为 true,未登录为false,在该写pass的地方不写,可能会报错

在这里插入图片描述
实现当数据存在做增量,不存在做新建

  1. 在这里插入图片描述
    绑定加入购物车事件

  2. 在这里插入图片描述
    可以一下删除两条数据

  3. 在这里插入图片描述
    退出登录之后这两个数据都为空值

  4. 在这里插入图片描述
    导入多个模块

  5. cookie只能存字符串

展示购物车

在这里插入图片描述

展示购物车接口设计和定义

  1. 请求方式
选项方案
请求方法GET
请求地址/carts/

在相同视图中完成这个逻辑

  1. 请求参数:

  1. 响应结果:HTML

cart.html

  1. 后端接口定义
class CartsView(View):
    """购物车管理"""

    def get(self, request):
        """展示购物车"""
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询redis购物车
            pass
        else:
            # 用户未登录,查询cookies购物车
            pass

展示购物车后端逻辑实现

  1. 查询Redis购物车
class CartsView(View):
    """购物车管理"""

    def get(self, request):
        """展示购物车"""
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询redis购物车
            redis_conn = get_redis_connection('carts')
            # 获取redis中的购物车数据
            # 
            redis_cart = redis_conn.hgetall('carts_%s' % user.id)
            # 获取redis中的选中状态
            cart_selected = redis_conn.smembers('selected_%s' % user.id)

            # 将redis中的数据构造成跟cookie中的格式一致,方便统一查询
            cart_dict = {}
            for sku_id, count in redis_cart.items():
                cart_dict[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in cart_selected
                }
        else:
            # 用户未登录,查询cookies购物车
            pass
  1. 查询cookie购物车
class CartsView(View):
    """购物车管理"""

    def get(self, request):
        """展示购物车"""
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询redis购物车
            ......
        else:
            # 用户未登录,查询cookies购物车
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                # 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
  1. 查询购物车SKU信息
class CartsView(View):
    """购物车管理"""

    def get(self, request):
        """展示购物车"""
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询redis购物车
            ......
        else:
            # 用户未登录,查询cookies购物车
            ......

        # 构造购物车渲染数据
        sku_ids = cart_dict.keys()
        skus = models.SKU.objects.filter(id__in=sku_ids)
        cart_skus = []
        for sku in skus:
            cart_skus.append({
                'id':sku.id,
                'name':sku.name,
                'count': cart_dict.get(sku.id).get('count'),
                'selected': str(cart_dict.get(sku.id).get('selected')),  # 将True,转'True',方便json解析
                'default_image_url':sku.default_image.url,
                'price':str(sku.price), # 从Decimal('10.2')中取出'10.2',方便json解析
                'amount':str(sku.price * cart_dict.get(sku.id).get('count')),
            })

        context = {
            'cart_skus':cart_skus,
        }

        # 渲染购物车页面
        return render(request, 'cart.html', context)
  1. 渲染购物车信息
<div class="total_count">全部商品<em>[[ total_count ]]</em></div>
<ul class="cart_list_th clearfix">
    <li class="col01">商品名称</li>
    <li class="col03">商品价格</li>
    <li class="col04">数量</li>
    <li class="col05">小计</li>
    <li class="col06">操作</li>
</ul>
<ul class="cart_list_td clearfix" v-for="(cart_sku,index) in carts" v-cloak>
    <li class="col01"><input type="checkbox" name="" v-model="cart_sku.selected" @change="update_selected(index)"></li>
    <li class="col02"><img :src="cart_sku.default_image_url"></li>
    <li class="col03">[[ cart_sku.name ]]</li>
    <li class="col05">[[ cart_sku.price ]]元</li>
    <li class="col06">
        <div class="num_add">
            <a @click="on_minus(index)" class="minus fl">-</a>
            <input v-model="cart_sku.count" @blur="on_input(index)" type="text" class="num_show fl">
            <a @click="on_add(index)" class="add fl">+</a>
        </div>
    </li>
    <li class="col07">[[ cart_sku.amount ]]元</li>
    <li class="col08"><a @click="on_delete(index)">删除</a></li>
</ul>
<ul class="settlements" v-cloak>
    <li class="col01"><input type="checkbox" name="" @change="on_selected_all" v-model="selected_all"></li>
    <li class="col02">全选</li>
    <li class="col03">合计(不含运费):<span>¥</span><em>[[ total_selected_amount ]]</em><br>共计<b>[[ total_selected_count ]]</b>件商品</li>
    <li class="col04"><a href="place_order.html">去结算</a></li>
</ul>
笔记
  1. 要修改cart.html 的购物车入口代码
  2. 在这里插入图片描述
  3. 在这里插入图片描述
  4. 模板先把数据给中间人,中间人再交给vue渲染
  5. 两种方式进行查询数据的结构应该构造成一样的,这样就方便渲染了
  6. 在这里插入图片描述
    二进制字符串好像也可以做键
  7. 在这里插入图片描述
    取出的是一个键和值的元祖

在这里插入图片描述

  1. 在这里插入图片描述

判断二进制对象是否在字典中

  1. 用户未登陆的代码直接做代码复用
  2. 在这里插入图片描述
    查询所有键

在这里插入图片描述

在这里插入图片描述
vue不识别python的true

  1. 在这里插入图片描述
    列表有特殊字符,防止转义

  2. 拷贝使用vue渲染界面的代码

修改购物车

提示:在购物车页面修改购物车使用局部刷新的效果。

1. 修改购物车接口设计和定义

  1. 请求方式
选项方案
请求方法PUT
请求地址/carts/
  1. 请求参数:JSON(主要因为ajax实现)
参数名类型是否必传说明
sku_idint商品SKU编号
countint商品数量
selectedbool是否勾选
  1. 响应结果:JSON
字段说明
sku_id商品SKU编号
count商品数量
selected是否勾选
  1. 后端接口定义
class CartsView(View):
    """购物车管理"""

    def put(self, request):
        """修改购物车"""
        # 接收和校验参数
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,修改redis购物车
            pass
        else:
            # 用户未登录,修改cookie购物车
            pass

2. 修改购物车后端逻辑实现

  1. 接收和校验参数
class CartsView(View):
    """购物车管理"""

    def put(self, request):
        """修改购物车"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        count = json_dict.get('count')
        selected = json_dict.get('selected', True)

        # 判断参数是否齐全
        if not all([sku_id, count]):
            return http.HttpResponseForbidden('缺少必传参数')
        # 判断sku_id是否存在
        try:
            sku = models.SKU.objects.get(id=sku_id)
        except models.SKU.DoesNotExist:
            return http.HttpResponseForbidden('商品sku_id不存在')
        # 判断count是否为数字
        try:
            count = int(count)
        except Exception:
            return http.HttpResponseForbidden('参数count有误')
        # 判断selected是否为bool值
        if selected:
            if not isinstance(selected, bool):
                return http.HttpResponseForbidden('参数selected有误')

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,修改redis购物车
            pass
        else:
            # 用户未登录,修改cookie购物车
            pass
  1. 修改Redis购物车
class CartsView(View):
    """购物车管理"""

    def put(self, request):
        """修改购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,修改redis购物车
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()
            # 因为接口设计为幂等的,直接覆盖
            pl.hset('carts_%s' % user.id, sku_id, count)
            # 是否选中
            if selected:
                pl.sadd('selected_%s' % user.id, sku_id)
            else:
                pl.srem('selected_%s' % user.id, sku_id)
            pl.execute()

            # 创建响应对象
            cart_sku = {
                'id':sku_id,
                'count':count,
                'selected':selected,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price,
                'amount': sku.price * count,
            }
            return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'修改购物车成功', 'cart_sku':cart_sku})
        else:
            # 用户未登录,修改cookie购物车
            pass
  1. 修改cookie购物车
class CartsView(View):
    """购物车管理"""

    def put(self, request):
        """修改购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,修改redis购物车
            ......
        else:
            # 用户未登录,修改cookie购物车
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                # 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
            # 因为接口设计为幂等的,直接覆盖
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }
            # 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
            cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()

            # 创建响应对象
            cart_sku = {
                'id': sku_id,
                'count': count,
                'selected': selected,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price,
                'amount': sku.price * count,
            }
            response = http.JsonResponse({'code':RETCODE.OK, 'errmsg':'修改购物车成功', 'cart_sku':cart_sku})
            # 响应结果并将购物车数据写入到cookie
            response.set_cookie('carts', cookie_cart_str, max_age=constants.CARTS_COOKIE_EXPIRES)
            return response
笔记
  1. 修改数据的时候直接进行覆盖写入

  2. 修改数据和新增数据的代码很相似,将新增数据,接收参数,校验参数的代码进行拷贝

  3. 在这里插入图片描述
    将对应条的hash 数据进行覆盖写入

  4. 在这里插入图片描述
    通过 if selected 可以知道是选中还是没选中

  5. 改完代码重新进行测试

  6. 在这里插入图片描述
    点了加号要等响应完成之后数量才显示2,

  7. 测试的时候要测刷新之后数据有没有变化

  8. 用户未登陆先拷贝新增数据用户未登陆的代码
    在这里插入图片描述

删除购物车

提示:在购物车页面删除购物车使用局部刷新的效果。

1. 删除购物车接口设计和定义

  1. 请求方式
选项方案
请求方法DELETE
请求地址/carts/
  1. 请求参数:JSON
参数名类型是否必传说明
sku_idint商品SKU编号
3.响应结果:JSON

字段 说明
code 状态码
errmsg 错误信息
4.后端接口定义

class CartsView(View):
    """购物车管理"""

    def delete(self, request):
        """删除购物车"""
        # 接收和校验参数
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,删除redis购物车
            pass
        else:
            # 用户未登录,删除cookie购物车
            pass

2. 删除购物车后端逻辑实现

  1. 接收和校验参数
class CartsView(View):
    """购物车管理"""

    def delete(self, request):
        """删除购物车"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')

        # 判断sku_id是否存在
        try:
            models.SKU.objects.get(id=sku_id)
        except models.SKU.DoesNotExist:
            return http.HttpResponseForbidden('商品不存在')

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户未登录,删除redis购物车
            pass
        else:
            # 用户未登录,删除cookie购物车
            pass
  1. 删除Redis购物车
class CartsView(View):
    """购物车管理"""

    def delete(self, request):
        """删除购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户未登录,删除redis购物车
            redis_conn = get_redis_connection('carts')
            pl = redis_conn.pipeline()
            # 删除键,就等价于删除了整条记录
            pl.hdel('carts_%s' % user.id, sku_id)
            pl.srem('selected_%s' % user.id, sku_id)
            pl.execute()

            # 删除结束后,没有响应的数据,只需要响应状态码即可
            return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除购物车成功'})
        else:
            # 用户未登录,删除cookie购物车
            pass
  1. 删除cookie购物车
class CartsView(View):
    """购物车管理"""

    def delete(self, request):
        """删除购物车"""
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户未登录,删除redis购物车
            ......
        else:
            # 用户未登录,删除cookie购物车
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                # 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}

            # 创建响应对象
            response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除购物车成功'})
            if sku_id in cart_dict:
                del cart_dict[sku_id]
                # 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串
                cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
                # 响应结果并将购物车数据写入到cookie
                response.set_cookie('carts', cookie_cart_str, max_age=constants.CARTS_COOKIE_EXPIRES)
            return response
笔记
  1. 删除只能一次删除一条数据
  2. 接收参数,校验参数,判断用户是否已经登陆
  3. 对于redis 数据,只要删除key,对应的值会消失
    在这里插入图片描述
    在redis操作的时候,出错都会被忽略
  4. 对于字典,删除key,就能删除一条记录
    在这里插入图片描述
    对于这类易报错的指令,要进行逻辑判断或者是异常捕获
  5. 在这里插入图片描述
    这里显示对指定请求的相应结果

全选购物车

提示:在购物车页面修改购物车使用局部刷新的效果。

1. 全选购物车接口设计和定义

  1. 请求方式
选项方案
请求方法PUT
请求地址/carts/selection/
  1. 请求参数:JSON
参数名类型是否必传说明
selectedbool是否全选
3.响应结果:JSON
字段说明
code状态码
errmsg错误信息
  1. 后端接口定义
class CartsSelectAllView(View):
    """全选购物车"""

    def put(self, request):
        # 接收和校验参数
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,操作redis购物车
            pass
        else:
            # 用户未登录,操作cookie购物车
            pass

2. 全选购物车后端逻辑实现

  1. 接收和校验参数(这段代码直接拷贝)
class CartsSelectAllView(View):
    """全选购物车"""

    def put(self, request):
        # 接收参数
        json_dict = json.loads(request.body.decode())
        selected = json_dict.get('selected', True)

        # 校验参数
        if selected:
            if not isinstance(selected, bool):
                return http.HttpResponseForbidden('参数selected有误')

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户已登录,操作redis购物车
            pass
        else:
            # 用户已登录,操作cookie购物车
            pass
  1. 全选Redis购物车
class CartsSelectAllView(View):
    """全选购物车"""

    def put(self, request):
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户已登录,操作redis购物车
            redis_conn = get_redis_connection('carts')
            cart = redis_conn.hgetall('carts_%s' % user.id)
            sku_id_list = cart.keys()
            if selected:
                # 全选
                redis_conn.sadd('selected_%s' % user.id, *sku_id_list)
            else:
                # 取消全选
                redis_conn.srem('selected_%s' % user.id, *sku_id_list)
            return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '全选购物车成功'})
        else:
            # 用户已登录,操作cookie购物车
            pass
  1. 全选cookie购物车
class CartsSelectAllView(View):
    """全选购物车"""

    def put(self, request):
        # 接收和校验参数
        ......

        # 判断用户是否登录
        user = request.user
        if user is not None and user.is_authenticated:
            # 用户已登录,操作redis购物车
            ......
        else:
            # 用户已登录,操作cookie购物车
            cart = request.COOKIES.get('carts')
            response = http.JsonResponse({'code': RETCODE.OK, 'errmsg': '全选购物车成功'})
            if cart is not None:
                cart = pickle.loads(base64.b64decode(cart.encode()))
                for sku_id in cart:
                    cart[sku_id]['selected'] = selected
                cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
                response.set_cookie('carts', cookie_cart, max_age=constants.CARTS_COOKIE_EXPIRES)

            return response
笔记
  1. 选中与否的数据来自于后端,需要发起请求
  2. 在这里插入图片描述
    在这里插入图片描述
    对于第二个参数为 *args的,传递list,应使用 *list 传递

删除多个数据
在这里插入图片描述

  1. 用户未登陆时,获取数据的代码直接进行拷贝
  2. 在这里插入图片描述
    遍历字典的所有键值

在这里插入图片描述

  1. 详情页使用mysql做的,购物车使用redis 速度有明显差异

合并购物车

需求:用户登录时,将cookie购物车数据合并到Redis购物车数据中。

提示:

  • QQ登录和账号登录时都要进行购物车合并操作。

1. 合并购物车逻辑分析

1.合并方向:cookie购物车数据合并到Redis购物车数据中。
2.合并数据:购物车商品数据和勾选状态。
3.合并方案:
3.1 Redis数据库中的购物车数据保留。
3.2 如果cookie中的购物车数据在Redis数据库中已存在,将cookie购物车数据覆盖Redis购物车数据。
3.3 如果cookie中的购物车数据在Redis数据库中不存在,将cookie购物车数据新增到Redis。
3.4 最终购物车的勾选状态以cookie购物车勾选状态为准。

2. 合并购物车逻辑实现

新建文件:carts.utils.py

def merge_cart_cookie_to_redis(request, user, response):
    """
    登录后合并cookie购物车数据到Redis
    :param request: 本次请求对象,获取cookie中的数据
    :param response: 本次响应对象,清除cookie中的数据
    :param user: 登录用户信息,获取user_id
    :return: response
    """
    # 获取cookie中的购物车数据
    cookie_cart_str = request.COOKIES.get('carts')
    # cookie中没有数据就响应结果
    if not cookie_cart_str:
        return response
    cookie_cart_dict = pickle.loads(base64.b64decode(cookie_cart_str.encode()))

    new_cart_dict = {}
    new_cart_selected_add = []
    new_cart_selected_remove = []
    # 同步cookie中购物车数据
    for sku_id, cookie_dict in cookie_cart_dict.items():
        new_cart_dict[sku_id] = cookie_dict['count']

        if cookie_dict['selected']:
            new_cart_selected_add.append(sku_id)
        else:
            new_cart_selected_remove.append(sku_id)

    # 将new_cart_dict写入到Redis数据库
    redis_conn = get_redis_connection('carts')
    pl = redis_conn.pipeline()
    pl.hmset('carts_%s' % user.id, new_cart_dict)
    # 将勾选状态同步到Redis数据库
    if new_cart_selected_add:
        pl.sadd('selected_%s' % user.id, *new_cart_selected_add)
    if new_cart_selected_remove:
        pl.srem('selected_%s' % user.id, *new_cart_selected_remove)
    pl.execute()

    # 清除cookie
    response.delete_cookie('carts')

    return response

3. 账号和QQ登录合并购物车

在users.views.py和oauth.views.py文件中调用合并购物车的工具方法

# 合并购物车
response = merge_cart_cookie_to_redis(request=request, user=user, response=response)

展示商品页面简单购物车

在这里插入图片描述

需求:用户鼠标悬停在商品页面右上角购物车标签上,以下拉框形式展示当前购物车数据。

笔记
  1. 对于不同公司,对于合并的需求有很多
  2. 这里使用cookie的数量和勾选状态覆盖redis的数量和勾选状态
    只要有重复保留redis的
  3. 对于账号登录和qq登录都要进行合并,在登录过程中有三个地方要实现合并,应该使用代码封装
  4. 在这里插入图片描述
    好像函数也可以这样注释
  5. 在这里插入图片描述
    一次添加多个项到哈希表中
  6. 空字符串,空列表,bool运算结果都为false
  7. 在这里插入图片描述
    调用了redis底层的代码,应该是调用redis接口出现错误
  8. 合并有多重方式实现
  9. base64 编码时只操作二进制数据

1. 简单购物车数据接口设计和定义

  1. 请求方式
选项方案
请求方法GET
请求地址/carts/simple/
  1. 请求参数:

  1. 响应结果:JSON
字段说明
code状态码
errmsg错误信息
cart_skus[ ]简单购物车SKU列表
id购物车SKU编号
name购物车SKU名称
count购物车SKU数量
default_image_url购物车SKU图片
{
    "code":"0",
    "errmsg":"OK",
    "cart_skus":[
        {
            "id":1,
            "name":"Apple MacBook Pro 13.3英寸笔记本 银色",
            "count":1,
            "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrPB4GAWkTlAAGuN6wB9fU4220429"
        },
        ......
    ]
}

4.后端接口定义

class CartsSimpleView(View):
    """商品页面右上角购物车"""

    def get(self, request):
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询Redis购物车
            pass
        else:
            # 用户未登录,查询cookie购物车
            pass

        # 构造简单购物车JSON数据
        pass

2. 简单购物车数据后端逻辑实现

  1. 查询Redis购物车
class CartsSimpleView(View):
    """商品页面右上角购物车"""

    def get(self, request):
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询Redis购物车
            redis_conn = get_redis_connection('carts')
            redis_cart = redis_conn.hgetall('carts_%s' % user.id)
            cart_selected = redis_conn.smembers('selected_%s' % user.id)
            # 将redis中的两个数据统一格式,跟cookie中的格式一致,方便统一查询
            cart_dict = {}
            for sku_id, count in redis_cart.items():
                cart_dict[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in cart_selected
                }
        else:
            # 用户未登录,查询cookie购物车
            pass

        # 构造简单购物车JSON数据
        pass

  1. 查询Redis购物车
class CartsSimpleView(View):
    """商品页面右上角购物车"""

    def get(self, request):
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询Redis购物车
            ......
        else:
            # 用户未登录,查询cookie购物车
            cart_str = request.COOKIES.get('carts')
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
  1. 构造简单购物车JSON数据
class CartsSimpleView(View):
    """商品页面右上角购物车"""

    def get(self, request):
        # 判断用户是否登录
        user = request.user
        if user.is_authenticated:
            # 用户已登录,查询Redis购物车
            ......
        else:
            # 用户未登录,查询cookie购物车
            ......

        # 构造简单购物车JSON数据
        cart_skus = []
        sku_ids = cart_dict.keys()
        skus = models.SKU.objects.filter(id__in=sku_ids)
        for sku in skus:
            cart_skus.append({
                'id':sku.id,
                'name':sku.name,
                'count':cart_dict.get(sku.id).get('count'),
                'default_image_url': sku.default_image.url
            })

        # 响应json列表数据
        return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'OK', 'cart_skus':cart_skus})

3. 展示商品页面简单购物车

  1. 商品页面发送Ajax请求
  • index.js、list.js、detail.js
get_carts(){
    let url = '/carts/simple/';
    axios.get(url, {
        responseType: 'json',
    })
        .then(response => {
            this.carts = response.data.cart_skus;
            this.cart_total_count = 0;
            for(let i=0;i<this.carts.length;i++){
                if (this.carts[i].name.length>25){
                    this.carts[i].name = this.carts[i].name.substring(0, 25) + '...';
                }
                this.cart_total_count += this.carts[i].count;
            }
        })
        .catch(error => {
            console.log(error.response);
        })
},
  1. 商品页面渲染简单购物车数据
  • index.html、list.html、detail.html
<div @mouseenter="get_carts" class="guest_cart fr" v-cloak>
    <a href="{{ url('carts:info') }}" class="cart_name fl">我的购物车</a>
    <div class="goods_count fl" id="show_count">[[ cart_total_count ]]</div>
    <ul class="cart_goods_show">
        <li v-for="sku in carts">
            <img :src="sku.default_image_url" alt="商品图片">
            <h4>[[ sku.name ]]</h4>
            <div>[[ sku.count ]]</div>
        </li>
    </ul>
</div>
笔记

和查询购物车做的差不多,只是返回数据的规定不同,这段代码只是简单说了一下,前端代码也不用管

笔记

  1. 在这里插入图片描述
  2. gitbook 可以转换为pdf格式的
  3. update_or_create 有就更新,没有就新建,稀有语法自己去测试
  4. 在这里插入图片描述
    为对象添加first_name:Bob 数据
  5. 商品规格的查询难度大,搞清表以及表之间的关系
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值