Sails 模型校验:掌握.validate()同步校验方法与实战细节
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
导读
在 Sails(基于 Node.js 的实时 MVC 框架)应用中,模型属性的校验规则通常在.create()或.update()时自动执行。.validate()是 Waterline ORM 暴露给模型的一个便捷方法,它允许你在不触碰数据库的前提下,针对单个属性预先验证某个值是否符合模型定义的校验规则,并返回"宽松归一化(loosely coerced)"后的结果。本文将以 docs/reference/waterline/models/validate.md 为骨架,结合仓库中的校验规则文档与错误处理文档,完整讲解.validate()的用法、错误协商、适用场景与边界限制,帮助你用它写出更 DRY(Don't Repeat Yourself)的代码。
一、方法签名与参数说明
.validate()是一个模型方法(model method),调用方式如下:
Something.validate(attrName, value);其中Something是某个已定义的数据模型(例如User、BankAccount),它接受两个参数:
| # | 参数 | 可接受的数据类型 | 是否必填 | 说明 |
|---|---|---|---|---|
| 1 | attrName | ((string)) | 是 | 要针对其进行校验的属性名(attribute name) |
| 2 | value | ((ref)) | 是 | 待校验/归一化的值 |
需要特别注意的是:这里的attrName必须是模型attributes中真实存在的属性名(例如emailAddress、password、balance)。value则可以是任意 JavaScript 值,因为它会被当作该属性的"新值"来走一遍完整的校验与类型归一化流程。
方法签名中的
Something.validate(attrName, value)与仓库中其他模型方法(如.create()、.update())处于同一文档体系,均属于 docs/reference/waterline/models/ 目录下的方法参考。
二、核心语义:它是.update()的一次"预演(dry run)"
.validate()的本质,是把传入的数据当作将要传给.update()的valuesToSet中的某个值,进行相同的校验与(可能发生的)归一化。你可以把它理解为:
- 不真正写库;
- 不真正执行查询;
- 只运行内存中的 JavaScript 校验逻辑。
因此,文档明确提示:.validate()不会与数据库通信,它只能发现"逻辑层"的失败——比如类型安全错误(type safety errors)和高层校验规则违规;而无法检测"物理层"的约束,例如唯一性(unique)冲突,因为唯一性约束由底层数据库负责检查,而不是由 Sails 或 Waterline 检查。
这一边界在 docs/concepts/ORM/Validations.md 中也有呼应:除了unique是数据库级约束外,其余所有校验规则均在 Node.js 服务器进程中以 JavaScript 实现并运行。这意味着.validate()能复用的正是这批纯 JavaScript 逻辑层的校验规则。
三、基础示例:校验并归一化单个属性
假设User模型上定义了emailAddress与password两个属性,我们可以在控制器中直接校验来自请求参数的值:
User.validate('emailAddress', req.param('email')); User.validate('password', req.param('password'));流程如下:
- 读取
req.param('email')/req.param('password')拿到的原始值; - 针对
User模型中emailAddress/password属性定义的类型(例如type: 'string')与校验规则(例如isEmail、minLength)进行校验; - 如果校验通过,返回值是"宽松归一化"后的结果——例如,字符串类型的属性会把传入值规整为符合该类型的 JavaScript 值。
注意:如果归一化不可行(即校验失败),
.validate()会抛出一个同步异常。文档特别强调:在异步回调内部,你必须手动处理任何被抛出的错误,否则可能导致未捕获异常(uncaught exception)。
3.1 关于"宽松归一化"的底层依据
"宽松校验 + 归一化"这一行为与 Waterline 的整体设计一致。在 docs/concepts/ORM/Validations.md 中明确写道:Waterline 及其适配器会对 criteria 字典以及传给.create()/.update()的值执行"宽松校验(loose validation)",以确保其符合预期的数据类型。.validate()复用的正是这套逻辑层机制。
仓库根目录的 package.json 显示当前项目为 Sails 1.5.18,其依赖中包含sails-hook-orm(devDependencies 中的"sails-hook-orm": "^4.0.2"),ORM 的实际实现由 Waterline 生态提供,Sails 侧的模型方法文档统一收纳在 docs/reference/waterline/models/ 目录下。
四、错误协商:像.update()一样处理使用错误
由于.validate()与.update()共享同一套校验逻辑,因此它可能抛出你在调用.update()时见到的任何使用错误(usage errors)。典型场景如下:
try { var normalizedBalance = BankAccount.validate('balance', '$349.86'); } catch (err) { switch (err.code) { case 'E_VALIDATION': // => '[Error: Invalid `bankAccount`]' _.each(err.all, function(woe){ sails.log(woe.attrName + ': ' + woe.message); }); break; default: throw err; } }关键信息解读:
err.code === 'E_VALIDATION':表示校验规则被违反(在 Sails 中通常对应name: 'UsageError'这一类);err.all:包含所有违规明细的数组,每一项包含attrName(违规的属性名)与message(人类可读的错误描述);- 遍历
err.all即可向用户逐条呈现"哪个字段、为什么失败"。
4.1 错误分类体系
在 docs/concepts/ORM/errors.md 中,Sails/Waterline 将错误实例归一化为一致的属性:
| 属性 | 类型 | 说明 |
|---|---|---|
name | ((string)) | 错误的宽泛分类,例如'UsageError' |
message | ((string)) | 错误描述信息 |
stack | ((string)) | 堆栈信息 |
code | ((string?)) | 有时存在的更细分类,例如'E_UNIQUE' |
其中"使用错误(usage errors)"即name: 'UsageError',表示某个 Waterline 方法被错误使用,或以无效选项执行——例如试图创建一条违反模型高层校验规则的新记录。.validate()抛出的正是这类错误(code: 'E_VALIDATION')。而E_UNIQUE属于AdapterError大类,只能来自.create()、.update()、.addToCollection()、.replaceCollection(),永远不会由.validate()产生——这与本文第二部分".validate()无法检测唯一性"的结论完全一致。
提示:在异步代码中更推荐使用
.intercept()与.tolerate()这类查询装饰器来协商错误;但由于.validate()是同步方法,直接使用try...catch即可。
五、同步特性:无需await与回调
.validate()是同步方法,这意味着:
- 不需要
await; - 不需要 promise 链式调用;
- 不需要传统 Node 回调(
.exec())。
你可以把它当作一个普通的、立即返回结果的函数来用。这也解释了为什么它的返回值和错误都以"直接返回 / 直接抛出"的方式呈现,而不是像.create()、.update()那样返回可等待的 deferred 对象。
六、与.create()/.update()的关系
.validate()只是为方便而单独暴露的方法。你完全可以只调用.create()或.update(),而不必先调用.validate(),因为这两个模型方法会自动执行完全相同的检查。
那么为什么要单独提供它?文档给出了明确理由:在以下场景中,复用模型校验能让代码更 DRY、更易读:
- 调用第三方 API 之前校验不可信数据:例如在把用户数据发给 Mailgun、Stripe 等第三方服务之前,先用模型的校验规则"把关",避免把脏数据发出去;
- 分阶段校验以简化推理:在业务逻辑中先跑一遍特定校验,让后续代码的前提假设更清晰、更容易推理;
- 在无需落库的中间流程中复用规则:比如表单的多步校验,前几步只想验证、不想写库。
6.1 需要手动校验的场景
需要注意的是,并不是所有场景都适合用模型校验。在 docs/concepts/ORM/Validations.md 的"When to use validations"一节中明确提醒:
- 模型校验会在每一次
.create()/.update()时运行; - 如果某个校验只应在特定分支生效(例如"两个邮箱二选一必填"取决于用户通过邮箱还是 LinkedIn 注册),就不应把
required: true写在模型属性上,而应在控制器内联校验,或在 services / 模型类方法中自行检查; - 不要害怕为了可维护性而放弃内置校验,改为在控制器或 helper 中手工检查。
这恰好凸显了.validate()的价值:当你想"临时复用某条模型校验规则、但又不希望它永久生效于所有写入"时,它就是最干净的工具。
七、校验规则的完整视图
要真正用好.validate(),需要理解它背后实际运行的规则集。下面按 docs/concepts/ORM/Validations.md 整理规则全表(.validate()的E_VALIDATION错误正是这些规则被触发时产生的):
| 规则名 | 检查内容 | 用法示例 | 兼容属性类型 |
|---|---|---|---|
custom | 传入自定义函数作为第一个参数时返回true | custom: function(value){ … } | 任意 |
isAfter | 解析为日期后晚于配置的Date实例 | isAfter: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') | ((string)), ((number)) |
isBefore | 解析为日期后早于配置的Date实例 | isBefore: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') | ((string)), ((number)) |
isBoolean | 值为true或false | isBoolean: true | ((json)), ((ref)) |
isCreditCard | 值为信用卡号(注意 PCI 合规问题) | isCreditCard: true | ((string)) |
isEmail | 值看起来像邮箱地址 | isEmail: true | ((string)) |
isHexColor | 值为十六进制颜色字符串 | isHexColor: true | ((string)) |
isIn | 值在指定字符串数组中 | isIn: ['paid', 'delinquent'] | ((string)) |
isInteger | 值为整数 | isInteger: true | ((number)) |
isIP | 值为合法 IP 地址(v4 或 v6) | isIP: true | ((string)) |
isNotEmptyString | 值不是空字符串 | isNotEmptyString: true | ((json)), ((ref)) |
isNotIn | 值不在配置数组中 | isNotIn: ['profanity1', 'profanity2'] | ((string)) |
isNumber | 值为 JavaScript 数字 | isNumber: true | ((json)), ((ref)) |
isString | 值为字符串(typeof(value) === 'string') | isString: true | ((json)), ((ref)) |
isURL | 值看起来像 URL | isURL: true | ((string)) |
isUUID | 值看起来像 UUID(v3、v4 或 v5) | isUUID: true | ((string)) |
max | 数值小于等于配置值 | max: 10000 | ((number)) |
min | 数值大于等于配置值 | min: 0 | ((number)) |
maxLength | 字符串长度不超过配置值 | maxLength: 144 | ((string)) |
minLength | 字符串长度至少为配置值 | minLength: 8 | ((string)) |
regex | 字符串匹配配置的正则 | regex: /^[a-z0-9]$/i | ((string)) |
要点补充:
- 若某规则兼容 ((string))、((number)) 或 ((boolean)),则该规则同时也兼容 ((json)) 与 ((ref));
- 除
unique外,所有规则都在内存中运行,这正是.validate()可以完整复用它们的前提; - 大部分规则不额外限制空字符串
"",但isNotEmptyString、isBoolean、isNumber、max、min等属于例外; string、number、boolean类型默认不接受null,如需允许null需开启allowNull: true(该标志仅对上述类型有效,对json、ref、关联属性和主键无效);required: true意味着.create()时必须提供值,且创建/更新时不允许置为null或空字符串。
7.1 自定义校验规则
custom规则允许你定义任意复杂的校验逻辑。自定义函数接收待校验值作为第一个参数,返回true表示合法,false表示非法。这类规则同样会被.validate()复用:
// api/models/User.js module.exports = { attributes: { location: { type: 'json', custom: function(value) { return _.isObject(value) && _.isNumber(value.x) && _.isNumber(value.y) && value.x !== Infinity && value.x !== -Infinity && value.y !== Infinity && value.y !== -Infinity; } }, password: { type: 'string', custom: function(value) { // 必须为字符串、至少 6 位、包含至少一个字母和一个数字 return _.isString(value) && value.length >= 6 && value.match(/[a-z]/i) && value.match(/[0-9]/); } } } };7.2 内置数据类型(类型安全的前提)
属性必须始终声明一种内置数据类型,这是所有校验与归一化的大前提:
| 数据类型 | 用法 | 说明 |
|---|---|---|
| ((string)) | type: 'string' | 任意字符串 |
| ((number)) | type: 'number' | 任意数字 |
| ((boolean)) | type: 'boolean' | true或false |
| ((json)) | type: 'json' | 任意可 JSON 序列化的值(数字、布尔、字符串、数组、字典、null) |
| ((ref)) | type: 'ref' | 除undefined外的任意 JavaScript 值(仅在需要利用适配器特定行为时使用) |
例如,一个"可选邮箱"属性可以这样定义,使得.validate('workEmail', value)在值合法时返回归一化字符串:
workEmail: { type: 'string', isEmail: true, }这里workEmail可接受合法邮箱或空字符串"",但不能接受null(违反type: 'string'的类型安全限制)。若希望接受null,可改为type: 'json'并(视需要)追加isString: true。
八、注意事项与边界总结
最后,把.validate()的关键注意事项汇总如下(均来自 docs/reference/waterline/models/validate.md 及仓库内关联文档):
- 同步执行:不要使用
await、promise 链或 Node 回调;返回值直接得到,失败直接抛出。 - 便捷而非必需:
create()/update()内部自动执行同样检查,.validate()只是让你"先验一下"。 - 典型价值在 DRY:在与第三方 API(如 Mailgun、Stripe)交互前校验不可信数据,或让部分代码先完成校验以便于推理。
- 只查逻辑层,不查物理层:类型安全与高层校验规则可被检测;
unique之类的数据库约束无法被检测(这类问题只会由真正的写入操作抛出E_UNIQUE)。 - 错误形态与
.update()一致:以E_VALIDATION(UsageError)形式抛出,可通过err.all获取逐属性违规明细;在异步回调中必须手动try...catch。 - 与
req.validate()无关:仓库测试 test/hooks/request/initialize.test.js(第 39 行附近)验证的是请求 hook 暴露的req.validate()函数,且断言"调用它应当总是失败";而本文讨论的.validate()是模型方法,二者不可混淆。
九、进一步阅读
- 模型方法参考:
.update()——.validate()校验语义的对照对象 - 模型方法参考:
.create()—— 同样自动执行校验的写入方法 - 概念:Validations 校验规则详解 —— 规则全表、类型系统、
allowNull/required、自定义规则 - 概念:Errors 错误协商 ——
UsageError/AdapterError/E_UNIQUE的分类体系 - 概念:模型与 ORM —— 模型定义与模型方法总览
- 查询装饰器:
.intercept()与.tolerate()—— 异步场景下的错误协商利器
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考