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
相关产品推荐
相关产品推荐

