在現代 Web 開發中,表單驗證和錯誤處理是至關重要的環節,尤其是在多語言環境下。
本文將透過一個實際的示例,演示如何使用 Go 語言的 Gin 框架結合 validator
包,實現高階的表單驗證功能,並且支援國際化(i18n)的錯誤資訊提示。
背景與需求
假設我們正在開發一個使用者註冊功能,需要對使用者提交的資訊進行嚴格的驗證。例如,使用者名稱不能為空、郵箱格式必須正確、密碼和確認密碼必須一致、使用者年齡應在合理範圍內(如 1 到 130 歲),並且日期欄位不能早於當前日期。除此之外,系統還需要根據使用者的語言偏好提供相應語言的錯誤提示資訊。
程式碼示例
我們將從以下幾個方面展開:
- 表單資料的結構定義
- 表單驗證器的初始化與自定義
- 多語言支援的實現
- 處理表單提交與錯誤返回
package main
import (
"fmt"
"net/http"
"reflect"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
// 定義一個全域性翻譯器
var trans ut.Translator
表單資料結構定義
首先,我們定義使用者提交的表單資料結構 SignUpParam
。這個結構體中包含了使用者註冊時所需的各個欄位,並透過結構標籤(tags)指定了驗證規則。
type SignUpParam struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
Date string `json:"date" binding:"required,datetime=2006-01-02,checkDate"`
}
Age
欄位必須在 1 到 130 歲之間。Name
欄位不能為空。Email
欄位必須是有效的電子郵件地址。Password
和RePassword
欄位必須一致。Date
欄位需要使用自定義校驗方法checkDate
,確保輸入日期晚於當前日期。
初始化與自定義表單驗證器
在 Gin 框架中,我們可以透過 binding.Validator.Engine()
獲取到內建的驗證器,並對其進行自定義。
在下面的程式碼中,我們完成了翻譯器的初始化,並註冊了自定義的標籤名稱和驗證方法。
func InitTrans(locale string) (err error) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 註冊獲取 JSON tag 的自定義方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// 註冊結構體級別的驗證函式
v.RegisterStructValidation(SignUpParamStructLevelValidation, SignUpParam{})
// 註冊自定義校驗方法
if err := v.RegisterValidation("checkDate", customFunc); err != nil {
return err
}
// 初始化多語言支援
zhT := zh.New()
enT := en.New()
uni := ut.New(enT, zhT, enT)
var ok bool
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}
// 註冊語言翻譯
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
if err != nil {
return err
}
// 註冊自定義翻譯
if err := v.RegisterTranslation(
"checkDate",
trans,
registerTranslator("checkDate", "{0}必須晚於當前日期"),
translate,
); err != nil {
return err
}
return
}
return
}
實現自定義校驗邏輯
在上面的程式碼中,我們自定義了兩個校驗函式:
- customFunc:用於校驗日期是否晚於當前日期。
- SignUpParamStructLevelValidation:用於校驗兩個密碼欄位是否一致。
func customFunc(fl validator.FieldLevel) bool {
date, err := time.Parse("2006-01-02", fl.Field().String())
if err != nil {
return false
}
return date.After(time.Now())
}
func SignUpParamStructLevelValidation(sl validator.StructLevel) {
su := sl.Current().Interface().(SignUpParam)
if su.Password != su.RePassword {
sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password")
}
}
處理多語言錯誤提示
為了確保錯誤資訊能夠根據使用者的語言偏好正確返回,我們註冊了一個自定義的翻譯函式 registerTranslator
,並在驗證失敗時使用該函式對錯誤資訊進行翻譯。
// registerTranslator 為自定義欄位新增翻譯功能
func registerTranslator(tag string, msg string) validator.RegisterTranslationsFunc {
return func(trans ut.Translator) error {
if err := trans.Add(tag, msg, false); err != nil {
return err
}
return nil
}
}
// translate 自定義欄位的翻譯方法
func translate(trans ut.Translator, fe validator.FieldError) string {
msg, err := trans.T(fe.Tag(), fe.Field())
if err != nil {
panic(fe.(error).Error())
}
return msg
}
主程式邏輯
最後,我們在 Gin 中處理使用者的註冊請求。當使用者提交的資料驗證失敗時,系統會自動返回翻譯後的錯誤提示資訊。
// removeTopStruct 去除欄位名中的結構體名稱標識
// refer from:https://github.com/go-playground/validator/issues/633#issuecomment-654382345
func removeTopStruct(fields map[string]string) map[string]string {
res := map[string]string{}
for field, err := range fields {
res[field[strings.Index(field, ".")+1:]] = err
}
return res
}
func main() {
// 初始化翻譯器
if err := InitTrans("zh"); err != nil {
fmt.Printf("初始化翻譯器失敗: %v\n", err)
return
}
r := gin.Default()
r.POST("/signup", func(c *gin.Context) {
var u SignUpParam
if err := c.ShouldBind(&u); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"msg": removeTopStruct(errs.Translate(trans))})
return
}
// 其他的一些業務邏輯操作……
c.JSON(http.StatusOK, gin.H{"msg": "success"})
})
err := r.Run(":8080")
if err != nil {
fmt.Printf("伺服器執行失敗: %v\n", err)
}
}
總結
本文透過一個完整的示例,展示瞭如何在 Go 語言中使用 Gin 框架實現多語言的表單驗證。
我們不僅探討了基礎的驗證規則,還介紹瞭如何自定義驗證邏輯以及如何實現國際化的錯誤提示。這種方式使得我們的應用程式不僅在功能上更加強大,同時也能更好地適應全球化的需求。