解决products模型_id字段Cast to ObjectId转换失败问题
问题:获取单个产品数据时出现ObjectId转换错误
错误信息
Cast to ObjectId failed for value "[object Object]" (type string) at path "_id" for model "products".
相关代码
后端代码
productController.jsx
const formidable = require("formidable"); const { responseReturn } = require("./../../utiles/response"); const cloudinary = require("cloudinary").v2; const productModel = require("./../../models/productModels"); class productController { add_product = async (req, res) => { const { id } = req; const form = formidable({ multiples: true }); form.parse(req, async (err, field, files) => { let { name, category, brand, price, stock, discount, description, shopName } = field; const { images } = files; name = name.trim(); const slug = name.split(" ").join("-"); cloudinary.config({ cloud_name: process.env.cloud_name, api_key: process.env.api_key, api_secret: process.env.api_secret, secure: true, }); try { let allImageUrl = []; for (let index = 0; index < images.length; index++) { const result = await cloudinary.uploader.upload(images[index].filepath, { folder: "products" }); allImageUrl = [...allImageUrl, result.url]; } await productModel.create({ sellerId: id, name, slug, shopName, category: category.trim(), description: description.trim(), stock: parseInt(stock), price: parseInt(price), discount: parseInt(discount), brand: brand.trim(), images: allImageUrl, }); responseReturn(res, 201, { message: "Product Added Successfully" }); } catch (error) { console.error("Error adding product:", error); responseReturn(res, 500, { error: "Internal Server Error" }); } }); }; products_get = async (req, res) => { const { page, searchValue, parPage } = req.query; const { id } = req; const skipPage = parseInt(parPage) * (parseInt(page) - 1); try { if (searchValue) { const products = await productModel .find({ $text: { $search: searchValue }, sellerId: id, }) .skip(skipPage) .limit(parPage) .sort({ createdAt: -1 }); const totalProduct = await productModel .find({ $text: { $search: searchValue }, sellerId: id, }) .countDocuments(); responseReturn(res, 200, { products, totalProduct }); } else { const products = await productModel .find({ sellerId: id }) .skip(skipPage) .limit(parPage) .sort({ createdAt: -1 }); const totalProduct = await productModel.find({ sellerId: id }).countDocuments(); responseReturn(res, 200, { products, totalProduct }); } } catch (error) { console.log(error); } }; product_get = async (req, res) => { const { productId } = req.params; try { const product = await productModel.findById(productId); responseReturn(res, 200, { product }); } catch (error) { console.log(error.message); } }; } module.exports = new productController();
productRoutes.js
const productController = require("./../../controllers/dashboard/productController"); const { authMiddleware } = require("../../middlewares/authMiddleware"); const router = require("express").Router(); router.post("/product-add", authMiddleware, productController.add_product); router.get("/products-get", authMiddleware, productController.products_get); router.get("/product-get/:productId", authMiddleware, productController.product_get); module.exports = router;
前端代码
EditProduct.jsx
import React, { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { get_category } from "./../../store/Reducers/categoryReducer"; import { get_product } from "./../../store/Reducers/productReducer"; function EditProduct() { const { productId } = useParams(); const dispatch = useDispatch(); const { categories } = useSelector((state) => state.category); const { product } = useSelector((state) => state.product); useEffect(() => { dispatch( get_category({ searchValue: "", parPage: "", page: "", }) ); }, []); useEffect(() => { dispatch( get_product({ productId }) ); }, [productId]); const [categoryShow, setCategoryShow] = useState(false); const [category, setCategory] = useState(""); const [allCategory, setAllCategory] = useState(categories); const [searchValue, setSearchValue] = useState(""); const categorySearch = (event) => { const value = event.target.value; setSearchValue(value); if (value) { let searchValue = allCategory.filter( (category) => category.name.toLowerCase().indexOf(value.toLowerCase()) > -1 ); setAllCategory(searchValue); } else { setAllCategory(categories); } }; const [state, setState] = useState({ name: "", description: "", discount: "", price: "", brand: "", stock: "", }); const inputHandle = (event) => { setState({ ...state, [event.target.name]: event.target.value, }); }; const [images, setImages] = useState([]); const [imageShow, setImageShow] = useState([]); const changeImage = (img, files) => { if (files.length > 0) { console.log(files[0]); console.log(img); } }; useEffect(() => { setState({ name: product.name, description: product.description, discount: product.discount, price: product.price, brand: product.brand, stock: product.stock, }); setCategory(product.category); setImageShow(product.images); }, [product]); }
Product.jsx(代码片段)
<Link to={`/seller/dashboard/edit-product/${product._id}`} className="p-[6px] bg-yellow-500 rounded hover:shadow-lg hover:shadow-yellow-500/50">{''} <FaEdit />{''} </Link>
productReducer.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import api from "../../api/api"; export const get_product = createAsyncThunk( "product/get_product", async (productId, { rejectWithValue, fulfillWithValue }) => { try { const { data } = await api.get(`/product-get/${productId}`, { withCredentials: true }); console.log(data); return fulfillWithValue(data); } catch (error) { return rejectWithValue(error.response.data); } } ); export const productReducer = createSlice({ name: "product", initialState: { successMessage: "", errorMessage: "", loader: false, userInfo: "", products: [], product: "", totalProduct: 0, }, reducers: { messageClear: (state, _) => { state.errorMessage = ""; }, }, extraReducers: (builder) => { builder .addCase(add_product.pending, (state, { payload }) => { state.loader = true; }) .addCase(add_product.rejected, (state, { payload }) => { state.loader = false; state.errorMessage = payload.error; }) .addCase(add_product.fulfilled, (state, { payload }) => { state.loader = false; state.successMessage = payload.message; }) .addCase(get_products.fulfilled, (state, { payload }) => { state.totalProduct = payload.totalProduct; state.products = payload.products; }) .addCase(get_product.fulfilled, (state, { payload }) => { state.product = payload.product; }); }, }); export const { messageClear } = productReducer.actions; export default productReducer.reducer;
错误原因
核心问题出在前端Redux异步请求的传参错误:
- 在
EditProduct.jsx中,调用dispatch(get_product({ productId }))时,把productId包装成了对象传入 - 但
productReducer.js里的get_product异步thunk期望接收字符串类型的productId,实际收到的是{ productId: "xxx" }这个对象 - 拼接API URL时,对象被强制转换为字符串
"[object Object]",后端接收到这个无效值后,MongoDB无法将其转换为合法的ObjectId,从而抛出错误
解决方案
1. 修正前端传参方式
在EditProduct.jsx的useEffect中,直接传入productId字符串,不要包装成对象:
useEffect(() => { dispatch(get_product(productId)); // 去掉外层的{} }, [productId]);
2. (可选)后端增加参数校验
为避免类似错误,在productController.jsx的product_get方法中,先校验productId是否为合法的ObjectId:
const mongoose = require('mongoose'); product_get = async (req, res) => { const { productId } = req.params; try { // 校验ObjectId合法性 if (!mongoose.Types.ObjectId.isValid(productId)) { return responseReturn(res, 400, { error: "无效的产品ID" }); } const product = await productModel.findById(productId); if (!product) { return responseReturn(res, 404, { error: "产品不存在" }); } responseReturn(res, 200, { product }); } catch (error) { console.log(error.message); responseReturn(res, 500, { error: "服务器内部错误" }); } };
3. 确认路由参数传递
当前Product.jsx中的链接参数传递是正确的,无需修改:
<Link to={`/seller/dashboard/edit-product/${product._id}`}> <FaEdit /> </Link>
内容的提问来源于stack exchange,提问作者Conrad
相关产品推荐
相关产品推荐

