阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 自定义验证器与错误处理

自定义验证器与错误处理

作者:陈川 阅读数:1495人阅读 分类: MongoDB

自定义验证器的基本概念

Mongoose允许通过自定义验证器对数据进行更精细的校验。内置验证器如requiredmin等虽然常用,但面对复杂业务逻辑时往往不够灵活。自定义验证器分为同步和异步两种形式,可以直接在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或回调。

性能优化注意事项

复杂的验证逻辑可能影响性能,特别是在批量操作时。有几种优化策略值得考虑:

  1. 对频繁执行的验证添加缓存
  2. 将CPU密集型验证移到后台进程
  3. 对不常变化的参考数据建立内存缓存
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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