在 Django REST Framework 中实现购物车逻辑 && 装饰器形式实现购物车合并

购物车逻辑-增删改查

1.需求分析

  1. 在用户登录或未登录的状态下,都可以保存用户的购物车数据
  2. 用户可以对购物车数据进行增、删、该、查
  3. 保存购物车中商品数量,是否勾选(在订单页面会使用到)
  4. 在用户登录时,合并cookie的购物车数据到redis(最新数据以redis为准)

2.实现方法

  1. 对于未登录的用户,将购物车数据存储到浏览器cookie中
  2. 对于已登录的用户,将购物车数据存储到后端的redis

购物车逻辑是一个典型的增删改查的案例,通过设计一个简单的数据存储方式,实现增删改查的操作

3.购物车数据存储方式

redis保存已登录用户的购物车数据,使用string类型
redis数据存储结构
说明:(后面会对操作购物车数据的方法进行封装,之后再调用会很愉快)

cookie保存未登录用户的购物车数据

为了方便购物车的合并,所以尽量将redis和cookie的数据存储方式保持一致

{
    sku_{id}: {
        "count": xxx,  // 数量
        "selected": True  // 是否勾选
    },
    sku_{id}: {
        "count": xxx,
        "selected": False
    },
    ...
}

在Django中设置cookie:response.set_cookie(键,值,有效期)
response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)

4.引入pickle模块和base64模块

在cookie中只能保存字符串数据,所以将上述数据使用pickle进行序列化转换 ,并使用base64编码为字符串,保存到cookie中

pickle模块的使用:

  • pickle模块是python的标准模块,提供了对于python数据的序列化操作,可以将数据转换为bytes类型,其序列化速度比json模块要高。
  • pickle.dumps() 将python数据序列化为bytes类型
    pickle.loads() 将bytes类型数据反序列化为python的数据类型

base64模块的使用:

  • Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2^6=64,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
  • Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。
  • python标准库中提供了base64模块,用来进行转换
    base64.b64encode() 将bytes类型数据进行base64编码,返回编码后的bytes类型
    base64.b64deocde() 将base64编码的bytes类型或者str类型进行解码,返回解码后的bytes类型
import base64
import pickle
cart_dict = {1: [2, True], 2: [1, True]}

# dict -> bytes -> str
cart_bytes = pickle.dumps(cart_dict)        # 将python字典转换为16进制bytes类型
print(type(cart_bytes), "|", cart_bytes)    # <class 'bytes'>

cart_b64_bytes = base64.b64encode(cart_bytes)       # 转换为base64_bytes
print(type(cart_b64_bytes), "|", cart_b64_bytes)    # <class 'bytes'>

cart_b64_str = cart_b64_bytes.decode()          # 解码成base64_str
print(type(cart_b64_str), "|", cart_b64_str)    # <class 'str'>

print("=" * 100)

cart_b64_str = "gAN9cQAoSwFdcQEoSwKIZUsCXXECKEsBiGV1Lg=="
# str -> bytes -> dict
cart_bytes = base64.b64decode(cart_b64_str)     # 将字符串直接将base64_str转换为16进制的bytes
print(type(cart_bytes), "|", cart_bytes)        # <class 'bytes'>

cart_dict = pickle.loads(cart_bytes)        # 将16进制的bytes转换为python字典
print(type(cart_dict), "|", cart_dict)      # <class 'dict'>

# 值得注意的是:在得到python字典的过程中,base64.decode()可以解码 base64_bytes,也可以直接解码 base64_str

5.具体逻辑实现

自定义辅助类
  • 实现数据的转换
    获取用户的登录状态
    已登录:读取redis的数据,或 将数据保存到redis
    未登录:读取cookie的数据,或 将数据保存到cookie

  • 合并购物车
    对用户登录的视图类/函数进行装饰
    就可以在用户登录时进行购物车合并操作

import base64
import pickle
from django_redis import get_redis_connection
from carts import constants

