Mongoose 中间件(Middleware)和钩子(Hooks)是强大的功能,允许在执行某些操作之前或之后执行自定义逻辑。中间件分为两类:文档中间件和查询中间件。
中间件类型
1. 文档中间件(Document Middleware)
在文档实例上执行的操作,如 save()、validate()、remove() 等。
javascriptuserSchema.pre('save', function(next) { console.log('About to save user:', this.name); next(); }); userSchema.post('save', function(doc) { console.log('User saved:', doc.name); });
2. 查询中间件(Query Middleware)
在 Model 查询上执行的操作,如 find()、findOne()、updateOne() 等。
javascriptuserSchema.pre('find', function() { this.where({ deleted: false }); }); userSchema.post('find', function(docs) { console.log('Found', docs.length, 'users'); });
常用钩子
文档操作钩子
validate- 验证文档save- 保存文档remove- 删除文档init- 初始化文档(从数据库加载)
查询操作钩子
count- 计数查询find- 查找文档findOne- 查找单个文档findOneAndDelete- 查找并删除findOneAndUpdate- 查找并更新updateOne- 更新单个文档updateMany- 更新多个文档deleteOne- 删除单个文档deleteMany- 删除多个文档
Pre 和 Post 钩子的区别
Pre 钩子
- 在操作执行前运行
- 可以修改数据或中止操作
- 必须调用
next()或返回 Promise - 可以访问
this(文档实例或查询对象)
javascriptuserSchema.pre('save', function(next) { if (this.age < 0) { const err = new Error('Age cannot be negative'); return next(err); } this.email = this.email.toLowerCase(); next(); });
Post 钩子
- 在操作执行后运行
- 不能修改数据或中止操作
- 接收操作结果作为参数
- 可以访问
this(文档实例或查询对象)
javascriptuserSchema.post('save', function(doc) { console.log('User saved with ID:', doc._id); // 发送通知、记录日志等 });
异步中间件
Mongoose 中间件支持异步操作:
javascript// 使用 async/await userSchema.pre('save', async function(next) { const existing = await this.constructor.findOne({ email: this.email }); if (existing && existing._id.toString() !== this._id.toString()) { const err = new Error('Email already exists'); return next(err); } next(); }); // 返回 Promise userSchema.pre('save', function() { return checkEmailAvailability(this.email).then(isAvailable => { if (!isAvailable) { throw new Error('Email already exists'); } }); });
实际应用场景
- 密码哈希:在保存用户前对密码进行加密
- 时间戳:自动设置 createdAt 和 updatedAt
- 软删除:在删除前标记为已删除
- 数据验证:执行复杂的验证逻辑
- 日志记录:记录操作历史
- 缓存失效:更新相关缓存
- 关联数据:自动更新关联文档
- 通知发送:在操作后发送通知
注意事项
- 中间件按定义顺序执行
pre钩子中的错误会中止操作- 查询中间件不会触发文档中间件
- 使用
findOneAndUpdate等方法时,需要设置{ runValidators: true }来触发验证 - 中间件中避免无限循环