(快速參考)

11 驗證

版本 6.2.0

11 驗證

Grails 的驗證功能建構於 Spring 的驗證器 API 和資料繫結功能上。然而,Grails 更進一步,並透過其約束機制提供統一的方式來定義驗證「約束」。

Grails 中的約束是一種宣告式指定驗證規則的方式。最常見的是套用於 網域類別,不過 URL 對應命令物件 也支援約束。

11.1 宣告約束

在網域類別中,約束 是透過指派程式碼區塊的約束屬性來定義的

class User {
    String login
    String password
    String email
    Integer age

    static constraints = {
      ...
    }
}

然後,您使用與約束套用的屬性名稱相符的方法呼叫,搭配命名參數來指定約束

class User {
    ...

    static constraints = {
        login size: 5..15, blank: false, unique: true
        password size: 5..15, blank: false
        email email: true, blank: false
        age min: 18
    }
}

在此範例中,我們宣告 login 屬性長度必須介於 5 到 15 個字元之間,不能為空白,且必須是唯一的。我們也對 passwordemailage 屬性套用其他約束。

預設情況下,所有網域類別屬性都不可為空 (即具有隱含的 nullable: false 約束)。

您可以在快速參考部分的約束標題下找到可用約束的完整參考。

請注意,約束只會評估一次,這可能與依賴於值(例如 java.util.Date 的實例)的約束相關。

class User {
    ...

    static constraints = {
        // this Date object is created when the constraints are evaluated, not
        // each time an instance of the User class is validated.
        birthDate max: new Date()
    }
}

警告 - 從約束中參照網域類別屬性

很容易嘗試從靜態約束區塊參照實例變數,但這在 Groovy(或 Java)中是不合法的。如果您這樣做,您將會為您的麻煩取得一個 MissingPropertyException。例如,您可能會嘗試

class Response {
    Survey survey
    Answer answer

    static constraints = {
        survey blank: false
        answer blank: false, inList: survey.answers
    }
}

看看 inList 約束如何參照實例屬性 survey?這不會奏效。相反,使用自訂 驗證器

class Response {
    ...
    static constraints = {
        survey blank: false
        answer blank: false, validator: { val, obj -> val in obj.survey.answers }
    }
}

在此範例中,自訂驗證器的 obj 參數是正在驗證的網域實例,因此我們可以存取它的 survey 屬性,並傳回一個布林值來表示 answer 屬性 val 的新值是否有效。

11.2 驗證約束

驗證基礎

呼叫 驗證 方法來驗證網域類別實例

def user = new User(params)

if (user.validate()) {
    // do something with user
}
else {
    user.errors.allErrors.each {
        println it
    }
}

網域類別上的 errors 屬性是 Spring Errors 介面的實例。Errors 介面提供方法來導覽驗證錯誤,並擷取原始值。

驗證階段

在 Grails 中有兩個驗證階段,第一個是 資料繫結,它發生在您將要求參數繫結到實例時,例如

def user = new User(params)

在這個時候,您可能已經在 errors 屬性中有錯誤,這是因為類型轉換(例如將字串轉換為日期)。您可以使用 Errors API 檢查這些錯誤並取得原始輸入值

if (user.hasErrors()) {
    if (user.errors.hasFieldErrors("login")) {
        println user.errors.getFieldError("login").rejectedValue
    }
}

驗證的第二個階段發生在您呼叫 驗證儲存 時。這是 Grails 將驗證繫結值與您定義的 約束 的時候。例如,預設上 儲存 方法在執行之前會呼叫 驗證,讓您可以撰寫像這樣的程式碼

if (user.save()) {
    return user
}
else {
    user.errors.allErrors.each {
        println it
    }
}

11.3 在類別之間共用約束

Grails 中的常見模式是使用 命令物件 來驗證使用者提交的資料,然後將命令物件的屬性複製到相關的網域類別。這通常表示您的命令物件和網域類別共用屬性及其約束。您可以手動複製和貼上這兩個約束,但那是一個非常容易出錯的方法。相反地,請使用 Grails 的全域約束和匯入機制。

