驗證 & 約束
在本教學中,您將學習如何在 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
的可接受字串陣列,則這將被解釋為多個字串引數,而不是一個陣列引數。要解決此問題,請傳遞單一長度的引數陣列,例如上面顯示的 [['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)
),但是模型範圍的驗證方法更簡潔。