Skip to main content
Version: v6 - stable

验证和约束

在本教程中,你将学习如何在 Sequelize 中为模型设置验证和约束。

¥In this tutorial you will learn how to setup validations and constraints for your models in Sequelize.

对于本教程,将假定以下设置:

¥For this tutorial, the following setup will be assumed:

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
})();

验证和约束之间的区别

¥Difference between Validations and Constraints

验证是在 Sequelize 级别以纯 JavaScript 执行的检查。如果你提供自定义验证器函数,它们可以任意复杂,或者可以是 Sequelize 提供的内置验证器之一。如果验证失败,则根本不会向数据库发送 SQL 查询。

¥Validations are checks performed in the Sequelize level, in pure JavaScript. They can be arbitrarily complex if you provide a custom validator function, or can be one of the built-in validators offered by Sequelize. If a validation fails, no SQL query will be sent to the database at all.

另一方面,约束是在 SQL 级别定义的规则。约束的最基本示例是唯一约束。如果约束检查失败,数据库将抛出一个错误,Sequelize 会将此错误转发给 JavaScript(在本例中,抛出 SequelizeUniqueConstraintError)。请注意,与验证的情况不同,在本例中执行了 SQL 查询。

¥On the other hand, constraints are rules defined at SQL level. The most basic example of constraint is an Unique Constraint. If a constraint check fails, an error will be thrown by the database and Sequelize will forward this error to JavaScript (in this example, throwing a SequelizeUniqueConstraintError). Note that in this case, the SQL query was performed, unlike the case for validations.

唯一约束

¥Unique Constraint

上面的代码示例定义了 username 字段的唯一约束:

¥Our code example above defines a unique constraint on the username field:

/* ... */ {
username: {
type: DataTypes.TEXT,
allowNull: false,
unique: true
},
} /* ... */

当此模型同步时(例如通过调用 sequelize.sync),username 字段将在表中创建为 username TEXT UNIQUE,并且尝试插入已存在的用户名将抛出 SequelizeUniqueConstraintError

¥When this model is synchronized (by calling sequelize.sync for example), the username field will be created in the table as `username` TEXT UNIQUE, and an attempt to insert an username that already exists there will throw a SequelizeUniqueConstraintError.

允许/禁止空值

¥Allowing/disallowing null values

默认情况下,null 是模型每一列的允许值。可以通过为列设置 allowNull: false 选项来禁用此功能,就像我们的代码示例中的 username 字段中所做的那样:

¥By default, null is an allowed value for every column of a model. This can be disabled setting the allowNull: false option for a column, as it was done in the username field from our code example:

/* ... */ {
username: {
type: DataTypes.TEXT,
allowNull: false,
unique: true
},
} /* ... */

如果没有 allowNull: false,则调用 User.create({}) 也可以。

¥Without allowNull: false, the call User.create({}) would work.

关于 allowNull 实现的注意事项

¥Note about allowNull implementation

allowNull 检查是 Sequelize 中唯一的检查,它是本教程开头描述的意义上的验证和约束的混合。这是因为:

¥The allowNull check is the only check in Sequelize that is a mix of a validation and a constraint in the senses described at the beginning of this tutorial. This is because:

  • 如果尝试将 null 设置为不允许为空的字段,则会抛出 ValidationError,而不执行任何 SQL 查询。

    ¥If an attempt is made to set null to a field that does not allow null, a ValidationError will be thrown without any SQL query being performed.

  • 另外,在 sequelize.sync 之后,具有 allowNull: false 的列将被定义有 NOT NULL SQL 约束。这样,尝试将该值设置为 null 的直接 SQL 查询也会失败。

    ¥In addition, after sequelize.sync, the column that has allowNull: false will be defined with a NOT NULL SQL constraint. This way, direct SQL queries that attempt to set the value to null will also fail.

验证器

¥Validators

模型验证器允许你为模型的每个属性指定格式/内容/继承验证。验证会在 createupdatesave 上自动运行。你也可以调用 validate() 手动验证实例。

¥Model validators allow you to specify format/content/inheritance validations for each attribute of the model. Validations are automatically run on create, update and save. You can also call validate() to manually validate an instance.

每个属性验证

¥Per-attribute validations