全域約束

除了在網域類別、指令物件和 其他可驗證類別 中定義約束外,您還可以在 grails-app/conf/runtime.groovy 中定義約束

grails.gorm.default.constraints = {
    '*'(nullable: true, size: 1..20)
    myShared(nullable: false, blank: false)
}

這些約束不會附加到任何特定類別,但可以從任何可驗證類別輕鬆地參考它們

class User {
    ...

    static constraints = {
        login shared: "myShared"
    }
}

請注意 shared 參數的使用,其值是 grails.gorm.default.constraints 中定義的其中一個約束的名稱。儘管有設定名稱,您仍可以從任何可驗證類別(例如指令物件)參考這些共用約束。

「*」約束是一個特殊情況:它表示相關約束(在上述範例中為「nullable」和「size」)將套用至所有可驗證類別中的所有屬性。這些預設值可以由可驗證類別中宣告的約束覆寫。

匯入約束

Grails 2 導入了一種共用約束的替代方法,讓您可以從一個類別匯入一組約束到另一個類別。

假設您有一個網域類別如下所示

class User {
    String firstName
    String lastName
    String passwordHash

    static constraints = {
        firstName blank: false, nullable: false
        lastName blank: false, nullable: false
        passwordHash blank: false, nullable: false
    }
}

然後,您想要建立一個指令物件 UserCommand,它共用網域類別的部分屬性和對應的約束。您可以使用 importFrom() 方法執行此動作

class UserCommand {
    String firstName
    String lastName
    String password
    String confirmPassword

    static constraints = {
        importFrom User

        password blank: false, nullable: false
        confirmPassword blank: false, nullable: false
    }
}

這將從 User 網域類別匯入所有約束,並將它們套用至 UserCommand。匯入會忽略來源類別 (User) 中任何沒有對應屬性的約束(匯入類別 (UserCommand))。在上述範例中,只有「firstName」和「lastName」約束會匯入至 UserCommand,因為這兩個類別共用的屬性只有這兩個。

如果您想要進一步控制要匯入哪些約束,請使用 includeexclude 參數。這兩個參數都接受一個簡單或正規表示式字串清單,這些字串會與來源約束中的屬性名稱進行比對。因此,例如,如果您只想匯入「lastName」約束,您會使用

...
static constraints = {
    importFrom User, include: ["lastName"]
    ...
}

或者,如果您想要匯入所有以「Name」結尾的約束

...
static constraints = {
    importFrom User, include: [/.*Name/]
    ...
}

當然,exclude 會執行相反的動作,指定不應匯入哪些約束。

11.4 驗證客戶端

顯示錯誤

通常,如果您遇到驗證錯誤,您會重新導向回檢視進行呈現。一旦進入檢視,您需要一些方式來顯示錯誤。Grails 支援一組豐富的標籤來處理錯誤。若要將錯誤呈現為清單,您可以使用 renderErrors

<g:renderErrors bean="${user}" />

如果您需要進一步控制,您可以使用 hasErrorseachError

<g:hasErrors bean="${user}">
  <ul>
   <g:eachError var="err" bean="${user}">
       <li>${err}</li>
   </g:eachError>
  </ul>
</g:hasErrors>

重點顯示錯誤

當欄位輸入不正確時,使用紅色方塊或一些指標進行重點顯示通常很有用。這也可以透過 hasErrors 標籤來執行,方法是將它呼叫為方法。例如

<div class='value ${hasErrors(bean:user,field:'login','errors')}'>
   <input type="text" name="login" value="${fieldValue(bean:user,field:'login')}"/>
</div>

這段程式碼檢查 user bean 的 login 欄位是否有任何錯誤,如果有,它會將 errors CSS 類別新增至 div,讓您可以使用 CSS 規則來重點顯示 div

擷取輸入值

