乐闻世界logo
搜索文章和话题

Mongoose 插件如何创建和使用?

2月22日 20:12

Mongoose 插件(Plugins)是一种可重用的机制,用于扩展 Mongoose Schema 的功能。插件允许你将通用功能封装起来,并在多个 Schema 中复用。

基本插件

创建简单插件

javascript
// timestamp.js function timestampPlugin(schema) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(next) { this.updatedAt = new Date(); next(); }); } module.exports = timestampPlugin;

使用插件

javascript
const timestampPlugin = require('./plugins/timestamp'); const userSchema = new Schema({ name: String, email: String }); userSchema.plugin(timestampPlugin); const User = mongoose.model('User', userSchema);

插件选项

带选项的插件

javascript
// softDelete.js function softDeletePlugin(schema, options = {}) { const deletedAtField = options.deletedAtField || 'deletedAt'; const isDeletedField = options.isDeletedField || 'isDeleted'; schema.add({ [deletedAtField]: Date, [isDeletedField]: { type: Boolean, default: false } }); schema.pre('find', function() { this.where({ [isDeletedField]: { $ne: true } }); }); schema.pre('findOne', function() { this.where({ [isDeletedField]: { $ne: true } }); }); schema.methods.softDelete = function() { this[isDeletedField] = true; this[deletedAtField] = new Date(); return this.save(); }; } module.exports = softDeletePlugin; // 使用带选项的插件 userSchema.plugin(softDeletePlugin, { deletedAtField: 'deletedAt', isDeletedField: 'isDeleted' });

常用插件类型

1. 时间戳插件

javascript
function timestampPlugin(schema) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(next) { this.updatedAt = new Date(); next(); }); }

2. 软删除插件

javascript
function softDeletePlugin(schema) { schema.add({ deletedAt: Date, isDeleted: { type: Boolean, default: false } }); schema.pre('find', function() { this.where({ isDeleted: { $ne: true } }); }); schema.pre('findOne', function() { this.where({ isDeleted: { $ne: true } }); }); schema.methods.softDelete = function() { this.isDeleted = true; this.deletedAt = new Date(); return this.save(); }; schema.statics.findDeleted = function() { return this.find({ isDeleted: true }); }; }

3. 分页插件

javascript
function paginatePlugin(schema) { schema.statics.paginate = function(query = {}, options = {}) { const { page = 1, limit = 10, sort = {}, populate = [] } = options; const skip = (page - 1) * limit; return Promise.all([ this.countDocuments(query), this.find(query) .sort(sort) .skip(skip) .limit(limit) .populate(populate) ]).then(([total, docs]) => ({ docs, total, page, pages: Math.ceil(total / limit) })); }; } // 使用分页插件 const result = await User.paginate( { status: 'active' }, { page: 1, limit: 10, sort: { name: 1 } } );

4. 自动填充插件

javascript
function autoPopulatePlugin(schema, options) { const fields = options.fields || []; schema.pre('find', function() { fields.forEach(field => { this.populate(field); }); }); schema.pre('findOne', function() { fields.forEach(field => { this.populate(field); }); }); } // 使用自动填充插件 userSchema.plugin(autoPopulatePlugin, { fields: ['profile', 'settings'] });

5. 加密插件

javascript
const bcrypt = require('bcrypt'); function encryptionPlugin(schema, options) { const fields = options.fields || ['password']; schema.pre('save', async function(next) { for (const field of fields) { if (this.isModified(field)) { this[field] = await bcrypt.hash(this[field], 10); } } next(); }); schema.methods.comparePassword = async function(candidatePassword) { return bcrypt.compare(candidatePassword, this.password); }; } // 使用加密插件 userSchema.plugin(encryptionPlugin, { fields: ['password'] });

插件组合

多个插件

javascript
const timestampPlugin = require('./plugins/timestamp'); const softDeletePlugin = require('./plugins/softDelete'); const paginatePlugin = require('./plugins/paginate'); userSchema.plugin(timestampPlugin); userSchema.plugin(softDeletePlugin); userSchema.plugin(paginatePlugin);

插件依赖

javascript
function advancedPlugin(schema, options) { // 依赖其他插件的功能 if (!schema.path('createdAt')) { throw new Error('timestampPlugin must be applied before advancedPlugin'); } // 使用其他插件添加的字段 schema.virtual('age').get(function() { return Date.now() - this.createdAt.getTime(); }); }

全局插件

应用到所有 Schema

javascript
// 全局应用插件 mongoose.plugin(timestampPlugin); // 之后创建的所有 Schema 都会自动应用该插件 const userSchema = new Schema({ name: String }); const postSchema = new Schema({ title: String }); // 两个 Schema 都会有 createdAt 和 updatedAt 字段

条件全局插件

javascript
// 只对特定模型应用插件 mongoose.plugin(function(schema) { if (schema.options.enableTimestamps) { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); } }); // 在 Schema 选项中启用 const userSchema = new Schema({ name: String }, { enableTimestamps: true });

插件最佳实践

1. 插件命名

javascript
// 使用清晰的命名 const timestampPlugin = require('./plugins/timestamp'); const softDeletePlugin = require('./plugins/softDelete');

2. 插件文档

javascript
/** * 软删除插件 * * 功能: * - 添加 deletedAt 和 isDeleted 字段 * - 自动过滤已删除的文档 * - 提供 softDelete() 方法 * * 选项: * - deletedAtField: 删除时间字段名(默认:deletedAt) * - isDeletedField: 删除标记字段名(默认:isDeleted) */ function softDeletePlugin(schema, options = {}) { // 插件实现 }

3. 插件测试

javascript
// plugins/softDelete.test.js const mongoose = require('mongoose'); const softDeletePlugin = require('./softDelete'); describe('softDeletePlugin', () => { let User; beforeAll(async () => { await mongoose.connect('mongodb://localhost/test'); const userSchema = new Schema({ name: String }); userSchema.plugin(softDeletePlugin); User = mongoose.model('User', userSchema); }); it('should add soft delete fields', async () => { const user = await User.create({ name: 'John' }); expect(user.isDeleted).toBe(false); expect(user.deletedAt).toBeUndefined(); }); it('should filter deleted documents', async () => { const user = await User.create({ name: 'Jane' }); await user.softDelete(); const users = await User.find(); expect(users.length).toBe(0); }); });

发布插件

NPM 包结构

shell
mongoose-plugin-softdelete/ ├── package.json ├── README.md ├── index.js └── test/ └── softDelete.test.js

package.json

json
{ "name": "mongoose-plugin-softdelete", "version": "1.0.0", "description": "Mongoose plugin for soft delete functionality", "main": "index.js", "keywords": ["mongoose", "plugin", "soft-delete"], "peerDependencies": { "mongoose": ">=6.0.0" } }

最佳实践

  1. 保持插件简单:每个插件只做一件事
  2. 提供选项:允许用户自定义插件行为
  3. 文档完善:提供清晰的文档和示例
  4. 测试覆盖:为插件编写完整的测试
  5. 版本管理:使用语义化版本控制
  6. 错误处理:妥善处理错误情况
  7. 性能考虑:避免插件影响性能
  8. 向后兼容:保持 API 的向后兼容性
标签:Mongoose