<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          boss: 這小子還不會(huì)使用validator庫進(jìn)行數(shù)據(jù)校驗(yàn),開了~

          共 12808字,需瀏覽 26分鐘

           ·

          2021-03-07 22:23

          前言

          在公司做項(xiàng)目,在做API部分開發(fā)時(shí),需要對(duì)請(qǐng)求參數(shù)的校驗(yàn),防止用戶的惡意請(qǐng)求。例如日期格式,用戶年齡,性別等必須是正常的值,不能隨意設(shè)置。最開始在做這一部分的時(shí)候,我采用老方法,自己編寫參數(shù)檢驗(yàn)方法,統(tǒng)一進(jìn)行參數(shù)驗(yàn)證。后來在同事CR的時(shí)候,說GIN有更好的參數(shù)檢驗(yàn)方法,gin框架使用github.com/go-playground/validator進(jìn)行參數(shù)校驗(yàn),我們只需要在定義結(jié)構(gòu)體時(shí)使用bindingvalidatetag標(biāo)識(shí)相關(guān)校驗(yàn)規(guī)則,就可以進(jìn)行參數(shù)校驗(yàn)了,很方便。相信也有很多小伙伴不知道這個(gè)功能,今天就來介紹一下這部分。

          快速安裝

          使用之前,我們先要獲取validator這個(gè)庫。

          # 第一次安裝使用如下命令
          $ go get github.com/go-playground/validator/v10
          # 項(xiàng)目中引入包
          import "github.com/go-playground/validator/v10"

          簡單示例

          安裝還是很簡單的,下面我先來一個(gè)官方樣例,看看是怎么使用的,然后展開分析。

          package main

          import (
           "fmt"
           "net/http"

           "github.com/gin-gonic/gin"
          )

          type RegisterRequest struct {
           Username string `json:"username" binding:"required"`
           Nickname string `json:"nickname" binding:"required"`
           Email    string `json:"email" binding:"required,email"`
           Password string `json:"password" binding:"required"`
           Age      uint8  `json:"age" binding:"gte=1,lte=120"`
          }

          func main() {

           router := gin.Default()

           router.POST("register", Register)

           router.Run(":9999")
          }

          func Register(c *gin.Context) {
           var r RegisterRequest
           err := c.ShouldBindJSON(&r)
           if err != nil {
            fmt.Println("register failed")
            c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
            return
           }
           //驗(yàn)證 存儲(chǔ)操作省略.....
           fmt.Println("register success")
           c.JSON(http.StatusOK, "successful")
          }

          • 測試
          curl --location --request POST 'http://localhost:9999/register' \
          --header 'Content-Type: application/json' \
          --data-raw '{
              "username": "asong",
              "nickname": "golang夢(mèng)工廠",
              "email": "7418.com",
              "password": "123",
              "age": 140
          }'

          • 返回結(jié)果
          {
              "msg""Key: 'RegisterRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag\nKey: 'RegisterRequest.Age' Error:Field validation for 'Age' failed on the 'lte' tag"
          }

          看這個(gè)輸出結(jié)果,我們可以看到validator的檢驗(yàn)生效了,email字段不是一個(gè)合法郵箱,age字段超過了最大限制。我們只在結(jié)構(gòu)體中添加tag就解決了這個(gè)問題,是不是很方便,下面我們就來學(xué)習(xí)一下具體使用。

          validator庫

          gin框架是使用validator.v10這個(gè)庫來進(jìn)行參數(shù)驗(yàn)證的,所以我們先來看看這個(gè)庫的使用。

          先安裝這個(gè)庫:

          $ go get github.com/go-playground/validator/v10

          然后先寫一個(gè)簡單的示例:

          package main

          import (
           "fmt"

           "github.com/go-playground/validator/v10"
          )

          type User struct {
           Username string `validate:"min=6,max=10"`
           Age      uint8  `validate:"gte=1,lte=10"`
           Sex      string `validate:"oneof=female male"`
          }

          func main() {
           validate := validator.New()

           user1 := User{Username: "asong", Age: 11, Sex: "null"}
           err := validate.Struct(user1)
           if err != nil {
            fmt.Println(err)
           }

           user2 := User{Username: "asong111", Age: 8, Sex: "male"}
           err = validate.Struct(user2)
           if err != nil {
            fmt.Println(err)
           }

          }

          我們?cè)诮Y(jié)構(gòu)體定義validator標(biāo)簽的tag,使用validator.New()創(chuàng)建一個(gè)驗(yàn)證器,這個(gè)驗(yàn)證器可以指定選項(xiàng)、添加自定義約束,然后在調(diào)用他的Struct()方法來驗(yàn)證各種結(jié)構(gòu)對(duì)象的字段是否符合定義的約束。

          上面的例子,我們?cè)赨ser結(jié)構(gòu)體中,有三個(gè)字段:

          • Name:通過min和max來進(jìn)行約束,Name的字符串長度為[6,10]之間。
          • Age:通過gte和lte對(duì)年輕的范圍進(jìn)行約束,age的大小大于1,小于10。
          • Sex:通過oneof對(duì)值進(jìn)行約束,只能是所列舉的值,oneof列舉出性別為男士??和女士??(不是硬性規(guī)定奧,可能還有別的性別)。

          所以user1會(huì)進(jìn)行報(bào)錯(cuò),錯(cuò)誤信息如下:

          Key: 'User.Name' Error:Field validation for 'Name' failed on the 'min' tag
          Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
          Key: 'User.Sex' Error:Field validation for 'Sex' failed on the 'oneof' tag

          各個(gè)字段違反了什么約束,一眼我們便能從錯(cuò)誤信息中看出來??赐炅撕唵问纠?,下面我就來看一看都有哪些tag,我們都可以怎么使用。本文不介紹所有的tag,更多使用方法,請(qǐng)到官方文檔自行學(xué)習(xí)。

          字符串約束

          • excludesall:不包含參數(shù)中任意的 UNICODE 字符,例如excludesall=ab;

          • excludesrune:不包含參數(shù)表示的 rune 字符,excludesrune=asong;

          • startswith:以參數(shù)子串為前綴,例如startswith=hi;

          • endswith:以參數(shù)子串為后綴,例如endswith=bye。

          • contains=:包含參數(shù)子串,例如contains=email

          • containsany:包含參數(shù)中任意的 UNICODE 字符,例如containsany=ab;

          • containsrune:包含參數(shù)表示的 rune 字符,例如`containsrune=asong;

          • excludes:不包含參數(shù)子串,例如excludes=email;

          范圍約束

          范圍約束的字段類型分為三種:

          • 對(duì)于數(shù)值,我們則可以約束其值
          • 對(duì)于切片、數(shù)組和map,我們則可以約束其長度
          • 對(duì)于字符串,我們則可以約束其長度

          常用tag介紹:

          • ne:不等于參數(shù)值,例如ne=5;
          • gt:大于參數(shù)值,例如gt=5
          • gte:大于等于參數(shù)值,例如gte=50
          • lt:小于參數(shù)值,例如lt=50;
          • lte:小于等于參數(shù)值,例如lte=50;
          • oneof:只能是列舉出的值其中一個(gè),這些值必須是數(shù)值或字符串,以空格分隔,如果字符串中有空格,將字符串用單引號(hào)包圍,例如oneof=male female。
          • eq:等于參數(shù)值,注意與len不同。對(duì)于字符串,eq約束字符串本身的值,而len約束字符串長度。例如eq=10;
          • len:等于參數(shù)值,例如len=10;
          • max:小于等于參數(shù)值,例如max=10;
          • min:大于等于參數(shù)值,例如min=10

          Fields約束

          • eqfield:定義字段間的相等約束,用于約束同一結(jié)構(gòu)體中的字段。例如:eqfield=Password
          • eqcsfield:約束統(tǒng)一結(jié)構(gòu)體中字段等于另一個(gè)字段(相對(duì)),確認(rèn)密碼時(shí)可以使用,例如:eqfiel=ConfirmPassword
          • nefield:用來約束兩個(gè)字段是否相同,確認(rèn)兩種顏色是否一致時(shí)可以使用,例如:nefield=Color1
          • necsfield:約束兩個(gè)字段是否相同(相對(duì))

          常用約束

          • unique:指定唯一性約束,不同類型處理不同:

            • 對(duì)于map,unique約束沒有重復(fù)的值
            • 對(duì)于數(shù)組和切片,unique沒有重復(fù)的值
            • 對(duì)于元素類型為結(jié)構(gòu)體的碎片,unique約束結(jié)構(gòu)體對(duì)象的某個(gè)字段不重復(fù),使用unique=field指定字段名
          • email:使用email來限制字段必須是郵件形式,直接寫eamil即可,無需加任何指定。

          • omitempty:字段未設(shè)置,則忽略

          • -:跳過該字段,不檢驗(yàn);

          • |:使用多個(gè)約束,只需要滿足其中一個(gè),例如rgb|rgba;

          • required:字段必須設(shè)置,不能為默認(rèn)值;

          好啦,就介紹這些常用的約束,更多約束學(xué)習(xí)請(qǐng)到文檔自行學(xué)習(xí)吧,都有example供你學(xué)習(xí),很快的。

          gin中的參數(shù)校驗(yàn)

          學(xué)習(xí)了validator,我們也就知道了怎么在gin中使用參數(shù)校驗(yàn)了。這些約束是都沒有變的,在validator中,我們直接結(jié)構(gòu)體中將約束放到validate tag中,同樣道理,在gin中我們只需將約束放到bindingtag中就可以了。是不是很簡單。

          但是有些時(shí)候,并不是所有的參數(shù)校驗(yàn)都能滿足我們的需求,所以我們可以定義自己的約束。自定義約束支持自定義結(jié)構(gòu)體校驗(yàn)、自定義字段校驗(yàn)等。這里來介紹一下自定義結(jié)構(gòu)體校驗(yàn)。

          自定義結(jié)構(gòu)體校驗(yàn)

          當(dāng)涉及到一些復(fù)雜的校驗(yàn)規(guī)則,這些已有的校驗(yàn)規(guī)則就不能滿足我們的需求了。例如現(xiàn)在有一個(gè)需求,存在db的用戶信息中創(chuàng)建時(shí)間與更新時(shí)間都要大于某一時(shí)間,假設(shè)是從前端傳來的(當(dāng)然不可能,哈哈)。現(xiàn)在我們來寫一個(gè)簡單示例,學(xué)習(xí)一下怎么對(duì)這個(gè)參數(shù)進(jìn)行校驗(yàn)。

          package main

          import (
           "fmt"
           "net/http"
           "time"

           "github.com/gin-gonic/gin"
           "github.com/gin-gonic/gin/binding"
           "github.com/go-playground/validator/v10"
          )

          type Info struct {
           CreateTime time.Time `form:"create_time" binding:"required,timing" time_format:"2006-01-02"`
           UpdateTime time.Time `form:"update_time" binding:"required,timing" time_format:"2006-01-02"`
          }

          // 自定義驗(yàn)證規(guī)則斷言
          func timing(fl validator.FieldLevel) bool {
           if date, ok := fl.Field().Interface().(time.Time); ok {
            today := time.Now()
            if today.After(date) {
             return false
            }
           }
           return true
          }

          func main() {
           route := gin.Default()
           // 注冊(cè)驗(yàn)證
           if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
            err := v.RegisterValidation("timing", timing)
            if err != nil {
             fmt.Println("success")
            }
           }

           route.GET("/time", getTime)
           route.Run(":8080")
          }

          func getTime(c *gin.Context) {
           var b Info
           // 數(shù)據(jù)模型綁定查詢字符串驗(yàn)證
           if err := c.ShouldBindWith(&b, binding.Query); err == nil {
            c.JSON(http.StatusOK, gin.H{"message""time are valid!"})
           } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
           }
          }

          寫好了,下面我就來測試驗(yàn)證一下:

          $ curl "localhost:8080/time?create_time=2020-10-11&update_time=2020-10-11"
          # 結(jié)果
          {"message":"time are valid!"}%
          $ curl "localhost:8080/time?create_time=1997-10-11&update_time=1997-10-11"
          # 結(jié)果
          {"error":"Key: 'Info.CreateTime' Error:Field validation for 'CreateTime' failed on the 'timing' tag\nKey: 'Info.UpdateTime' Error:Field validation for 'UpdateTime' failed on the 'timing' tag"}%

          這里我們看到雖然參數(shù)驗(yàn)證成功了,但是這里返回的錯(cuò)誤顯示的也太全了,在項(xiàng)目開發(fā)中不可以給前端返回這么詳細(xì)的信息的,所以我們需要改造一下:

          func getTime(c *gin.Context) {
           var b Info
           // 數(shù)據(jù)模型綁定查詢字符串驗(yàn)證
           if err := c.ShouldBindWith(&b, binding.Query); err == nil {
            c.JSON(http.StatusOK, gin.H{"message""time are valid!"})
           } else {
            _, ok := err.(validator.ValidationErrors)
            if !ok {
             c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
             return
            }
            c.JSON(http.StatusOK, gin.H{"code"1000"msg""param is error"})
           }
          }

          這里在出現(xiàn)錯(cuò)誤時(shí)返回固定錯(cuò)誤即可。這里你也可以使用一個(gè)方法封裝一下,對(duì)錯(cuò)誤進(jìn)行處理在進(jìn)行返回,更多使用方法等你發(fā)覺喲。

          小彩蛋

          我們返回錯(cuò)誤時(shí)都是英文的,當(dāng)錯(cuò)誤很長的時(shí)候,對(duì)于我這種英語渣渣,就要借助翻譯軟件了。所以要是能返回的錯(cuò)誤直接是中文的就好了。validator庫本身是支持國際化的,借助相應(yīng)的語言包可以實(shí)現(xiàn)校驗(yàn)錯(cuò)誤提示信息的自動(dòng)翻譯。下面就寫一個(gè)代碼演示一下啦。

          package main

          import (
           "fmt"
           "log"
           "net/http"

           "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"
           chTranslations "github.com/go-playground/validator/v10/translations/zh"
          )

          var trans ut.Translator

          // loca 通常取決于 http 請(qǐng)求頭的 'Accept-Language'
          func transInit(local string) (err error) {
           if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
            zhT := zh.New() //chinese
            enT := en.New() //english
            uni := ut.New(enT, zhT, enT)

            var o bool
            trans, o = uni.GetTranslator(local)
            if !o {
             return fmt.Errorf("uni.GetTranslator(%s) failed", local)
            }
            //register translate
            // 注冊(cè)翻譯器
            switch local {
            case "en":
             err = enTranslations.RegisterDefaultTranslations(v, trans)
            case "zh":
             err = chTranslations.RegisterDefaultTranslations(v, trans)
            default:
             err = enTranslations.RegisterDefaultTranslations(v, trans)
            }
            return
           }
           return
          }

          type loginRequest struct {
           Username string `json:"username" binding:"required"`
           Password string `json:"password" binding:"required,max=16,min=6"`
          }

          func main() {
           if err := transInit("zh"); err != nil {
            fmt.Printf("init trans failed, err:%v\n", err)
            return
           }
           router := gin.Default()

           router.POST("/user/login", login)

           err := router.Run(":8888")
           if err != nil {
            log.Println("failed")
           }
          }

          func login(c *gin.Context) {
           var req loginRequest
           if err := c.ShouldBindJSON(&req); err != nil {
            // 獲取validator.ValidationErrors類型的errors
            errs, ok := err.(validator.ValidationErrors)
            if !ok {
             // 非validator.ValidationErrors類型錯(cuò)誤直接返回
             c.JSON(http.StatusOK, gin.H{
              "msg": err.Error(),
             })
             return
            }
            // validator.ValidationErrors類型錯(cuò)誤則進(jìn)行翻譯
            c.JSON(http.StatusOK, gin.H{
             "msg": errs.Translate(trans),
            })
            return
           }
           //login 操作省略
           c.JSON(http.StatusOK, gin.H{
            "code"0,
            "msg":  "success",
           })
          }

          我這里請(qǐng)求參數(shù)中限制密碼的長度,來驗(yàn)證一下吧。

          curl --location --request POST 'http://localhost:8888/user/login' \
          --header 'Content-Type: application/json' \
          --data-raw '{
              "username": "asong",
              "password": "11122222222222222222"
          }'
          # 返回
          {
              "msg": {
                  "loginRequest.Password": "Password長度不能超過16個(gè)字符"
              }
          }

          看,直接顯示中文了,是不是很棒,我們可以在測試的時(shí)候使用這個(gè),上線項(xiàng)目不建議使用呦?。。?/p>

          總結(jié)

          好啦,這一篇文章到這里結(jié)束啦。這一篇干貨還是滿滿的。學(xué)會(huì)這些知識(shí)點(diǎn),提高我們的開發(fā)效率,省去了一些沒必要寫的代碼。能用的輪子我們還是不要錯(cuò)過滴。



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色欲AV影院 | 视频站欧美日韩 | 大香蕉大香蕉最新视频在线75 | 操B视频在线观看 | 成人在线性爱视频 |