Skip to content

Node系列 · ORM:数据验证

数据验证是后端的"第一道防线"——不合法数据应该在入库前被拦下。本章讲清楚字符串级验证(validator)、对象级 schema 验证(validator.js / Joi)、以及验证的层次划分。

一、为什么要验证

风险验证可解决的问题
数据脏邮箱格式错、手机号位数错,导致后续短信发不出去
安全XSS(name = '<script>...')、SQL 注入(name = "1' OR ..."
业务错数量为负、起止日期倒置
崩溃字段长度超出数据库限制,写入报错

DANGER

永远不要信任客户端传来的数据。前端表单验证只是 UX,后端验证才是安全底线——绕过前端是分分钟的事。

二、字符串级验证:validator

最常用的字符串验证库。

bash
npm install validator

2.1 常用验证方法

javascript
const validator = require('validator');

// 邮箱
validator.isEmail('alice@example.com');     // true
validator.isEmail('not-an-email');          // false

// 手机号(不同国家规则不同)
validator.isMobilePhone('13800138000', 'zh-CN');  // true
validator.isMobilePhone('13800138000', 'en-US');  // false

// URL
validator.isURL('https://example.com');     // true

// IP
validator.isIP('192.168.1.1');              // true
validator.isIP('192.168.1.1', 'ipv4');      // 显式指定
validator.isIP('::1', 'ipv6');               // true

// 邮箱(更严格)
validator.isEmail('a@b', { allow_display_name: false });

// 是否数字
validator.isNumeric('123');                  // true
validator.isInt('42');                       // true
validator.isFloat('3.14');                   // true

// 是否 URL slug / UUID
validator.isSlug('hello-world');             // true
validator.isUUID('550e8400-e29b-41d4-a716-446655440000');  // true

2.2 字符串清洗

javascript
// 转义 HTML(防 XSS)
validator.escape('<script>alert("xss")</script>');
// '&lt;script&gt;alert(&quot;xss&quot;)&lt;&#x2F;script&gt;'

// 去除首尾空白
validator.trim('  hello  ');     // 'hello'

// 规范化邮箱(小写)
validator.normalizeEmail('Alice@Example.COM');
// 'alice@example.com'

// 去除 ASCII 控制字符(0x00-0x1F)
const dirty = "hello�world";  // 含 BEL(0x07) 与 NUL(0x00)
const clean = validator.stripLow(dirty);
// 'helloworld'```

### 2.3 自定义错误信息

```javascript:validator-errors.js
if (!validator.isEmail(email)) {
  throw new Error('邮箱格式不正确');
}

if (!validator.isMobilePhone(phone, 'zh-CN')) {
  throw new Error('手机号必须是 11 位数字');
}

三、对象级验证:validator.js

对复杂对象做 schema 验证。validator.js(npm 包名)不是 validator(字符串验证库)——注意区分。

bash
npm install validatorjs

3.1 基本用法

javascript
const Validator = require('validatorjs');

const data = {
  name: 'Alice',
  email: 'alice@example.com',
  age: 25,
};

const rules = {
  name: 'required|string|min:2|max:50',
  email: 'required|email',
  age: 'required|integer|min:0|max:150',
};

const validation = new Validator(data, rules);

if (validation.fails()) {
  console.log(validation.errors.all());
  // {
  //   email: ['The email format is invalid.'],
  //   age: ['The age must be an integer.']
  // }
} else {
  console.log('验证通过');
}

3.2 常用规则

规则含义
required必填
string / integer / numeric / boolean类型
email / url / uuid格式
min:n / max:n最小/最大
size:n长度等于 n
in:a,b,c在枚举中
regex:/pattern/正则
same:field与某字段相等
different:field与某字段不等
confirmedxxx_confirmation 字段相等(密码二次确认)

3.3 自定义规则

javascript
Validator.register('strong_password', (value) => {
  return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(value);
}, '密码必须 8 位以上,含大小写字母和数字');

// 使用
const rules = { password: 'required|string|strong_password' };

3.4 异步验证

javascript
Validator.registerAsync('unique_email', async (value, attribute, req, passes) => {
  const exists = await User.findOne({ where: { email: value } });
  if (exists) {
    return passes(false, '邮箱已被注册');
  }
  return passes();
}, '邮箱唯一性校验');

// 使用
const validation = new Validator(data, {
  email: 'required|email|unique_email',
});

validation.checkAsync(() => {
  if (validation.fails()) {
    return res.status(400).json({ errors: validation.errors.all() });
  }
  // 继续处理
});

四、其他验证库对比

风格大小异步推荐
validator函数式字符串验证很小✅ 基础工具
validatorjsschema 字符串✅ 中等项目
Joi链式 schema✅ 大型项目
Yupschema(类 Joi)✅ 前后端共享
ZodTS schema✅ TS 项目首选
class-validator装饰器(TS)NestJS 项目
ajvJSON Schema需要 JSON Schema 兼容

4.1 Joi 示例

javascript
const Joi = require('joi');

const schema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(150).required(),
});

const { error, value } = schema.validate(req.body);
if (error) return res.status(400).json({ error: error.details });
// value 是类型安全的(TS 推断)

4.2 Zod 示例(TS 项目)

typescript
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

type User = z.infer<typeof UserSchema>;  // 自动推导 TS 类型

const result = UserSchema.safeParse(req.body);
if (!result.success) {
  return res.status(400).json({ errors: result.error.flatten() });
}
const user: User = result.data;  // 类型安全

五、验证的层次

数据验证应该在多层进行,每层有不同的关注点:

层次关注工具
前端表单必填、长度、即时格式提示HTML5 validation / 表单库
API 接收类型、格式、字段约束Joi / Zod / validatorjs
数据库唯一键、外键、CHECKMySQL 约束
业务逻辑跨字段、跨表、异步唯一性应用代码

TIP

不要把验证只放在前端——绕过前端是分分钟的事。也不要把跨表逻辑放在数据库约束里(性能差、维护难)。合理的层次是:

  • 前端:UX 提示,不安全
  • 后端 API:硬验证,必须
  • 数据库:兜底约束(避免最坏情况)
  • 业务逻辑:跨字段、异步规则

六、与 ORM 集成

Sequelize 的 validate 只做字段级校验,不支持跨字段。要更复杂校验用 validatorjs / Joi:

javascript
const Validator = require('validatorjs');
const { User } = require('./models');

// Express 中间件风格
function validateUser(req, res, next) {
  const rules = {
    name: 'required|string|min:2|max:50',
    email: 'required|email',
    password: 'required|string|min:8',
    passwordConfirm: 'required|same:password',
  };

  const validation = new Validator(req.body, rules);

  if (validation.fails()) {
    return res.status(400).json({ errors: validation.errors.all() });
  }

  next();
}

app.post('/api/users', validateUser, async (req, res) => {
  // 验证通过,处理业务
});

七、最佳实践

场景推荐
字符串验证validator(isEmail / isMobilePhone / isURL)
对象验证Joi / Zod(TS 项目)
XSS 防护输入时 validator.escape,输出时模板默认转义
唯一性异步验证(查数据库),不要在同步 schema 里做
错误响应返回 { field: [msg, ...] } 结构,前端按字段提示
验证位置API 层必须有;数据库层做兜底
时机越早越好;在控制器入口验证,不要到 service 层才检查

八、小结

  • 字符串验证:validator.isEmail / isMobilePhone / isURL / escape
  • 对象验证:validatorjs(轻量)/ Joi(强大)/ Zod(TS 首选)
  • 验证分四层:前端 UX / API 必填 / 数据库兜底 / 业务逻辑
  • 永远不在前端验证作为安全防线——后端 API 必须做硬验证
  • 异步唯一性用 Validator.registerAsync + 数据库查询
  • XSS 用 validator.escape 输入转义;输出靠框架默认转义