开发Mongoose软删除插件:过滤与处理根文档及嵌套数组文档
实现支持根文档与嵌套数组的Mongoose软删除插件
一、核心插件实现
以下是满足需求的Mongoose软删除插件代码,包含自动过滤、软删除方法及查询控制功能:
const mongoose = require('mongoose'); const softDeletePlugin = (schema, options = {}) => { // 为根文档添加软删除标识字段,默认未删除 schema.add({ isDeleted: { type: Boolean, default: false, index: true } }); // 配置需要处理的嵌套数组字段(需提前指定) const nestedArrayFields = options.nestedArrayFields || []; // Pre-find钩子:自动过滤已删除条目 schema.pre(['find', 'findOne', 'findById'], function(next) { const query = this.getQuery(); // 若查询包含includeDeleted: true,跳过所有过滤逻辑 if (query.includeDeleted) { delete query.includeDeleted; return next(); } // 过滤根文档:仅当用户未显式指定isDeleted条件时,排除已删除文档 if (!query.isDeleted) { this.where({ isDeleted: { $ne: true } }); } // 过滤嵌套数组:仅返回数组中未删除的元素 if (nestedArrayFields.length > 0) { // 保留用户指定的投影字段,避免覆盖原有需求 const projectStage = { ...this.getProjection() }; nestedArrayFields.forEach(field => { projectStage[field] = { $filter: { input: `$${field}`, as: 'item', cond: { $eq: ['$$item.isDeleted', false] } } }; }); // 将find查询转换为聚合管道,实现数组元素过滤 this.aggregate([ { $match: this.getQuery() }, { $project: projectStage } ]); } next(); }); // 根文档软删除实例方法 schema.methods.softDelete = async function() { this.isDeleted = true; return this.save(); }; // 嵌套数组条目软删除静态方法 schema.statics.softDeleteNestedItem = async function(docId, arrayField, itemKey, itemValue) { return this.updateOne( { _id: docId, [`${arrayField}.${itemKey}`]: itemValue }, { $set: { [`${arrayField}.$.isDeleted`]: true } } ); }; // 根文档恢复实例方法 schema.methods.restore = async function() { this.isDeleted = false; return this.save(); }; // 嵌套数组条目恢复静态方法 schema.statics.restoreNestedItem = async function(docId, arrayField, itemKey, itemValue) { return this.updateOne( { _id: docId, [`${arrayField}.${itemKey}`]: itemValue }, { $set: { [`${arrayField}.$.isDeleted`]: false } } ); }; }; module.exports = softDeletePlugin;
二、核心功能说明
1. 自动过滤逻辑
- 根文档过滤:通过pre-find钩子自动添加
isDeleted: { $ne: true }条件,排除已软删除的根文档;若用户显式指定isDeleted查询条件,插件不会覆盖。 - 嵌套数组过滤:利用MongoDB的
$filter聚合操作,在查询时仅保留数组中isDeleted: false的元素,需在插件初始化时指定需要处理的数组字段。
2. 软删除与恢复方法
- 根文档:通过实例方法
softDelete()和restore()直接修改文档的isDeleted状态并保存。 - 嵌套数组:通过静态方法
softDeleteNestedItem()和restoreNestedItem(),根据数组元素的唯一标识(如id)定位并修改对应条目的状态。
3. 包含已删除条目查询
在查询时添加includeDeleted: true参数,插件会跳过所有过滤逻辑,返回包含已软删除的根文档和嵌套数组条目。
三、使用示例
1. 定义Schema并应用插件
const userSchema = new mongoose.Schema({ name: String, email: String, age: Number, nestedArr: [{ id: Number, value: String, isDeleted: { type: Boolean, default: false } }] }); // 应用插件,指定需要处理的嵌套数组字段 userSchema.plugin(softDeletePlugin, { nestedArrayFields: ['nestedArr'] }); const User = mongoose.model('User', userSchema);
2. 基本查询(自动过滤已删除项)
// 查询所有未删除的用户,且nestedArr仅包含未删除的条目 const activeUsers = await User.find();
3. 查询包含已删除项
// 查询所有用户(包括已删除的根文档),返回nestedArr的所有条目 const allUsers = await User.find({ includeDeleted: true });
4. 软删除根文档
const user = await User.findById('66c883a5e6ddbf5f720ae1b6'); await user.softDelete();
5. 软删除嵌套数组条目
// 删除nestedArr中id为4086837的条目 await User.softDeleteNestedItem('66c883a5e6ddbf5f720ae1b6', 'nestedArr', 'id', 4086837);
6. 恢复操作
// 恢复根文档(需先查询到已删除的文档) const deletedUser = await User.findById('66c883a5e6ddbf5f720ae1b6', { includeDeleted: true }); await deletedUser.restore(); // 恢复嵌套数组条目 await User.restoreNestedItem('66c883a5e6ddbf5f720ae1b6', 'nestedArr', 'id', 4086837);
四、注意事项
- 索引优化:为
isDeleted字段添加索引,提升根文档过滤的查询效率;若需保障未删除文档的字段唯一性,可添加复合索引(如{ email: 1, isDeleted: 1 })。 - 嵌套数组配置:需明确指定需要处理的嵌套数组字段,避免误处理不需要软删除的数组。
- 聚合转换限制:当查询转换为聚合管道时,部分mongoose链式方法(如
populate)无法直接使用,需改用聚合的$lookup实现关联查询。
内容的提问来源于stack exchange,提问作者mongoPioneer
相关产品推荐
相关产品推荐

