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

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);
    }
}

问题分析与修复

核心问题点

  1. 参数格式错误:findByIdAndUpdate的第三个参数是单一配置对象,你拆分传了两个独立的配置对象,导致Mongoose无法正确解析arrayFilters、new等关键选项。
  2. 重复累加bug:循环中每次都会执行$inc: { total_clients: req.body.total_clients },如果sales_report有n条数据,total_clients会被累加n次,完全不符合业务预期。
  3. 选项冗余冲突:returnDocument: 'after'是MongoDB原生驱动选项,Mongoose中用new: true即可实现返回更新后文档,两者同时存在会导致配置冲突。
  4. 错误处理混淆:catch块直接返回404,但404仅对应资源不存在,数据库操作错误等情况应该返回500,否则会误导问题定位。
  5. 低效循环更新:循环调用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 });
    }
}

关键修复说明

  1. 合并配置选项:将所有配置参数合并为单一对象,确保Mongoose正确解析arrayFilters、new等选项。
  2. 优化统计逻辑:先循环计算出total_product_sold和sales_revenue的总和,再一次性提交更新,避免重复累加和多次数据库请求。
  3. 批量数组更新:动态生成数组过滤器和$set路径,实现多月份数据的批量匹配更新。
  4. 精准错误处理:区分无效ID、资源不存在、服务器错误三种场景,返回对应状态码并打印错误详情便于调试。
  5. 移除冗余配置:删除safe: true(Mongoose默认已处理),统一使用new: true替代returnDocument。

内容的提问来源于stack exchange,提问作者Gino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:10:24