You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django页面刷新重复生成会话键致购物车重复问题排查

问题

我正在开发一个用Django会话管理购物车的应用,现在遇到一个问题:每次刷新页面都会生成新的session key,导致创建多个购物车,出现同一用户/会话对应多个购物车的异常情况。

刷新/products页面时,Django后端日志显示创建了多个会话:

[16/Sep/2024 17:11:31] "GET /api/products/ HTTP/1.1" 200 212
Session key: iedrvaus89536ly57pb242tnalwtx7vo
[16/Sep/2024 17:11:31] "GET /api/carts/retrieve_cart/ HTTP/1.1" 200 107
[16/Sep/2024 17:11:31] "GET /api/products/ HTTP/1.1" 200 212
Session key: eipxdrmkf343fjtkchk7ads14ef1vccr
[16/Sep/2024 17:11:31] "GET /api/carts/retrieve_cart/ HTTP/1.1" 200 107

点击加入购物车时会创建两个购物车:

{
    "id": 77,
    "items": [
        {
            "id": 66,
            "product": 1,
            "product_sku": "MNT-MST-MDL-HRV",
            "product_name": "Monthly Harvest Box",
            "product_price": 50.0,
            "quantity": 1
        }
    ],
    "created_at": "2024-09-16T20:10:24.950541Z",
    "updated_at": "2024-09-16T20:10:24.950541Z"
},
{
    "id": 78,
    "items": [],
    "created_at": "2024-09-16T20:10:25.006548Z",
    "updated_at": "2024-09-16T20:10:25.006548Z"
}

前端ProductsPage.js代码如下:

import React, { useState, useEffect } from "react";
import './ProductsPage.css';
import Cart from './Cart';
import PictureGrid from "./PictureGrid";

const ProductsPage = () => {
    const [products, setProducts] = useState([]);
    const [cartItems, setCartItems] = useState([]);
    const [cart, setCart] = useState(null);  // Cart starts as null
    const [total, setTotal] = useState(0);
    const [loadingCart, setLoadingCart] = useState(true);  // For managing loading state

    // Fetch products from the Django API
    useEffect(() => {
        fetch('http://localhost:8000/api/products/')
            .then(response => response.json())
            .then(data => setProducts(data))
            .catch(error => console.error('Error fetching products:', error));
    }, []);

    // Retrieve or create the cart for the current session
    useEffect(() => {
        if (!cart) {
            loadCart();  // Load the cart for the current session
        }
    }, [cart]);

    // Load cart items for the current session
    const loadCart = () => {
        setLoadingCart(true);  // Set loading state to true while loading cart
        fetch(`http://localhost:8000/api/carts/retrieve_cart/`)
            .then(response => response.json())
            .then(cartData => {
                setCart(cartData.id);  // Set the cart ID
                setCartItems(cartData.items);  // Set cart items
                calculateTotal(cartData.items);  // Calculate the total
                setLoadingCart(false);  // Set loading state to false after loading cart
            })
            .catch(error => {
                console.error('Error fetching cart data:', error);
                setLoadingCart(false);  // Set loading state to false even if there's an error
            });
    };

    // Calculate total price of the cart
    const calculateTotal = (items) => {
        const totalPrice = items.reduce((sum, item) => {
            const itemPrice = parseFloat(item.product_price);
            return sum + (itemPrice * item.quantity);
        }, 0);
        setTotal(totalPrice);
    };

    // Function to handle adding a product to the cart
    const addToCart = (productSku) => {
        if (!cart) {
            console.error('Cart not defined yet. Try again later.');
            return;
        }

        console.log('Adding product with SKU:', productSku);

        fetch(`http://localhost:8000/api/carts/${cart}/add_item`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ sku: productSku, quantity: 1 }),
        })
            .then(response => response.json())
            .then(data => {
                console.log('Product added to cart:', data);

                // Reload the cart after adding the item
                loadCart();
            })
            .catch(error => console.error('Error adding product to cart:', error));
    };

    // Function to handle removing a product from the cart
    const removeFromCart = (productSku) => {
        if (!cart) {
            console.error('Cart not defined yet. Try again later.');
            return;  // Prevent the function from running if cart is not yet defined
        }

        console.log('Removing product with SKU:', productSku);

        fetch(`http://localhost:8000/api/carts/${cart}/remove_item/`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ sku: productSku }),
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error(`Error: ${response.statusText}`);
                }
                return response.json();
            })
            .then(data => {
                console.log('Item removed from cart:', data);
                const updatedCartItems = cartItems.filter(item => item.product_sku !== productSku);
                setCartItems(updatedCartItems);  // Update the cart items after removal
                calculateTotal(updatedCartItems);  // Recalculate the total
            })
            .catch(error => console.error('Error removing item from cart:', error));
    };

    return (
        <div className="products-page">
            <PictureGrid />
            <h2>Our Subscriptions</h2>
            <div className="products-grid">
                {products.map(product => (
                    <div className="product-card" key={product.sku}>
                        <img src={"box.jpg"} alt={product.name} className="product-image" />
                        <h3>{product.name}</h3>
                        <p>Price: ${product.price}</p>
                        <button className="buy-button" onClick={() => addToCart(product.sku)} disabled={loadingCart}>
                            {loadingCart ? 'Loading...' : 'Add to Cart'}
                        </button>
                        <p>SKU: {product.sku}</p>
                    </div>  
                ))}
            </div>

            {/* Include the Cart component */}
            <Cart cartItems={cartItems} total={total} removeFromCart={removeFromCart} />
        </div>
    );
};