# redis 存储方式为字符串类型:
# set key value (user_id,{sku_id: [count, selected]})
class CartMixin(object):
    """ 自定义辅助类 """

    def read_cart(self, request) -> dict:
        """ 读取购物车数据 """

        # 获取用户登录状态
        user = request.user
        # 如果用户存在且通过认证
        if user and user.is_authenticated:
            # 读取 redis 中购物车数据,返回字典数据
            return self.read_from_redis(user)
        else:
            # 读取 cookie 中购物车数据,返回字典数据
            return self.read_from_cookie(request)

    def wright_cart(self, request, cart_dict, response):
        """ 写入购物车数据 """

        # 获取用户登录状态
        user = request.user
        # 如果用户存在且通过验证
        if user and user.is_authenticated:
            # 将商品保存到 redis
            return self.wright_to_redis(request, cart_dict)
        else:
            # 将商品保存到 cookie
            return self.wright_to_cookie(cart_dict, response)

    def read_from_redis(self, user) -> dict:
        """ 读取 redis 中的购物车数据,返回字典 """
        redis_conn = get_redis_connection("cart")
        # 从 redis 取出来的值是 <class 'bytes'> 类型
        cart_bytes = redis_conn.get(f"cart_{user.id}")
        cart_dict = pickle.loads(base64.b64decode(cart_bytes)) if cart_bytes else dict()
        return cart_dict

    def wright_to_redis(self, request, cart_dict):
        """ 商品保存到redis """
        redis_conn = get_redis_connection("cart")
        cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
        redis_conn.set(f"cart_{request.user.id}", cart_str)

    def read_from_cookie(self, request) -> dict:
        """ 读取 cookie 中的购物车数据,返回字典 """
        cart_str = request.COOKIES.get("cart")      # <class 'str'>
        cart_dict = pickle.loads(base64.b64decode(cart_str.encode())) if cart_str else dict()
        return cart_dict

    def wright_to_cookie(self, cart_dict, response):
        """ 将商品信息保存到 cookie 中 """
        cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
        response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)


def merge_cart_cookie_to_redis(request, user, response):
    """ 登录时合并购物车,将cookie中的数据合并到redis中 """
    # 相同商品如何处理?
    # 商品数量:以cookie为准
    # 勾选状态:以cookie为准

    # 1 业务处理,实例化对象
    merge_cart = CartMixin()
    # 2 获取redis中的购物车数据
    redis_cart_dict = merge_cart.read_from_redis(user)
    # 3 获取cookie中的购物车数据
    cookie_cart_dict = merge_cart.read_from_cookie(request)
    # 4 合并
    redis_cart_dict.update(cookie_cart_dict)
    # 5 将购物车数据保存到redis中
    merge_cart.wright_to_redis(request, redis_cart_dict)
    # 6 删除cookie中的购物车数据
    response.delete_cookie("cart")


""" 使用装饰器的方式实现合并购物车 """
def merge_cart_decoration(func):
    def wrapper(request, *args, **kwargs):
        # 执行视图

        print("调用原始方法" + request.method)

        resp = func(request, *args, **kwargs)

        if 200 <= resp.status_code < 300 :
            # 判断视图执行结果,如果视图成功执行,且用户登录认证成功,则进行合并购物车操作
            if request.user and request.user.is_authenticated:
                merge_cart_cookie_to_redis(request, request.user, resp)
                print("合并购物车成功")
            else:
                # raise Exception("如果登录成功,请添加 user 属性到 request 中")
                print("如果登录成功,请添加 user 属性到 request 中")
        else:
            print("身份认证错误,没有合并购物车")
        return resp

    return wrapper
视图类
from django.core.serializers import get_serializer
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
import logging
from carts.utils import CartMixin
from goods.models import SKU
from .serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer


logger = logging.getLogger("django")


