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
相关产品推荐
相关产品推荐