每個錯誤實際上都是 Spring 中 FieldError 類別的實例,它會保留原始輸入值。這很有用,因為您可以使用錯誤物件,使用 fieldValue 標籤還原使用者輸入的值

<input type="text" name="login" value="${fieldValue(bean:user,field:'login')}"/>

這段程式碼會檢查 `User` bean 中現有的 `FieldError`,如果有的話,取得 `login` 欄位的原始輸入值。

11.5 驗證與國際化

關於 Grails 中錯誤的另一個重要事項是,錯誤訊息並未在任何地方硬式編碼。Spring 中的 FieldError 類別會使用 Grails 的 i18n 支援,從訊息叢集中解析訊息。

約束與訊息代碼

代碼本身是由慣例決定的。例如,考慮我們先前看過的約束

package com.mycompany.myapp

class User {
    ...

    static constraints = {
        login size: 5..15, blank: false, unique: true
        password size: 5..15, blank: false
        email email: true, blank: false
        age min: 18
    }
}

如果違反約束,Grails 會根據慣例尋找訊息代碼

約束 錯誤代碼

空白

className.propertyName.blank

信用卡

className.propertyName.creditCard.invalid

電子郵件

className.propertyName.email.invalid

在清單中

className.propertyName.not.inList

符合

className.propertyName.matches.invalid

最大值

className.propertyName.max.exceeded

最大大小

className.propertyName.maxSize.exceeded

最小值

className.propertyName.min.notmet

最小大小

className.propertyName.minSize.notmet

不等於

className.propertyName.notEqual

可為空

className.propertyName.nullable

範圍

className.propertyName.range.toosmallclassName.propertyName.range.toobig

大小

className.propertyName.size.toosmallclassName.propertyName.size.toobig

唯一

className.propertyName.unique

網址

className.propertyName.url.invalid

驗證器

classname.propertyName. + Closure 傳回的字串

對於 空白約束,這會是 user.login.blank,因此您需要在 grails-app/i18n/messages.properties 檔案中輸入以下訊息

user.login.blank=Your login name must be specified!

類別名稱會同時尋找有和沒有套件的情況,套件版本優先。因此,例如,com.mycompany.myapp.User.login.blank 會優先於 user.login.blank 使用。這允許您的網域類別訊息代碼與外掛程式發生衝突的情況。

有關哪些代碼適用於哪些約束的參考,請參閱每個約束的參考指南(例如 空白)。

顯示訊息

renderErrors 標籤會使用 message 標籤自動為您查詢訊息。如果您需要更多控制權來進行呈現,您可以自行處理

<g:hasErrors bean="${user}">
  <ul>
   <g:eachError var="err" bean="${user}">
       <li><g:message error="${err}" /></li>
   </g:eachError>
  </ul>
</g:hasErrors>

在這個範例中,eachError 標籤的本體內,我們使用 message 標籤搭配其 error 參數,來讀取給定錯誤的訊息。

11.6 將驗證套用至其他類別

網域類別命令物件 預設支援驗證。其他類別可以透過在類別中定義靜態 constraints 屬性(如上所述)並告知框架,來使其可驗證。應用程式必須向框架註冊可驗證的類別非常重要。僅定義 constraints 屬性是不夠的。

可驗證特質

定義靜態 constraints 屬性並實作 Validateable 特質的類別將會是可驗證的。請考慮以下範例

src/main/groovy/com/mycompany/myapp/User.groovy
package com.mycompany.myapp

import grails.validation.Validateable

class User implements Validateable {
    ...

    static constraints = {
        login size: 5..15, blank: false, unique: true
        password size: 5..15, blank: false
        email email: true, blank: false
        age min: 18
    }
}
程式存取

存取可驗證物件上的約束略有不同。您可以在另一個內容中透過存取類別的 constraintsMap 靜態屬性,以程式方式存取命令物件的約束。該屬性是 Map<String, ConstrainedProperty> 的實例

在上述範例中,存取 User.constraintsMap.login.blank 會產生 false,而 User.constraintsMap.login.unique 會產生 true