React+Laravel项目:Redux购物车数据同步至MySQL数据库求助
购物车同步逻辑问题排查(React+Laravel)
我正在开发一个React前端、Laravel后端搭配MySQL数据库的项目,用来学习这两个技术栈。核心需求是:
- 用户未登录时,商品暂存到Redux
- 用户登录后,自动将Redux中的商品同步到数据库的cart表并关联user_id
- 用户已登录状态下添加商品,直接保存到数据库cart表
以下是我的代码,现在遇到了逻辑无法正常运行的问题,需要排查:
amazonSlice.js
import React, { useEffect, useState } from 'react' import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from "axios"; const initialState = { products: [], userInfo:[], status: 'idle' }; export const updateCartOnSignIn = createAsyncThunk('amazon/updateCartOnSignIn', async (selectItems, { getState }) => { const [apitoken, setapitoken] = useState(''); useEffect(() => { const storedapitoken = localStorage.getItem('api_token'); if (storedapitoken) { setapitoken(storedapitoken); } }, []); const { userInfo } = getState().amazon; try { const response = await axios.post('http://127.0.0.1:8000/api/user/updatecart',{user_id: userInfo.id, selectItems}, { headers: { Authorization: `Bearer ${apitoken}`, }, }); return response.data; } catch (error) { throw error; } } ); export const amazonSlice = createSlice({ name: "amazon", initialState, reducers: { addToCart: (state, action) => { const item = state.products.find((item) => item.id === action.payload.id) if(item) { item.quantity += action.payload.quantity } else { state.products.push(action.payload) } }, removeFromCart: (state, action) => { state.products = state.products.filter((item) => item.id !== action.payload) }, clearCart: (state) => { state.products = [] window.scrollTo({ top: 0 }); }, quantityIncrement: (state,action) => { const item = state.products.find((item) => item.id === action.payload) item.quantity++ }, quantityDecrement: (state, action) => { const item = state.products.find((item) => item.id === action.payload) if(item.quantity === 1) { item.quantity = 1 } else { item.quantity -- } } }, }); export const { addToCart, removeFromCart, clearCart, quantityIncrement, quantityDecrement } = amazonSlice.actions; export const selectItems = (state) => state.amazon.products; export const selectTotal = (state) => state.amazon.products.reduce((total, item) => total + (item.price * item.quantity) , 0) export default amazonSlice.reducer;
Product.js
import Image from 'next/legacy/image' import React, { useEffect, useState } from 'react' import { StarIcon } from '@heroicons/react/24/solid' import Currency from 'react-currency-formatter'; import prime from '../../images/prime.png' import axios from 'axios'; import { useDispatch, useSelector } from 'react-redux'; import { addToCart, updateCartOnSignIn, selectItems, clearCart } from '@/slices/amazonSlice'; const Product = ({ id, title, price, description, category, src }) => { const dispatch = useDispatch() const itemsInCart = useSelector(selectItems); const [apitoken, setapitoken] = useState(''); const [userid, setuserid] = useState(''); useEffect(() => { const storedUsername = localStorage.getItem('api_token'); const storedUserid = localStorage.getItem('userid'); if (storedUsername && storedUserid) { setapitoken(storedUsername); setuserid(storedUserid); } }, []); async function clickAddToCart(e) { const isAuthenticated = !!apitoken; if(!isAuthenticated) { dispatch(addToCart({ id, title, description, price, category, src, quantity: 1, })) } else if(isAuthenticated && itemsInCart.length > 0 ) { try { dispatch(updateCartOnSignIn); dispatch(clearCart()); } catch (error) { alert(error); } } else { let response = await axios.post('http://127.0.0.1:8000/api/user/updatecart', {id, title, price, description, category, src, quantity:1}, { headers: { Authorization: `Bearer ${apitoken}` } }) if(response) { console.log(response) } dispatch(addToCart({ id, title, description, price, category, src, quantity: 1, })) } } return ( <div className='relative flex flex-col m-5 bg-white z-30 p-10'> <p className='absolute -top-2 right-2 text-xs italic text-gray-400 my-3'>{category}</p> <img src={src} width={200} height={200} style={{ objectFit: 'contain', paddingBottom: '13px' }} alt="商品图片" /> <h4>{title}</h4> <p className='text-sm my-2 line-clamp-2'>{description}</p> <div className='mb-5'> <Currency quantity={price} /> </div> <button onClick={clickAddToCart} className='mt-auto button'>Add to Basket</button> </div> ) } export default Product
api.php
Route::middleware('auth:sanctum')->post('/user/updatecart', [ProductController::class, 'updatecart']);
ProductController.php
public function updateCart(Request $request) { if (Auth::check()) { $user_id = Auth::id(); $existingCartItem = Cart::where('user_id', $user_id) ->where('product_id', $request->id) ->first(); if ($existingCartItem) { $existingCartItem->quantity += $request->quantity; $existingCartItem->save(); } else { $cart = new Cart(); $cart->user_id = $user_id; $cart->product_id = $request->id; $cart->quantity = $request->quantity; $cart->save(); } return response()->json(['message' => 'Cart updated successfully']); } else { return response()->json(['message' => 'User not authenticated'], 401); } }
cart 迁移文件
public function up() { Schema::create('carts', function (Blueprint $table) { $table->id(); $table->string('user_id'); $table->string('product_id'); $table->string('quantity'); $table->timestamps(); }); }
核心问题排查与修正
1. Redux Thunk 误用React Hooks
updateCartOnSignIn是Redux异步Thunk,不是React组件,不能使用useState和useEffect。直接从localStorage读取token即可:
export const updateCartOnSignIn = createAsyncThunk('amazon/updateCartOnSignIn', async (selectItems, { getState }) => { const apitoken = localStorage.getItem('api_token'); if (!apitoken) throw new Error('未找到登录令牌'); const { userInfo } = getState().amazon; try { const response = await axios.post('http://127.0.0.1:8000/api/user/sync-cart', { items: selectItems }, { headers: { Authorization: `Bearer ${apitoken}` } }); return response.data; } catch (error) { throw error; } });
2. 同步逻辑触发时机错误
Product.js中,登录后同步Redux商品的逻辑不应该在点击添加商品时触发,应该在用户登录成功后立即执行(比如登录页面的回调里)。当前点击添加商品的逻辑分支混乱,修正后:
async function clickAddToCart(e) { const isAuthenticated = !!localStorage.getItem('api_token'); if (!isAuthenticated) { dispatch(addToCart({ id, title, description, price, category, src, quantity: 1 })); } else { // 已登录直接存数据库 await axios.post('http://127.0.0.1:8000/api/user/updatecart', { id, quantity: 1 }, { headers: { Authorization: `Bearer ${localStorage.getItem('api_token')}` } } ); // 可选:同步到Redux保持页面状态一致 dispatch(addToCart({ id, title, description, price, category, src, quantity: 1 })); } }
3. 后端批量同步接口缺失
原updateCart接口只能处理单个商品,需要新增批量同步接口来处理Redux中的多个商品:
// api.php新增路由 Route::middleware('auth:sanctum')->post('/user/sync-cart', [ProductController::class, 'syncCart']); // ProductController.php新增方法 public function syncCart(Request $request) { $userId = Auth::id(); $items = $request->input('items'); foreach ($items as $item) { Cart::updateOrCreate( ['user_id' => $userId, 'product_id' => $item['id']], ['quantity' => DB::raw('quantity + ' . $item['quantity'])] ); } return response()->json(['message' => '购物车同步完成']); }
4. 数据库字段类型错误
迁移文件中user_id、product_id应该是关联用户/商品表的整数类型,quantity也应该是整数,修正迁移:
public function up() { Schema::create('carts', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->unsignedBigInteger('product_id'); $table->integer('quantity')->default(1); $table->timestamps(); // 添加外键约束 $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); }); }
5. 冗余认证判断
路由已经使用auth:sanctum中间件,未认证请求会被直接拦截,控制器中不需要再写Auth::check()判断。
内容的提问来源于stack exchange,提问作者saddysad
相关产品推荐
相关产品推荐

