验证与约束
在本教程中,您将学习如何在 Sequelize 中为您的模型设置验证和约束。
对于本教程,将假设以下设置
const { Sequelize, Op, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('user', {
username: {
type: DataTypes.TEXT,
allowNull: false,
unique: true,
},
hashedPassword: {
type: DataTypes.STRING(64),
validate: {
is: /^[0-9a-f]{64}$/i,
},
},
});
(async () => {
await sequelize.sync({ force: true });
// Code here
})();
验证和约束之间的区别
验证是在 Sequelize 层级(纯 JavaScript 中)执行的检查。如果您提供自定义验证器函数,它们可以任意复杂,或者可以是 Sequelize 提供的内置验证器之一。如果验证失败,则根本不会向数据库发送任何 SQL 查询。
另一方面,约束是在 SQL 层级定义的规则。约束的最基本示例是唯一约束。如果约束检查失败,数据库将抛出错误,Sequelize 将此错误转发到 JavaScript(在本例中,抛出 SequelizeUniqueConstraintError
)。请注意,在这种情况下,SQL 查询已执行,这与验证情况不同。
唯一约束
我们上面的代码示例在 username
字段上定义了一个唯一约束
/* ... */ {
username: {
type: DataTypes.TEXT,
allowNull: false,
unique: true
},
} /* ... */
当此模型同步时(例如,通过调用 sequelize.sync
),username
字段将在表中创建为 `username` TEXT UNIQUE
,并且尝试插入已存在的用户名将抛出 SequelizeUniqueConstraintError
。
允许/不允许空值
默认情况下,null
是模型每个列的允许值。可以通过为列设置 allowNull: false
选项来禁用此功能,就像在我们的代码示例中的 username
字段中所做的那样
/* ... */ {
username: {
type: DataTypes.TEXT,
allowNull: false,
unique: true
},
} /* ... */
如果没有 allowNull: false
,则调用 User.create({})
将会生效。
关于 allowNull
实现的说明
allowNull
检查是 Sequelize 中唯一一个在本文开头所述的意义上同时兼具验证和约束功能的检查。这是因为
- 如果尝试将
null
设置为不允许为空的字段,则将抛出ValidationError
,而不会执行任何 SQL 查询。 - 此外,在
sequelize.sync
之后,具有allowNull: false
的列将使用NOT NULL
SQL 约束进行定义。这样,尝试将值设置为null
的直接 SQL 查询也将失败。
验证器
模型验证器允许您为模型的每个属性指定格式/内容/继承验证。验证会在 create
、update
和 save
上自动运行。您还可以调用 validate()
手动验证实例。
每个属性的验证
您可以定义自定义验证器或使用几个内置验证器,由 validator.js (10.11.0) 实现,如下所示。
sequelize.define('foo', {
bar: {
type: DataTypes.STRING,
validate: {
is: /^[a-z]+$/i, // matches this RegExp
is: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string
not: /^[a-z]+$/i, // does not match this RegExp
not: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string
isEmail: true, // checks for email format ([email protected])
isUrl: true, // checks for url format (https://foo.com)
isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format
isIPv4: true, // checks for IPv4 (129.89.23.1)
isIPv6: true, // checks for IPv6 format
isAlpha: true, // will only allow letters
isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail
isNumeric: true, // will only allow numbers
isInt: true, // checks for valid integers
isFloat: true, // checks for valid floating point numbers
isDecimal: true, // checks for any numbers
isLowercase: true, // checks for lowercase
isUppercase: true, // checks for uppercase
notNull: true, // won't allow null
isNull: true, // only allows null
notEmpty: true, // don't allow empty strings
equals: 'specific value', // only allow a specific value
contains: 'foo', // force specific substrings
notIn: [['foo', 'bar']], // check the value is not one of these
isIn: [['foo', 'bar']], // check the value is one of these
notContains: 'bar', // don't allow specific substrings
len: [2,10], // only allow values with length between 2 and 10
isUUID: 4, // only allow uuids
isDate: true, // only allow date strings
isAfter: "2011-11-05", // only allow date strings after a specific date
isBefore: "2011-11-05", // only allow date strings before a specific date
max: 23, // only allow values <= 23
min: 23, // only allow values >= 23
isCreditCard: true, // check for valid credit card numbers
// Examples of custom validators:
isEven(value) {
if (parseInt(value) % 2 !== 0) {
throw new Error('Only even values are allowed!');
}
}
isGreaterThanOtherField(value) {
if (parseInt(value) <= parseInt(this.otherField)) {
throw new Error('Bar must be greater than otherField.');
}
}
}
}
});
请注意,在需要向内置验证函数传递多个参数的地方,要传递的参数必须放在数组中。但是,如果要传递单个数组参数,例如 isIn
的可接受字符串数组,这将被解释为多个字符串参数而不是一个数组参数。要解决此问题,请传递一个长度为 1 的参数数组,例如 [['foo', 'bar']]
,如上所示。
要使用自定义错误消息而不是 validator.js 提供的错误消息,请使用对象而不是普通值或参数数组,例如,不需要参数的验证器可以使用以下自定义消息
isInt: {
msg: 'Must be an integer number of pennies';
}
或者如果需要传递参数,请添加一个 args
属性
isIn: {
args: [['en', 'zh']],
msg: "Must be English or Chinese"
}
使用自定义验证器函数时,错误消息将是抛出的 Error
对象包含的任何消息。
有关内置验证方法的更多详细信息,请参阅 validator.js 项目。
提示:您还可以为日志记录部分定义自定义函数。只需传递一个函数即可。第一个参数将是记录的字符串。
allowNull
与其他验证器的交互
如果模型的特定字段设置为不允许为空(使用 allowNull: false
)并且该值已设置为 null
,则所有验证器都将跳过,并且将抛出 ValidationError
。
另一方面,如果将其设置为允许为空(使用 allowNull: true
)并且该值已设置为 null
,则仅跳过内置验证器,而自定义验证器仍将运行。
这意味着,例如,您可以拥有一个字符串字段,该字段验证其长度在 5 到 10 个字符之间,但也允许 null
(因为当值为 null
时,长度验证器将自动跳过)
class User extends Model {}
User.init(
{
username: {
type: DataTypes.STRING,
allowNull: true,
validate: {
len: [5, 10],
},
},
},
{ sequelize },
);
您还可以使用自定义验证器有条件地允许 null
值,因为它不会被跳过
class User extends Model {}
User.init(
{
age: Sequelize.INTEGER,
name: {
type: DataTypes.STRING,
allowNull: true,
validate: {
customValidator(value) {
if (value === null && this.age !== 10) {
throw new Error("name can't be null unless age is 10");
}
},
},
},
},
{ sequelize },
);
您可以通过设置 notNull
验证器来自定义 allowNull
错误消息
class User extends Model {}
User.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Please enter your name',
},
},
},
},
{ sequelize },
);
模型范围验证
还可以定义验证以在特定于字段的验证器之后检查模型。使用此功能,您可以例如确保 latitude
和 longitude
都不设置或都设置,如果一个设置而另一个未设置则失败。
模型验证器方法使用模型对象的上下文调用,如果它们抛出错误则被视为失败,否则通过。这与自定义特定于字段的验证器相同。
收集到的任何错误消息都将与字段验证错误一起放入验证结果对象中,其键以 validate
选项对象中失败的验证方法的键命名。即使对于每个模型验证方法,一次只能有一个错误消息,但它以数组中的单个字符串错误的形式呈现,以最大程度地与字段错误保持一致。
一个例子
class Place extends Model {}
Place.init(
{
name: Sequelize.STRING,
address: Sequelize.STRING,
latitude: {
type: DataTypes.INTEGER,
validate: {
min: -90,
max: 90,
},
},
longitude: {
type: DataTypes.INTEGER,
validate: {
min: -180,
max: 180,
},
},
},
{
sequelize,
validate: {
bothCoordsOrNone() {
if ((this.latitude === null) !== (this.longitude === null)) {
throw new Error('Either both latitude and longitude, or neither!');
}
},
},
},
);
在这个简单的例子中,如果给出了纬度或经度,但两者都不给,则对象验证失败。如果我们尝试构建一个纬度超出范围且没有经度的对象,somePlace.validate()
可能会返回
{
'latitude': ['Invalid number: latitude'],
'bothCoordsOrNone': ['Either both latitude and longitude, or neither!']
}
此类验证也可以使用在单个属性(例如 latitude
属性)上定义的自定义验证器来完成,方法是检查 (value === null) !== (this.longitude === null)
,但模型范围验证方法更简洁。