export default ProductsPage;

切换为会话管理购物车前所有功能正常,请问哪里出错了?

分析与解决

核心问题:会话无法保持,导致每次请求生成新Session

1. 前端Fetch请求未携带Cookie凭证

Django的Session依赖Cookie识别用户会话,但fetch默认不会携带Cookie,导致每次请求都被Django判定为新用户,生成新Session Key,进而创建新购物车。

解决:给所有Fetch请求添加credentials: 'include'选项
修改所有fetch调用,比如:

// 获取产品列表
fetch('http://localhost:8000/api/products/', { credentials: 'include' })
// 获取购物车
fetch(`http://localhost:8000/api/carts/retrieve_cart/`, { credentials: 'include' })
// 添加商品到购物车
fetch(`http://localhost:8000/api/carts/${cart}/add_item`, {
    method: 'POST',
    credentials: 'include', // 新增
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ sku: productSku, quantity: 1 }),
})

该选项会让请求携带Cookie,确保Django能识别同一用户会话。

2. 跨域配置问题(前后端端口不同时)

如果前端运行在localhost:3000,后端在localhost:8000,属于跨域请求,需要Django配置允许携带Cookie:

  • 安装并配置django-cors-headers
  • 在settings.py中添加:
    CORS_ALLOW_CREDENTIALS = True
    CORS_ALLOWED_ORIGINS = [
        "http://localhost:3000",  # 前端地址
    ]
    

3. 后端retrieve_cart视图逻辑问题

检查后端视图是否正确根据Session关联购物车,确保不会每次请求都创建新购物车。正确逻辑示例:

from django.http import JsonResponse
from .models import Cart

def retrieve_cart(request):
    # 从Session获取已有的购物车ID
    cart_id = request.session.get('cart_id')
    if cart_id:
        try:
            cart = Cart.objects.get(id=cart_id)
        except Cart.DoesNotExist:
            # 购物车不存在则创建新的,并更新Session
            cart = Cart.objects.create()
            request.session['cart_id'] = cart.id
    else:
        # 无Session记录则创建新购物车
        cart = Cart.objects.create()
        request.session['cart_id'] = cart.id
    
    # 序列化购物车数据返回
    cart_data = {
        'id': cart.id,
        'items': [
            {
                'id': item.id,
                'product': item.product.id,
                'product_sku': item.product.sku,
                'product_name': item.product.name,
                'product_price': float(item.product.price),
                'quantity': item.quantity
            } for item in cart.items.all()
        ],
        'created_at': cart.created_at,
        'updated_at': cart.updated_at
    }
    return JsonResponse(cart_data)

确保只有当Session中无购物车ID或对应购物车不存在时,才创建新购物车。

4. 前端重复请求问题(次要)

日志中显示两次/api/products/和/api/carts/retrieve_cart/请求,可能是组件重复渲染导致。可以调整useEffect的依赖项避免重复触发请求:

useEffect(() => {
    loadCart();
}, []); // 仅组件挂载时调用一次

内容的提问来源于stack exchange,提问作者A-laz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 08:34:50