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

Node.js+MongoDB购物车同ID多尺码商品购买更新报错

问题场景

商品模块支持尺码选择,同一款商品选择不同尺码加入购物车时会生成独立购物车条目,效果如下:
购物车同商品不同尺码独立条目效果
业务要求:用户结算购物车成功后,本次已购买的对应尺码需要从商品详情页的可选尺码列表中移除。
现有逻辑问题:

  • 购买2款不同ID商品时功能正常
  • 购买同ID、不同尺码的商品时,仅第一个尺码会被成功过滤,抛出版本错误:
(node:21336) UnhandledPromiseRejectionWarning: VersionError: No matching document found for id "62bee4ce92e7c57a195686ae" version 0 modifiedPaths "size"
  • 调整代码后出现新报错:product.save is not a function

问题根因
  1. 版本错误原因:原代码使用forEach搭配async回调时,所有异步数据库操作是并行执行的。遇到同商品ID不同尺码的场景,多个并行任务会同时查询到版本号为0的同一份商品文档,第一个任务save成功后文档版本号会自增为1,后续任务拿着旧版本0的文档执行save,就会触发MongoDB的版本校验冲突,抛出匹配不到对应版本文档的错误。另外forEach不会等待异步回调执行完成,会出现商品数据还没更新完就已经生成订单返回的不一致问题。
  2. save不存在报错原因:Product.find()返回的是匹配到的文档数组,数组对象本身没有save方法,直接对查询结果数组调用save()必然报错。同时调整后的代码用map遍历修改数组内文档时,没有对单个文档执行保存,逻辑本身也不成立。

修复方案

核心修复思路:先聚合每个商品需要移除的已购尺码,对同一个商品只执行一次查询、一次保存,从根源避免同文档并行更新的版本冲突,同时保证所有数据库操作按顺序执行完成后再生成订单。

修复后完整代码

createOrder: async (_, {}, context) => {
  const userAuth = await auth(context);
  const cart = await Cart.findOne({ userId: userAuth._id });

  if (!cart) {
    throw new UserInputError('No available order!');
  }

  // 聚合每个商品ID对应的已购尺码,同商品多尺码合并处理
  const productPurchasedSizeMap = new Map();
  cart.cartProducts.forEach(item => {
    const pid = item.productId.toString();
    const size = +item.size;
    if (!productPurchasedSizeMap.has(pid)) {
      productPurchasedSizeMap.set(pid, new Set());
    }
    productPurchasedSizeMap.get(pid).add(size);
  });

  // 逐商品更新可选尺码,单次查询+单次保存避免版本冲突
  for (const [pid, sizesToRemove] of productPurchasedSizeMap) {
    const product = await Product.findById(pid);
    if (product) {
      product.size = product.size.filter(stockSize => !sizesToRemove.has(stockSize));
      await product.save();
    }
  }

  const newOrder = new Order({
    orderProducts: cart.cartProducts,
    purchasedBy: userAuth._id,
    datePurchased: new Date().toISOString(),
  });

  await newOrder.save();

  // 可选:下单成功后清空已结算的购物车内容
  // cart.cartProducts = [];
  // await cart.save();

  return newOrder;
},

高性能批量更新写法(可选)

如果商品量级大,可以用MongoDB的bulkWrite做批量更新,省去逐个查询文档的步骤,性能更高,也不会出现版本冲突问题:

// 替换上面逐商品更新的for循环即可
const bulkUpdateTasks = [];
for (const [pid, sizesToRemove] of productPurchasedSizeMap) {
  bulkUpdateTasks.push({
    updateOne: {
      filter: { _id: pid },
      // $pullAll 直接从数组中移除所有传入的尺码值
      update: { $pullAll: { size: Array.from(sizesToRemove) } }
    }
  });
}
if (bulkUpdateTasks.length) {
  await Product.bulkWrite(bulkUpdateTasks);
}

内容的提问来源于stack exchange,提问作者עמית שוקרון

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:39:19