Gin(四):表單提交校驗和模型繫結

youngxhui發表於2019-07-17

文章首發於 ISLAND

上一個章節中已經開始逐漸搭建了一個 web 頁面,現在我們開始逐步完善頁面上的功能,首先要完成的是登入和註冊功能。

接受表單資料

註冊頁面的 HTML 元素不在詳細寫出,具體頁面程式碼可以直接參考Github 上程式碼。

頁面完成後佈局:

Gin(四):表單提交校驗和模型繫結

註冊頁面有三個輸入框,分別為 email ,passwordpassword again

完善後端 Gin 程式碼。我們在 initRouteruserGroup 中編寫新的介面。

		userRouter.POST("/register", handler.UserRegister)
複製程式碼

編寫完新的介面就要開始編寫 Handler

func UserRegister(context *gin.Context) {
	email := context.PostForm("email")
	password := context.DefaultPostForm("password", "Wa123456")
	passwordAgain := context.DefaultPostForm("password-again", "Wa123456")
	println("email", email, "password", password, "password again", passwordAgain)
}
複製程式碼

UserRegister 方法中採用新的方式來接受 Post 請求提交的表單引數,PostFormDefaultPostFormPostForm 直接接受引數,而 DefaultPostForm 可以設定一個預設值,如果前端沒有進行傳值,那麼我們可以設定預設值,如上面的程式碼,如果前端沒有將密碼傳輸過來我們可以設定一個預設密碼。

當我們執行並且輸入的時候,在控制檯上可以清楚的看到我們在表單上的輸入。

當我們專案功能完善的時候,就可以完善我們的單元測試。

此時的單元測試交之前有點複雜。

首先我們要構造一個結構,該結構是為了幫助我們將我們要提交的資訊存放到表單中,同時要指定請求頭資訊。

func TestUserPostForm(t *testing.T) {
	value := url.Values{}
	value.Add("email", "youngxhui@gmail.com")
	value.Add("password", "1234")
	value.Add("password-again", "1234")
	w := httptest.NewRecorder()
	req, _ := http.NewRequest(http.MethodPost, "/user/register", bytes.NewBufferString(value.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded; param=value")
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
}
複製程式碼

單元測試編寫完成後可以執行單元測試,發現控制檯答應了我們在測試中寫的資料。

模型繫結

上例中我們的表單僅僅傳輸了三個引數,如果後期專案出現了十多個引數,每次寫一遍都很花費時間,也很消耗經歷。下面就對該方法進行改善

Gin 中提供了 模型繫結,將我們的表單資料與我們的模型進行一樣繫結。GIn會將資料統一封裝到模型中,方便我們日後使用。

首先定義我們的模型,新建 model 資料夾,建立 userModel.go

package model

type UserModel struct {
	Email         string `form:"email"`
	Password      string `form:"password"`
	PasswordAgain string `form:"password-again"`
}
複製程式碼

通過 form:"email" 來對錶單中的 email 輸入資料進行繫結。然後需要修改一下 Handler 方法。

func UserRegister(context *gin.Context) {
   var user model.UserModel
   if err := context.ShouldBind(&user); err != nil {
   	println("err ->", err.Error())
   	return
   }
   println("email", user.Email, "password", user.Password, "password again", user.PasswordAgain)
}
複製程式碼

此時我們的模型繫結已經寫好,執行 TestUserPostForm 測試用例,測試用例可以完美的通過。說明我們的模型繫結方法是正確的。同時模型繫結還是從 jsonxmlyml 等格式資料的繫結,日後會有介紹和說明。當然也可以通過瀏覽器中的登錄檔單進行提交。

資料校驗

做後端開發的人都明白一個道理:永遠不要相信前端傳過來的資料。所有的資料在進過後端時,務必要進行資料的校驗。

在模型中可用 binding 來對資料進行校驗。Gin 對於資料校驗使用的是 validator.v8 庫,該庫提供多種校驗方法。通過 binding:"" 方式來進行對資料的校驗。

我們將 UserModel 進行修改,新增一些規則,郵箱驗證和密碼校驗,要求第二次重複密碼要和第一次密碼一致。更多的校驗規則可以看官方文件

type UserModel struct {
	Email         string `form:"email" binding:"email"`
	Password      string `form:"password"`
	PasswordAgain string `form:"password-again" binding:"eqfield=Password"`
}
複製程式碼

我們重新寫一個測試用例用來測試郵箱和密碼校驗是否有效。

func TestUserPostFormEmailErrorAndPasswordError(t *testing.T) {
	value := url.Values{}
	value.Add("email", "youngxhui")
	value.Add("password", "1234")
	value.Add("password-again", "qwer")
	w := httptest.NewRecorder()
	req, _ := http.NewRequest(http.MethodPost, "/user/register", bytes.NewBufferString(value.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded; param=value")
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code)
}
複製程式碼

執行測試,發現測試雖然通過了,但是會有兩行 error 資訊

err ->  Key: 'UserModel.Email' Error:Field validation for 'Email' failed on the 'email' tag
Key: 'UserModel.PasswordAgain' Error:Field validation for 'PasswordAgain' failed on the 'eqfield' tag
複製程式碼

該資訊說明了我們的 EmailPasswordAgain 資訊校驗沒有通過。

使用Log和重定向

測試通過是因為無論我們程式碼如何都會返回 200 狀態碼,這是不符合http 狀態碼的規範的,所以我們要對http狀態碼進行規範化。同時我們之前的程式碼中一直使用 Printf 來列印日誌資訊,也是不規範的,因為 Printf 列印的日誌資訊相對侷限,所以應該選用 Log 進行日誌列印。

func UserRegister(context *gin.Context) {
	var user model.UserModel
	if err := context.ShouldBind(&user); err != nil {
		log.Println("err ->", err.Error())
		context.String(http.StatusBadRequest, "輸入的資料不合法")
	} else {
		log.Println("email", user.Email, "password", user.Password, "password again", user.PasswordAgain)
		context.Redirect(http.StatusMovedPermanently, "/")
	}
}
複製程式碼

首先我們將原來只用 Println 列印的資料都改成了 log 去列印資料。

同時將原來的狀態碼都進行了更改,不同的狀態碼代表不同的請求響應結果。

最後在請求成功的時候我們對路由進行了重定向,將頁面轉跳到首頁。

同時我們也要將測試用例裡的返回狀態碼進行修改。

總結

本節將表單提交,模型繫結和資料校驗有了一個相對細緻的介紹,程式碼中也通過不同的測試用例來檢查程式碼是否正確。

本章節程式碼

Github

其他章節索引

Gin(一):Hello
Gin(二):路由Router
Gin(三):模板tmpl
Gin(四):表單提交校驗和模型繫結
Gin(五):連線MySQL
Gin(六):檔案的上傳
Gin(七):中介軟體的使用和定義 Gin(八):Cookie的使用

個人公眾號

最新文章都會在公眾號上進行分享,歡迎各位大佬關注

Gin(四):表單提交校驗和模型繫結

相關文章