Mongoose单请求多操作($set/$addToSet/$inc)报错404求助
Mongoose多操作查询($set/$addToSet/$inc)失效及404问题排查
问题描述
我尝试在销售报表的Mongoose查询中同时使用$set、$addToSet和$inc操作,但功能无法正常运行。发送请求后控制台返回404错误,但req.body中的数据是正确的,怀疑问题出在查询语句上,相关代码如下:
export const report_of_sales = async (req, res) => { const { id } = req.params; console.log(req.body); try { if (!mongoose.Types.ObjectId.isValid(id)) return res.status(404).json({ message: 'Invalid ID' }); let i; for (i = 0; i < req.body.sales_report.length; i++) { await OwnerModels.findByIdAndUpdate(id, { $inc: { total_clients: req.body.total_clients, total_product_sold: req.body.sales_report[i].qty, sales_revenue: req.body.sales_report[i].amount }, $set: { "months.$[s].month_digit": req.body.months[i].month_digit, "months.$[s].targetsales": req.body.months[i].targetsales, "months.$[s].sales": req.body.months[i].sales, }, $addToSet: { sales_report: { $each: [{ identifier: req.body.sales_report[i].identifier, product_name: req.body.sales_report[i].product_name, generic_name: req.body.sales_report[i].generic_name, description: req.body.sales_report[i].description, qty: req.body.sales_report[i].qty, amount: req.body.sales_report[i].amount, profit: req.body.sales_report[i].profit }] } } }, { arrayFilters: [ { "s.month_digit": req.body.months[i].month_digit } ], returnDocument: 'after', safe: true, }, { new: true, upsert: true }) } } catch (error) { res.status(404).json(error); } }
问题分析与修复
核心问题点
- 参数格式错误:
findByIdAndUpdate的第三个参数是单一配置对象,你拆分传了两个独立的配置对象,导致Mongoose无法正确解析arrayFilters、new等关键选项。 - 重复累加bug:循环中每次都会执行
$inc: { total_clients: req.body.total_clients },如果sales_report有n条数据,total_clients会被累加n次,完全不符合业务预期。 - 选项冗余冲突:
returnDocument: 'after'是MongoDB原生驱动选项,Mongoose中用new: true即可实现返回更新后文档,两者同时存在会导致配置冲突。 - 错误处理混淆:catch块直接返回404,但404仅对应资源不存在,数据库操作错误等情况应该返回500,否则会误导问题定位。
- 低效循环更新:循环调用
findByIdAndUpdate会多次发起数据库请求,既影响性能也可能导致数据不一致。
修正后的代码
export const report_of_sales = async (req, res) => { const { id } = req.params; console.log(req.body); try { // 校验ID合法性 if (!mongoose.Types.ObjectId.isValid(id)) { return res.status(404).json({ message: 'Invalid ID' }); } // 初始化更新操作集合 const updateOps = { $inc: { total_clients: req.body.total_clients, total_product_sold: 0, sales_revenue: 0 }, $addToSet: { sales_report: { $each: [] } } }; // 批量计算销售统计,添加销售记录 req.body.sales_report.forEach(item => { updateOps.$inc.total_product_sold += item.qty; updateOps.$inc.sales_revenue += item.amount; updateOps.$addToSet.sales_report.$each.push({ identifier: item.identifier, product_name: item.product_name, generic_name: item.generic_name, description: item.description, qty: item.qty, amount: item.amount, profit: item.profit }); }); // 批量处理月份数据更新 const arrayFilters = []; const setOps = {}; if (req.body.months?.length) { req.body.months.forEach((month, idx) => { const filterKey = `month${idx}`; arrayFilters.push({ [`${filterKey}.month_digit`]: month.month_digit }); setOps[`months.$[${filterKey}].targetsales`] = month.targetsales; setOps[`months.$[${filterKey}].sales`] = month.sales; }); updateOps.$set = setOps; } // 执行单次更新操作 const updatedDoc = await OwnerModels.findByIdAndUpdate( id, updateOps, { arrayFilters, new: true, // 返回更新后的文档 upsert: true // 按需开启:文档不存在则创建 } ); // 校验文档是否存在 if (!updatedDoc) { return res.status(404).json({ message: 'Owner record not found' }); } res.status(200).json(updatedDoc); } catch (error) { console.error('Update failed:', error); res.status(500).json({ message: 'Server error', detail: error.message }); } }
关键修复说明
- 合并配置选项:将所有配置参数合并为单一对象,确保Mongoose正确解析
arrayFilters、new等选项。 - 优化统计逻辑:先循环计算出
total_product_sold和sales_revenue的总和,再一次性提交更新,避免重复累加和多次数据库请求。 - 批量数组更新:动态生成数组过滤器和
$set路径,实现多月份数据的批量匹配更新。 - 精准错误处理:区分无效ID、资源不存在、服务器错误三种场景,返回对应状态码并打印错误详情便于调试。
- 移除冗余配置:删除
safe: true(Mongoose默认已处理),统一使用new: true替代returnDocument。
内容的提问来源于stack exchange,提问作者Gino
相关产品推荐
相关产品推荐

