自定义验证器与错误处理
自定义验证器的基本概念
Mongoose允许通过自定义验证器对数据进行更精细的校验。内置验证器如required
、min
等虽然常用,但面对复杂业务逻辑时往往不够灵活。自定义验证器分为同步和异步两种形式,可以直接在Schema定义中使用。
const userSchema = new mongoose.Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /^1[3456789]\d{9}$/.test(v);
},
message: props => `${props.value} 不是有效的手机号码`
}
}
});
同步验证器的实现
同步验证器立即返回布尔值或错误消息,适合执行简单快速的校验。验证函数接收当前字段值作为参数,通过返回值决定验证是否通过。
const productSchema = new mongoose.Schema({
price: {
type: Number,
validate: {
validator: function(value) {
// 价格必须大于成本价
return value > this.costPrice;
},
message: '售价不能低于成本价'
}
},
costPrice: Number
});
当验证失败时,Mongoose会将错误添加到文档的errors集合中。同步验证器在保存文档前自动执行,可以通过doc.validate()
手动触发。
异步验证器的应用场景
需要数据库查询或网络请求的验证必须使用异步验证器。通过将isAsync
选项设为true,验证函数可以返回Promise或使用回调函数。
const orderSchema = new mongoose.Schema({
couponCode: {
type: String,
validate: {
validator: function(v) {
return new Promise((resolve, reject) => {
CouponModel.findOne({ code: v }, (err, coupon) => {
if (err || !coupon) return resolve(false);
resolve(coupon.isValid());
});
});
},
message: '无效的优惠券代码'
}
}
});
异步验证会在文档保存时自动执行,但需要注意:在更新操作中默认不触发验证,需要显式设置runValidators: true
。
验证错误的处理机制
Mongoose提供多种错误处理方式。当验证失败时,错误信息会附加到文档上,可以通过save
回调或Promise捕获。
const newUser = new User({ phone: '123456' });
newUser.save()
.catch(err => {
console.log(err.errors['phone'].message); // 输出具体错误消息
});
对于批量操作,错误处理需要特别注意。使用Model.updateMany()
时,单个文档验证失败不会阻止其他文档更新,但会返回包含错误信息的对象。
路径级与文档级验证
除了字段级别的验证,Mongoose还支持文档级别的验证。通过Schema的validate
方法可以定义涉及多个字段的复杂逻辑。
const bookingSchema = new mongoose.Schema({
startDate: Date,
endDate: Date,
guests: Number
});
bookingSchema.validate('datesValid', function() {
return this.endDate > this.startDate;
}, '结束日期必须晚于开始日期');
文档级验证在保存前执行,可以访问文档的所有字段。这种方式适合需要跨字段校验的场景,如日期范围、数量限制等。
自定义错误消息的进阶用法
错误消息不仅可以是静态字符串,还能动态生成。通过函数形式的消息模板,可以包含验证失败的上下文信息。
const inventorySchema = new mongoose.Schema({
stock: {
type: Number,
validate: {
validator: function(v) {
return v >= 0;
},
message: props => `库存值 ${props.value} 不能为负数`
}
}
});
对于多语言应用,可以在消息函数中实现国际化逻辑。错误消息也支持模板变量,如{PATH}
、{VALUE}
等内置占位符。
验证器与中间件的配合使用
验证器常与中间件结合实现更完整的业务逻辑。例如在pre('save')
钩子中执行额外校验,或在post('validate')
钩子中记录验证日志。
userSchema.pre('save', function(next) {
if (this.isModified('password')) {
if (this.password.length < 8) {
next(new Error('密码长度至少8位'));
} else {
next();
}
} else {
next();
}
});
这种组合方式既能保持验证逻辑的集中性,又能处理更复杂的业务规则。中间件还可以用于清理或标准化数据后再进行验证。
测试自定义验证的策略
完善的测试对验证逻辑至关重要。测试用例应覆盖各种边界情况,包括有效输入、极端值和故意构造的非法输入。
describe('用户模型验证', () => {
it('应该拒绝无效手机号', async () => {
const user = new User({ phone: '123' });
await expect(user.save()).to.eventually.be.rejectedWith('不是有效的手机号码');
});
it('应该接受有效手机号', async () => {
const user = new User({ phone: '13800138000' });
await expect(user.save()).to.eventually.be.fulfilled;
});
});
使用Mocha+Chai等测试框架可以方便地验证验证器行为。对于异步验证器,需要确保测试正确处理Promise或回调。
性能优化注意事项
复杂的验证逻辑可能影响性能,特别是在批量操作时。有几种优化策略值得考虑:
- 对频繁执行的验证添加缓存
- 将CPU密集型验证移到后台进程
- 对不常变化的参考数据建立内存缓存
const couponCache = new Map();
couponSchema.path('code').validate({
validator: async function(code) {
if (couponCache.has(code)) {
return couponCache.get(code);
}
const valid = await checkCouponValidity(code);
couponCache.set(code, valid);
return valid;
},
message: '优惠券已失效'
});
验证逻辑的重用模式
为避免代码重复,可以将常用验证逻辑提取为可复用组件。Mongoose支持通过插件机制共享验证逻辑。
// validators/phone.js
module.exports = function(schema, options) {
schema.path('phone').validate({
validator: function(v) {
return /^1[3456789]\d{9}$/.test(v);
},
message: '无效的手机号码格式'
});
};
// 在模型中使用
userSchema.plugin(require('./validators/phone'));
对于更复杂的场景,可以创建验证器工厂函数,根据配置生成不同的验证逻辑。这种方式特别适合需要根据不同环境调整验证规则的场景。
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:数据类型与字段验证