Django项目(八)购物车功能的实现

业务需求分析

1.在用户登录和未登录状态下,都可以保存用户的购物车数据

2.用户可以对购物车数据进行增、删、改、查

3.用户对于购物车数据的勾选也要保存,在订单结算页面会使用勾选数据

4.用户登录时,合并cookie

技术实现

对于未登录的用户,购物车数据使用浏览器cookie保存

对于已登录的用户,购物车数据在后端使用Redis保存

幂等(非幂等)的概念

  • 非幂等
    • sku_id=1 & num=1
    • 对于同一种行为,如果最终的结果与执行的次数有关,每次执行后结果都不相同,就称这种行为为非幂等
    • 缺点:当用户多次点击+,增加购物车商品数量时,如果其中有一次数据传输失败,则商品数量出错
  • 幂等
    • sku_id=1&finally_num=18
    • 对于同一种行为,如果执行不论多少次,最终的结果都是一致相同的,就称这种行为是幂等的
  • 结论:
    • 购物车中,无论是增加还是减少商品数量,都对应相同接口,都传递最终商品的数量即可
    • 优点:每次向服务器发送的是最终商品的数量,如果某一次传输失败,可以展示上一次的商品最终数量

非幂等:每点击一次发送一次请求

幂等:点击多次完成后,得到最终的数量,在鼠标移开事件中发送请求

数据库redis的设计:

hash: user.id    sku_id    count

set:sku_id(选中的商品)

redis中管道技术的实现:

1.创建管道实例

2.添加执行操作

3.管道统一执行

优点:统一执行可以减少查询redis数据库的操作

例如:

#创建管道实例
pl = redis_conn.pipeline()
pl.hincrby('cart_%s' % user.id, sku_id, count)
pl.sadd('cart_selected_%s' % user.id, sku_id)

# 统一让管道执行
pl.execute()

1.添加购物车

注意:因为前端请求时携带了Authorization请求头,而如果用户未登录,此请求头无意义,为了防止REST framework框架在验证此无意义的请求头时抛出401异常,在视图中需要做两个处理

  • 重写perform_authentication()方法,此方法是REST framework检查用户身份的方法
  • 在获取request.user属性时捕获异常,REST framework在返回user时,会检查Authorization请求头,无效的Authorization请求头会导致抛出异常

思路:

1.获取到数据,序列化器进行校验

2.获取到商品id,count和是否选中信息

3.判断用户是否为登录用户

         1> 如果为登录用户则数据保存到redis

                连接redis

                将校验过的数据设置到hash中(如果添加的是同一件商品,需要用到hincrby累加)

                如果传过来的selected是true,将它添加到set中

               返回序列化之后的数据

          2> 如果为非登录用户保存到cookies

                 获取到cookie中的cart的值

                  如果cart存在,对其解码解密

                  如果不存在,令cart={}

                  判断传过来的sku_id是否在cart中

                  如果在,获取到之前的count,让他加上此次传过来的count值

                   重新设置cart的值,并对其进行编码加密处理

                  然后重新写入到cookie中,返回给前端

def post(self, request):
        # 获取数据进行校验
        # sku_id,count是必传参数,selected是可选参数
        data = request.data
        serializer = CartSerializer(data=data)
        serializer.is_valid()
        # 获取到验证过后的数据
        sku_id = serializer.data.get('sku_id')
        count = serializer.data.get('count')
        selected = serializer.data.get('selected')
        # 判断用户是否存在
        try:
            user = request.user
        except Exception:
            user = None
        # request.user.is_authenticated
        if user is not None and user.is_authenticated:
            # 用户已经登录,将数据保存到redis中
            # 连接redis数据库
            redis_conn = get_redis_connection('cart')
            # # 向redis中添加count数据
            # # hash: user.id {sku_id:count}
            # # redis_conn.hset('cart_%s'%user.id,sku_id,count)
            # # hincrby 实现增量
            # redis_conn.hincrby('cart_%s' % user.id, sku_id, count)
            # # 如果是被选中的商品,将sku_id添加到集合中
            # if selected:
            #     redis_conn.sadd('cart_selected_%s' % user.id, sku_id)

            #创建管道实例
            pl = redis_conn.pipeline()
            pl.hincrby('cart_%s' % user.id, sku_id, count)
            pl.sadd('cart_selected_%s' % user.id, sku_id)

            # 统一让管道执行
            pl.execute()
            return Response(serializer.data)
        else:
            # 用户未登陆,将数据保存在cookie中
            # 将数据转换成cart数据,加密后保存到cookie中
            # {sku_id:{'count':xxx,'selected':xxx}}
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
            # 如果添加到购物车的商品之前已经添加过了,修改count的值
            if sku_id in cart_dict:
                origin_count = cart_dict[sku_id]['count']
                count += origin_count
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }
            response = Response(serializer.data)
            cart = base64.b64encode(pickle.dumps(cart_dict)).decode()
            response.set_cookie('cart', cart,24*3600)
            return response

2.查询购物车数据

 思路:

1.判断用户是否登录

2.登录的用户

     1> 连接redis数据库

     2> 获取hash中的值redis_cart

     3> 获取set选中商品的sku_id

     4>创建一个空字典cart类型    cart[sku_id] = { 'sku_id':count,'selected':selected}

     5> 遍历redis_cart中的每一个值(for sku_id,count  in redis_cart.items())

     6>注意redis中查询的数据都是二进制类型

              设置cart类型 

for sku_id,count in redis_cart.items():
                count = int(count.decode())
                cart[sku_id.decode()] = {
                    'count':count,
                    'selected':sku_id in redis_selected_id
                }

3.为登录的用户

      1> 获取到cookie中cart的值

       2>如果cart存在,对其进行解密

4.获取cart中所有的sku和选择商品的sku_id

