Sails 包支持自动验证模型属性。无论何时更新记录或创建新记录,每个属性的数据都会根据所有预定义的验证规则进行检查。这提供了一个方便的安全措施,以确保无效的条目不会进入应用程序的数据库。
除了unique
(作为数据库级别的约束实现;参见“唯一”),以下所有验证都在 JavaScript 中实现,并在与 Sails 相同的 Node.js 服务器进程中运行。还要记住,无论使用什么验证,属性都必须始终指定一个内置数据类型(string
、number
、json
等)。
// User
module.exports = {
attributes: {
emailAddress: {
type: 'string',
unique: true,
required: true
}
}
};
在 Sails/Waterline 中,模型属性始终具有一定的数据类型保证。这超出了底层数据库中可能存在的任何物理层约束——它更多地是为开发人员提供一种方法,让他们能够对进入或退出特定模型的数据做出合理的假设。
此数据类型保证用于结果和条件的逻辑验证和强制转换。以下是 Sails 和 Waterline 支持的数据类型列表
数据类型 | 用法 | 描述 |
---|---|---|
type: 'string' |
任何字符串。 | |
type: 'number' |
任何数字。 | |
type: 'boolean' |
true 或 false 。 |
|
type: 'json' |
任何可 JSON 序列化的值,包括数字、布尔值、字符串、数组、字典(纯 JavaScript 对象)和null 。 |
|
type: 'ref' |
除了undefined 之外的任何 JavaScript 值。(仅在利用适配器特定行为时使用)。 |
Sails 的 ORM(Waterline)及其适配器执行宽松的验证,以确保在条件字典中以及作为.create()
或 .update()
的值提供的的值与预期的数据类型匹配。
注意:在不支持JSON.stringify()
,然后存储在类型设置为text
的列中。每次返回记录时,数据都会调用JSON.parse()
。在考虑性能以及与其他应用程序或数据库中现有数据的兼容性时,需要注意这一点。官方的 PostgreSQL 和 mongoDB 适配器可以读取和写入
string
、number
和 boolean
数据类型在创建或更新记录时不接受null
作为值。为了允许设置null
值,请在属性上切换allowNull
标志。allowNull
标志仅对上述数据类型有效;它对类型为json
或 ref
的属性、任何关联或任何主键属性无效。
由于空字符串("") 是字符串,因此它通常受type: 'string'
属性支持;但有一些例外:主键(因为主键永远不支持空字符串)和任何具有required: true
的属性。
如果属性为required: true
,则在调用.create()
时必须始终为其指定值。这还会防止在创建或更新时将值设置为null
或空字符串("")。
以下没有任何验证规则对null
强加任何其他限制。也就是说,如果通常允许null
,则启用isEmail
验证规则不会导致null
被拒绝为无效。
类似地,以下大多数验证规则对空字符串("") 也不强加任何其他限制。有一些例外情况(isNotEmptyString
和非字符串相关的规则,如isBoolean
、isNumber
、max
和 min
),但在其他情况下,对于任何通常允许空字符串("") 的属性,添加验证规则都不会导致它被拒绝。
在下表中,“兼容属性类型”列显示每个验证规则适用的数据类型(即属性定义的type
属性)。在许多情况下,验证规则可以与多个类型一起使用。请注意,下表使用了捷径:如果与
规则名称 | 检查内容 | 用法说明 | 兼容属性类型 |
---|---|---|---|
custom | 一个值,当将其作为第一个参数提供给自定义函数时,该函数返回true 。 |
示例 | 任何 |
isAfter | 一个值,当解析为日期时,指的是在配置的 JavaScript Date 实例之后的时刻。 |
isAfter: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') |
|
isBefore | 一个值,当解析为日期时,指的是在配置的 JavaScript Date 实例之前的时刻。 |
isBefore: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') |
|
isBoolean | 值为true 或 false |
isBoolean: true | |
isCreditCard | 一个信用卡号码的值。 | 除非您的应用程序符合 PCI 标准,否则请勿将信用卡号码存储在数据库中!如果您想允许用户存储信用卡信息,一个安全的替代方案是使用支付 API,例如Stripe。 | |
isEmail | 看起来像电子邮件地址的值。 | isEmail: true |
|
isHexColor | 十六进制颜色的字符串。 | isHexColor: true |
|
isIn | 指定允许字符串数组中的值。 | isIn: ['paid', 'delinquent'] |
|
isInteger | 一个整数(整数)的数字 | isInteger: true |
|
isIP | 一个有效的 IP 地址(v4 或 v6)的值 | isIP: true |
|
isNotEmptyString | 不是空字符串的值 | isNotEmptyString: true |
|
isNotIn | 不在配置数组中的值。 | isNotIn: ['profanity1', 'profanity2'] |
|
isNumber | 一个 JavaScript 数字的值 | isNumber: true |
|
isString | 一个字符串的值(即typeof(value) === 'string' ) |
isString: true |
|
isURL | 看起来像 URL 的值。 | isURL: true |
|
isUUID | 看起来像 UUID(v3、v4 或 v5)的值 | isUUID: true |
|
max | 小于或等于配置数字的数字。 | max: 10000 |
|
min | 大于或等于配置数字的数字。 | min: 0 |
|
maxLength | 字符数不超过配置数字的字符串。 | maxLength: 144 |
|
minLength | 至少包含配置字符数的字符串。 | minLength: 8 |
|
regex | 与配置正则表达式匹配的字符串。 | regex: /^[a-z0-9]$/i |
假设您定义了一个如下所示的属性
workEmail: {
type: 'string',
isEmail: true,
}
当您调用.create()
_或_ .update()
时,此值可以设置为任何有效的电子邮件地址(如"[email protected]") 或空字符串("")。但是,您不能将其设置为null
,因为这会违反type: 'string'
强加的类型安全限制。
要使此属性接受
null
(例如,如果您正在使用预先存在的数据库),请将其更改为type: 'json'
。通常您还需要添加isString: true
,但由于在本例中我们已经强制执行isEmail: true
,因此无需这样做。需要注意的一个更高级的功能是,根据您的数据库,您可以选择利用
columnType
来告知 Sails/Waterline 在自动迁移期间(如果相关)定义哪个列类型。
如果我们想表明一个属性支持某些数字,例如星级评定,我们可以这样做
starRating: {
type: 'number',
min: 1,
max: 5,
required: true,
}
如果我们想使星级评定可选,最简单的方法就是移除required: true
标志。如果省略,则星级评定将默认为零。
null
)但是,如果星级评定不能始终为数字呢?假设我们需要与一个遗留数据库集成,在该数据库中,星级评定可以是数字或特殊的 null 字面量。在这种情况下,我们希望将starRating
属性定义为同时支持某些数字和null
。
要实现此目的,只需使用allowNull
starRating: {
type: 'number',
allowNull: true,
min: 1,
max: 5,
}
Sails 和 Waterline 属性支持
allowNull
以方便起见,但另一个可行的解决方案是将starRating
从type: 'number'
更改为type: 'json'
。但是,请记住,json
类型允许其他数据,例如布尔值、数组等。如果我们想明确防止这些数据类型被starRating
支持,我们可以添加isNumber: true
验证规则starRating: { type: 'json', isNumber: true, min: 1, max: 5, }
unique
与上面列出的所有验证规则都不同。实际上,它根本不是验证规则:它是一个数据库级别的约束。稍后将详细介绍。
如果属性声明自身为unique: true
,则 Sails 会确保不允许两条记录具有相同的值。规范示例是User
模型上的emailAddress
属性
// api/models/User.js
module.exports = {
attributes: {
emailAddress: {
type: 'string',
unique: true,
required: true
}
}
};
unique
与其他验证不同?假设您的数据库中有 1,000,000 条用户记录。如果unique
像其他验证一样实现,则每次有新用户注册您的应用程序时,Sails 都需要搜索一百万条现有记录,以确保没有其他人已经在使用新用户提供的电子邮件地址。这将非常缓慢,以至于在我们完成所有这些记录的搜索之前,其他人可能已经注册了!
幸运的是,这种唯一性检查可能是任何数据库中最通用的功能。为了利用这一点,Sails 依赖于数据库适配器来实现对unique
的支持——具体来说,它在自动迁移期间向数据库中相关的字段/列/属性添加一个唯一性约束。也就是说,当您的应用设置为migrate:'alter'
时,Sails 会自动在底层数据库中生成包含内置唯一性约束的表/集合。一旦您切换到migrate:'safe'
,更新数据库约束就由您自己负责了。
当您开始使用生产数据库时,设置索引以提高数据库性能始终是一个好主意。设置索引的确切过程和最佳实践因数据库而异,并且超出了本文档的范围。也就是说,如果您以前从未做过,请不要担心:它比您想象的更容易。
就像与您的生产模式相关的其他所有内容一样,一旦您将应用程序设置为使用migrate: 'safe'
,Sails 就会完全由您自己负责数据库索引。
请注意,这意味着在执行手动迁移时,您应该确保与唯一性约束一起更新索引。
验证可以避免您编写数百行重复代码,但请记住,模型验证会在应用程序中的每次创建或更新时运行。在您的某个属性定义中使用验证规则之前,请确保您同意它在您的应用程序每次调用.create()
或.update()
来指定该属性的新值时都应用。如果不是这种情况,请编写代码在您的控制器中内联验证传入的值,或者在您的服务或模型类方法之一中调用自定义函数。
假设您的 Sails 应用程序允许用户通过以下两种方式之一注册帐户:(A)输入电子邮件地址和密码,然后确认该电子邮件地址;或(B)使用 LinkedIn 注册。您的User
模型可能有一个名为manuallyEnteredEmail
的属性和另一个名为linkedInEmail
的属性。虽然其中一个电子邮件地址属性是必需的,但哪个属性是必需的取决于用户注册的方式。在这种情况下,您的User
模型不能使用required: true
验证。为了确认已提供两个电子邮件中的一个,并且提供的电子邮件有效,您需要在代码中相关的.create()
和.update()
调用之前手动检查这些值。
if ( !_.isString( req.param('email') ) ) {
return res.badRequest();
}
更进一步,假设您的应用程序接受付款。在注册流程中,如果用户使用付费计划注册,则他们必须提供用于计费的电子邮件地址(billingEmail
),而如果用户使用免费帐户注册,则他们会跳过此步骤。在帐户设置页面,付费计划的用户会看到一个“计费电子邮件”表单字段,他们可以在其中自定义其计费电子邮件。另一方面,免费计划的用户会看到一个链接到“升级计划”页面的号召性用语。
虽然这些需求看起来很具体,但仍然存在一些未解决的问题
linkedInEmail
时,计费电子邮件会发生什么情况?linkedInEmail
,计费电子邮件会发生什么情况?根据对这些问题的回答,我们最终可能会保留billingEmail
上的required
验证,添加新属性(如hasBillingEmailBeenChangedManually
),甚至重新考虑是否使用unique
约束。
最后,这里有一些提示
.update()
和.create()
。不要害怕放弃内置验证支持,而是在您的控制器或帮助器函数中手动检查值。通常,这是最简洁、最易维护的方法。unique
。在开发过程中,当您的应用程序配置为使用migrate: 'alter'
时,您可以随意添加或删除unique
验证。但是,如果您使用的是migrate: safe
(例如,使用您的生产数据库),则需要更新数据库中的约束/索引,以及手动迁移数据。尽可能地,最好在您花费任何大量时间实现任何后端代码之前获得或完善您自己的应用程序用户界面的线框图。当然,这并不总是可能的,这就是蓝图 API的用途。使用以 UI 为中心或“前端优先”的理念构建的应用程序更易于维护,往往错误更少,并且由于用户体验的意识在其核心,因此通常具有更优雅的 API。
您可以通过在属性中指定custom
函数来定义自己的自定义验证规则。
// api/models/User.js
module.exports = {
// Values passed for creates or updates of the User model must obey the following rules:
attributes: {
firstName: {
// Note that a base type (in this case "string") still has to be defined, even though validation rules are in use.
type: 'string',
required: true,
minLength: 5,
maxLength: 15
},
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) {
// • be a string
// • be at least 6 characters long
// • contain at least one number
// • contain at least one letter
return _.isString(value) && value.length >= 6 && value.match(/[a-z]/i) && value.match(/[0-9]/);
}
}
}
}
自定义验证函数将要验证的传入值作为其第一个参数接收。如果有效,则应返回true
,否则返回false
。
开箱即用,Sails.js 不支持自定义验证消息。相反,您的代码应该查看(或“协商”).create()
或.update()
调用抛出的验证错误并采取适当的操作,无论是发送 JSON 响应中的特定错误代码还是在 HTML 错误页面中呈现相应的消息。