你可以定义自定义验证器或使用由 validator.js (10.11.0) 实现的多个内置验证器,如下所示。

¥You can define your custom validators or use several built-in validators, implemented by validator.js (10.11.0), as shown below.

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 (foo@bar.com)
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 的可接受字符串数组,则这将被解释为多个字符串参数,而不是一个数组参数。要解决此问题,请传递单长度参数数组,例如上面所示的 [['foo', 'bar']]

¥Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for isIn, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as [['foo', 'bar']] as shown above.

要使用自定义错误消息而不是 validator.js 提供的错误消息,请使用对象而不是纯值或参数数组,例如,可以为不需要参数的验证器提供自定义消息

¥To use a custom error message instead of that provided by validator.js, use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with

isInt: {
msg: 'Must be an integer number of pennies';
}

或者如果还需要传递参数,请添加 args 属性:

¥or if arguments need to also be passed add an args property:

isIn: {
args: [['en', 'zh']],
msg: "Must be English or Chinese"
}

当使用自定义验证器函数时,错误消息将是抛出的 Error 对象包含的任何消息。

¥When using custom validator functions the error message will be whatever message the thrown Error object holds.

有关内置验证方法的更多详细信息,请参阅 validator.js 项目

¥See the validator.js project for more details on the built in validation methods.

提示:你还可以为日志记录部分定义自定义函数。只需传递一个函数即可。第一个参数是记录的字符串。

¥Hint: You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged.

allowNull 与其他验证者的交互

¥allowNull interaction with other validators

如果模型的特定字段设置为不允许 null(使用 allowNull: false)并且该值已设置为 null,则将跳过所有验证器并抛出 ValidationError

¥If a particular field of a model is set to not allow null (with allowNull: false) and that value has been set to null, all validators will be skipped and a ValidationError will be thrown.

另一方面,如果将其设置为允许 null(使用 allowNull: true)并且该值已设置为 null,则仅会跳过内置验证器,而自定义验证器仍将运行。

¥On the other hand, if it is set to allow null (with allowNull: true) and that value has been set to null, only the built-in validators will be skipped, while the custom validators will still run.

这意味着你可以拥有一个字符串字段,该字段验证其长度在 5 到 10 个字符之间,但也允许 null(因为当值为 null 时将自动跳过长度验证器):

¥This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows null (since the length validator will be skipped automatically when the value is null):

class User extends Model {}
User.init(
{
username: {
type: DataTypes.STRING,
allowNull: true,
validate: {
len: [5, 10],
},
},
},
{ sequelize },
);

你还可以使用自定义验证器有条件地允许 null 值,因为它不会被跳过:

¥You also can conditionally allow null values, with a custom validator, since it won't be skipped:

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 错误消息:

¥You can customize allowNull error message by setting the notNull validator:

class User extends Model {}
User.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: 'Please enter your name',
},
},
},
},
{ sequelize },
);

模型范围的验证

¥Model-wide validations

还可以定义验证以在特定于字段的验证器之后检查模型。例如,使用此功能,你可以确保 latitudelongitude 均未设置或两者均未设置,如果设置了其中一个但未设置另一个,则失败。

¥Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of latitude and longitude are set or both, and fail if one but not the other is set.

模型验证器方法是使用模型对象的上下文调用的,如果抛出错误,则被视为失败,否则通过。这与自定义特定于字段的验证器相同。

¥Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators.

收集到的任何错误消息都会与字段验证错误一起放入验证结果对象中,并且键以 validate 选项对象中失败的验证方法的键命名。尽管每种模型验证方法在任何时候只能有一条错误消息,但它会以数组中的单个字符串错误的形式展示,以最大限度地与字段错误保持一致。

¥Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the validate option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors.

一个例子:

¥An example:

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() 可能会返回:

¥In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, somePlace.validate() might return:

{
'latitude': ['Invalid number: latitude'],
'bothCoordsOrNone': ['Either both latitude and longitude, or neither!']
}

此类验证也可以使用在单个属性上定义的自定义验证器(例如通过检查 (value === null) !== (this.longitude === null)latitude 属性)来完成,但模型范围的验证方法更清晰。

¥Such validation could have also been done with a custom validator defined on a single attribute (such as the latitude attribute, by checking (value === null) !== (this.longitude === null)), but the model-wide validation approach is cleaner.