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

Mongoose实现自动为所有查询添加过滤条件的方法

嘿,这个需求太典型了——不用改现有一堆查询代码,就能给所有Mongoose操作自动加上accountId的范围过滤,完全可以优雅实现!我给你两种靠谱的方案,优先推荐Mongoose插件的方式,因为更贴合它的设计规范,后续维护也更省心。

方案一:使用Mongoose全局插件(推荐)

Mongoose的插件机制可以让你给所有模型统一注入逻辑,完美适配你的“全局过滤”需求。核心思路是给查询、更新、删除等操作添加前置钩子,自动合并accountId过滤条件。

1. 编写全局过滤插件

这个插件会监听所有常用的数据库操作,自动给查询条件加上accountId的$in过滤:

const accountFilterPlugin = function(schema) {
  // 定义需要处理的查询类方法
  const queryMethods = ['find', 'findOne', 'findById', 'countDocuments', 'estimatedDocumentCount'];
  // 定义需要处理的更新/删除类方法
  const modifyMethods = ['updateOne', 'updateMany', 'findOneAndUpdate', 'deleteOne', 'deleteMany', 'findOneAndDelete'];

  // 处理普通查询方法
  queryMethods.forEach(method => {
    schema.pre(method, function(next) {
      // 从查询选项中获取当前用户允许访问的accountId列表(需根据你的业务调整获取方式)
      const allowedAccounts = this._mongooseOptions?.user?.allowedAccounts;
      
      if (allowedAccounts && allowedAccounts.length > 0) {
        const existingFilter = this.getQuery();
        // 合并原有条件与accountId过滤条件
        this.setQuery({
          $and: [existingFilter, { accountId: { $in: allowedAccounts } }]
        });
      }
      next();
    });
  });

  // 处理更新/删除方法
  modifyMethods.forEach(method => {
    schema.pre(method, function(next) {
      const allowedAccounts = this._mongooseOptions?.user?.allowedAccounts;
      
      if (allowedAccounts && allowedAccounts.length > 0) {
        const existingFilter = this.getQuery();
        this.setQuery({
          $and: [existingFilter, { accountId: { $in: allowedAccounts } }]
        });
      }
      next();
    });
  });

  // 单独处理aggregate(聚合查询)
  schema.pre('aggregate', function(next) {
    const allowedAccounts = this._mongooseOptions?.user?.allowedAccounts;
    
    if (allowedAccounts && allowedAccounts.length > 0) {
      const pipeline = this.pipeline();
      const matchStage = { $match: { accountId: { $in: allowedAccounts } } };
      
      // 如果聚合管道第一个阶段已经是$match,就合并条件;否则插入到开头
      if (pipeline[0]?.$match) {
        pipeline[0].$match = { $and: [pipeline[0].$match, matchStage.$match] };
      } else {
        pipeline.unshift(matchStage);
      }
    }
    next();
  });
};

2. 注册全局插件

在你初始化Mongoose连接的代码里,注册这个插件,这样所有模型都会自动应用过滤逻辑:

const mongoose = require('mongoose');

// 连接数据库后注册插件
mongoose.connect('mongodb://localhost:27017/your-db')
  .then(() => {
    mongoose.plugin(accountFilterPlugin);
    console.log('全局account过滤插件已注册');
  });

3. 传递用户权限信息

查询时,把当前用户的allowedAccounts通过查询选项传入即可:

// 示例:Express路由中的查询(假设req.user包含用户权限信息)
app.get('/customers', async (req, res) => {
  try {
    // 第三个参数是查询选项,传入用户信息
    const customers = await Customer.find({}, null, { user: req.user });
    res.json(customers);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});
方案二:猴子补丁修改Mongoose.Query原型

如果你不想用插件,也可以直接修改Mongoose的Query原型(俗称“猴子补丁”),实现全局过滤。这种方式更“侵入式”,但同样有效:

const mongoose = require('mongoose');

// 重写setQuery方法,自动合并过滤条件
const originalSetQuery = mongoose.Query.prototype.setQuery;
mongoose.Query.prototype.setQuery = function(filter) {
  const allowedAccounts = this.options?.user?.allowedAccounts;
  
  if (allowedAccounts && allowedAccounts.length > 0) {
    const filteredFilter = {
      $and: [filter || {}, { accountId: { $in: allowedAccounts } }]
    };
    return originalSetQuery.call(this, filteredFilter);
  }
  return originalSetQuery.call(this, filter);
};

// 单独处理aggregate
const originalAggregate = mongoose.Query.prototype.aggregate;
mongoose.Query.prototype.aggregate = function(pipeline) {
  const allowedAccounts = this.options?.user?.allowedAccounts;
  
  if (allowedAccounts && allowedAccounts.length > 0) {
    const matchStage = { $match: { accountId: { $in: allowedAccounts } } };
    if (Array.isArray(pipeline)) {
      if (pipeline[0]?.$match) {
        pipeline[0].$match = { $and: [pipeline[0].$match, matchStage.$match] };
      } else {
        pipeline.unshift(matchStage);
      }
    } else {
      pipeline = [matchStage];
    }
  }
  return originalAggregate.call(this, pipeline);
};
关键注意事项
  • 权限上下文传递:不管用哪种方案,都要确保能拿到当前用户的allowedAccounts。如果是Express/Koa这类框架,除了通过查询选项传入,还可以用async_hooks实现请求上下文的自动传递(更优雅,不用每次查询都传选项)。
  • 跳过过滤的开关:如果有少数查询不需要过滤,可以加个开关,比如在查询选项里加skipAccountFilter: true,然后在插件/补丁里判断,存在该选项就跳过过滤。
  • 索引优化:给accountId字段加索引(schema.index({ accountId: 1 })),避免过滤操作拖慢查询性能。
  • 测试覆盖:一定要测试空条件、已有复杂条件、聚合查询、更新/删除等所有场景,确保过滤条件被正确合并。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:11:08