5.遍历skus,对每一个sku添加属性

6.对数据进行序列化操作

7.返回序列化之后的数据

def get(self, request):
        """
        思路
           #判断是否为登录用户
               #登录用户,从redis中获取数据
               #非登录用户,从cookie中获取数据
           #获取所有商品的信息
           #返回响应
        """
        try:
            user = request.user
        except Exception:
            user = None
        if user is not None and user.is_authenticated:
            # 登录用户
            # 从redis中获取数据
            redis_conn = get_redis_connection('cart')
            # 获取hash
            redis_cart = redis_conn.hgetall('cart_%s'%user.id)
            # 获取set中选中ID
            redis_selected_id = redis_conn.smembers('cart_selected_%s'%user.id)
            cart = {}
            # 遍历出redis中的值,遍历成cart类型数据

            for sku_id,count in redis_cart.items():
                count = int(count.decode())
                cart[sku_id.decode()] = {
                    'count':count,
                    'selected':sku_id in redis_selected_id
                }
        else:
            # 未登录用户
            # 获取到cookie中的cart数据
            cart_str = request.COOKIES.get('cart')
            # 如果cart中有数据,对其解码
            if cart_str is not None:
                cart = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart = {}
        # 找出cart所包含的所有sku_id
        ids = cart.keys()
        skus = SKU.objects.filter(pk__in=ids)
        # 遍历每一个sku_id,对其添加属性
        for sku in skus:
            # 给对象动态添加属性
            sku.count = cart[int(sku.id)]['count']
            sku.selected = cart[int(sku.id)]['selected']

            # 5.再将商品信息通过序列化器转换为字典
        serializer = CartSKUSerializer(skus, many=True)

        return Response(serializer.data)

3.修改购物车的数据

思路:

1.接收前端传过来的数据

2.对数据进行反序列化操作

3.对数据进行验证

4.获取到验证后的sku_id,count,selected(可选)

5.判断用户是否登录

       1.登录的用户

                 连接redis,重新设置Hash值,根据selected传过来的状态(true在集合中添加,false在集合中删除),然后返回序列

                 之后的数据

        2.未登录的用户

                  获取到cookie值,如果存在对其进行解密,将接收到的前端的参数重新设置到cart中,然后序列化数据之后加密

                  把数据传给前端

def put(self,request):
        """
        只要修改,就把所有的数据都提交
        幂等,就是将最终的结果提交给后端, 也得把最终结果返回给前端

        # 1. 前段应该把sku_id,count必须传递过来,是否选中的状态是一个可选
        # 2. 我们就需要对前端提交的数据进行校验
        # 3. 校验完成之后,我们需要得到数据,然后将数据保存到指定的地方
        # 4. 获取登录的状态
        # 5. 登录用户保存在redis中
        # 6. 未登录用户保存在cookie中

        """
        # 获取前端传过来的数据
        data = request.data
        # 对数据进行校验
        serializer = CartSerializer(data=data)
        # 获取到验证过后的数据
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.data.get('sku_id')
        count = serializer.data.get('count')
        selected = serializer.data.get('selected')
        # 判断用户是否存在
        try:
            user = request.user
        except Exception:
            user = None
        # request.user.is_authenticated
        if user is not None and user.is_authenticated:
            # 连接redis数据库
            redis_conn = get_redis_connection('cart')
            # 重新设置redis中hash值
            redis_conn.hset('cart_%s' % user.id, sku_id, count)
            if selected:
                redis_conn.sadd('cart_selected_%s' % user.id, sku_id)
            else:
                redis_conn.srem('cart_selected_%s' % user.id, sku_id)
            return Response(serializer.data)
        else:
            # 用户未登陆,将数据保存在cookie中
            # 将数据转换成cart数据,加密后保存到cookie中
            # {sku_id:{'count':xxx,'selected':xxx}}
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
            # 对数据进行更新
            if sku_id in cart_dict:
                cart_dict[sku_id] = {
                    'count': count,
                    'selected': selected
                }
            response = Response(serializer.data)
            cart = base64.b64encode(pickle.dumps(cart_dict)).decode()
            response.set_cookie('cart', cart, 24 * 3600)
            return response

4.删除购物车数据

思路:

1.获取到前端传过来的sku_id

2.对接收到的数据进行反序列化操作

3.对数据进行验证

4.从验证后的数据中取出sku_id

5.判断用户是否登录

   用户已登录:

               连接redis

                从hash值中删除该sku_id

                从set里删除该sku_id(如果set中没有这个值会不会报错)

                返回204状态码

  用户未登录:

                1.从cookie中获取cart值

                 2.对获取到的值进行解码

                 3.在该cart字典中删除这个sku_id

                  4.对删除后的cart数据编码加密

                   5.重新将cart写入cookie中

                   6.返回response

def delete(self,request):
        # 获取前端传过来的sku_id
        data = request.data
        serializer = CartDeleteSerializer(data=data)
        serializer.is_valid()
        sku_id = serializer.data.get('sku_id')
        try:
            user = request.user
        except Exception:
            user = None
        # request.user.is_authenticated
        if user is not None and user.is_authenticated:
            # 连接redis
            redis_conn = get_redis_connection('cart')
            redis_conn.hdel('cart_%s' % user.id, sku_id)
            redis_conn.srem('cart_selected_%s' % user.id, sku_id)
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart_dict = {}
            response = Response(status=status.HTTP_204_NO_CONTENT)
            if sku_id in cart_dict:
                del cart_dict[sku_id]
                cart = base64.b64encode(pickle.dumps(cart_dict)).decode()
                response.set_cookie('cart',cart,24*3600)
            return response

5.登录合并购物车

待实现

浏览器测试如下:

猜你喜欢

转载自blog.csdn.net/Spencer_q/article/details/82288330