class CartView(CartMixin, GenericAPIView):
    """ 购物车 """
    serializer_class = CartSerializer

    def post(self, request):
        """ 将商品保存到购物车 """
        # sku_id, count, selected
        # 使用序列化器进行校验
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 获取校验通过的数据
        # sku_id, count, selected
        attrs = serializer.validated_data
        sku_id, count, selected = attrs['sku_id'], attrs['count'], attrs['selected']

        # 读取购物车数据
        cart_dict = self.read_cart(request)

        # 根据幂等性进行处理
        cart_dict[sku_id] = [count, selected]

        # 保存购物车数据
        response = Response(serializer.data)
        self.wright_cart(request, cart_dict, response)

        return response

    def get(self, request):
        """ 查询购物车 """

        # 读取购物车数据
        cart_dict = self.read_cart(request)

        sku_id_list = cart_dict.keys()
        # 查询数据库,获取商品sku对象
        skus = list()
        try:
            skus = SKU.objects.filter(id__in=sku_id_list)
        except Exception as e:
            logger.error(f"数据库查询异常:[message: {e}]")
        # 遍历sku_obj_list 向sku对象中添加 count 和 selected 属性
        for sku in skus:
            sku.count = cart_dict[sku.id][0]
            sku.selected = cart_dict[sku.id][1]

        # 序列化返回
        serializer = CartSKUSerializer(skus, many=True)
        return Response(serializer.data)

    def put(self, request):
        """ 修改购物车数据 """
        # sku_id, count, selected
        # 使用序列化器进行校验
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 获取校验通过的数据
        attrs = serializer.validated_data
        sku_id, count, selected = attrs['sku_id'], attrs['count'], attrs['selected']

        # 读取购物车数据
        cart_dict = self.read_cart(request)

        # 修改购物车数据
        cart_dict[sku_id] = [count, selected]

        # 保存购物车数据
        response = Response(serializer.data)
        self.wright_cart(request, cart_dict, response)

        return response

    def delete(self, request):
        """ 删除购物车中的商品 """
        # sku_id
        # 校验参数
        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data["sku_id"]

        # 读取购物车数据
        cart_dict = self.read_cart(request)

        # 删除购物车数据
        try:
            cart_dict.pop(sku_id)
        except Exception:
            return Response({"message": "购物车中没有这件商品"}, status=status.HTTP_404_NOT_FOUND)

        # 保存购物车数据
        response = Response(serializer.data)
        self.wright_cart(request, cart_dict, response)

        return response

序列化器类

from rest_framework import serializers
from goods.models import SKU


class CartSKUSerializer(serializers.ModelSerializer):
    """ 查询购物车商品信息-序列化器 """
    count = serializers.IntegerField(label='数量')
    selected = serializers.BooleanField(label='是否勾选')

    class Meta:
        model = SKU
        fields = ('id', 'count', 'name', 'default_image_url', 'price', 'selected')


class CartSerializer(serializers.Serializer):
    """ 添加商品到购物车-序列化器,只进行数据校验 """
    sku_id = serializers.IntegerField(label='sku id ', min_value=1)
    count = serializers.IntegerField(label='数量', min_value=1)
    selected = serializers.BooleanField(label='是否勾选', default=True)

    def validate(self, data):
        try:
            sku = SKU.objects.get(id=data['sku_id'])
        except SKU.DoesNotExist:
            raise serializers.ValidationError('商品不存在')

        if data['count'] > sku.stock:
            raise serializers.ValidationError('商品库存不足')

        return data


class CartDeleteSerializer(serializers.Serializer):
    """ 删除购物车数据-序列化器 """
    sku_id = serializers.IntegerField(label='商品id', min_value=1)

    def validate_sku_id(self, value):
        try:
            sku = SKU.objects.get(id=value)
        except SKU.DoesNotExist:
            raise serializers.ValidationError('商品不存在')

        return value

具体的项目代码已上传到码云:https://gitee.com/chenkaichen/mmc.git

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值