添加到购物车

1. 后端接口设计

请求方式 : POST /cart/

请求参数: JSON 或 表单

参数 类型 是否必须 说明
sku_id int 商品sku id
count int 数量
selected bool 是否勾选,默认勾选

返回数据: JSON

参数 类型 是否必须 说明
sku_id int 商品sku id
count int 数量
selected bool 是否勾选,默认勾选

访问此接口,无论用户是否登录,前端请求都需携带请求头Authorization,由后端判断是否登录

2. 后端实现

因为前端可能携带cookie,为了保证跨域请求中,允许后端使用cookie,确保在配置文件有如下设置

CORS_ALLOW_CREDENTIALS = True

创建应用carts。

在carts/serialziers.py中创建序列化器

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

编写视图:

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

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

在carts/views.py中创建视图

class CartView(APIView):
    """
    购物车
    """
    def perform_authentication(self, request):
        """
        重写父类的用户验证方法,不在进入视图前就检查JWT
        """
        pass

    def post(self, request):
        """
        购物车记录添加
        """
        # 1. 获取参数(sku_id, count, selected)并进行参数校验
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        sku_id = serializer.validated_data['sku_id']
        count = serializer.validated_data['count']
        selected = serializer.validated_data['selected']

        # 获取登录用户
        try:
            user = request.user
        except Exception as e:
            user = None

        if user and user.is_authenticated:
            # 2. 保存用户的购物车记录
            redis_conn = get_redis_connection('cart')
            pipeline = redis_conn.pipeline()
            cart_key = 'cart_%s' % user.id

            # 保存购物车中商品及数量
            pipeline.hincrby(cart_key, sku_id, count)

            # 保存购物车中商品的选中状态
            cart_selected_key = 'cart_selected_%s' % user.id
            if selected:
                pipeline.sadd(cart_selected_key, sku_id)

            pipeline.execute()

            # 3. 返回应答,保存购物车记录成功
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            # 获取客户端发送的cookie信息
            cookie_cart = request.COOKIES.get('cart')

            if cookie_cart:
                # 对cookie数据进行解析
                cart_dict = pickle.loads(base64.b64decode(cookie_cart))
            else:
                cart_dict = {}

            if sku_id in cart_dict:
                count += cart_dict[sku_id]['count']

            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 对cart_dict数据进行处理
            cart_data = base64.b64encode(pickle.dumps(cart_dict)).decode()

            response = Response(serializer.data, status=status.HTTP_201_CREATED)
            response.set_cookie('cart', cart_data, max_age=constants.CART_COOKIE_EXPIRES)
            return response

在carts中新建constants.py 常量文件

# 购物车cookie的有效期
CART_COOKIE_EXPIRES = 365 * 24 * 60 * 60

3. 前端实现

在detail.js中编写添加购物车的调用

注意:前端在此跨域请求中要携带cookie,需要在axios中添加配置withCredentials: true

         // 添加购物车
        add_cart: function(){
            axios.post(this.host+'/cart/', {
                    sku_id: parseInt(this.sku_id),
                    count: this.sku_count
                }, {
                    headers: {
                        'Authorization': 'JWT ' + this.token
                    },
                    responseType: 'json',
                    withCredentials: true
                })
                .then(response => {
                    alert('添加购物车成功');
                    this.cart_total_count += response.data.count;
                })
                .catch(error => {
                    if ('non_field_errors' in error.response.data) {
                        alert(error.response.data.non_field_errors[0]);
                    } else {
                        alert('添加购物车失败');
                    }
                    console.log(error.response.data);
                })
        },