GO学习笔记 - 数据校验

GO学习笔记 - 数据校验

一. asaskevich/govalidator介绍

godoc里可以搜到若干相似的第三方数据校验模块,但笔者推荐使用asaskevich/govalidator,原因:

  • star最多、持续更新发布
  • 功能完善、使用便利
  • 丰富的字符串校验、数据匹配、裁剪拼接处理等
  • 支持struct元素合法性校验,并且支持嵌套检查
  • 源码值得学习,就是一个百宝箱
// 下载
go get github.com/asaskevich/govalidator

注意:查看使用方法到github,查看支持的函数列表到godoc

https://github.com/asaskevich/govalidator
https://godoc.org/github.com/asaskevich/govalidator

二. 字符串匹配

govalidator支持非常多种字符串匹配,先贴上一个简单例子

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

func main() {
    // 判断字符串值是否为合法的IPv4地址
    ip4 := "192.168.1.1"
    fmt.Println(govalidator.IsIPv4(ip4)) // true

    // 判断字符串值是否为合法的MAC
    mac := "aa:bb:cc:dd:ee:ffffff"
    fmt.Println(govalidator.IsMAC(mac)) // false

    // 判断数字是否在指定范围内
    dig := 101    // string类型也可以用
    fmt.Println(govalidator.InRange(dig, 0, 100)) // false
}

输出

true
false
false

三. struct元素匹配

govalidator专门提供了一个函数,用于校验struct的元素

govalidator.ValidateStruct()

简单例子

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

type foo struct {
    A string `valid:"ipv4"`
    B string `valid:"mac"`
    C string `valid:"range(0|100)"`    // 也可以使用int类型
}

func main() {
    f := foo{
        A: "192.168.1.1",
        B: "aa:bb:cc:dd:ee:ffffff",
        C: "101",
    }

    result, err := govalidator.ValidateStruct(f)
    if err != nil {
        fmt.Println("error: " + err.Error())
    }
    fmt.Println(result)
}

输出

error: B: aa:bb:cc:dd:ee:ffffff does not validate as mac;C: 101 does not validate as range(0|100)
false

注意:

▪ struct元素只支持部分常用的校验,详见本文附录2
▪ struct元素必须是导出型,也就是必须大写字母开头,govalidator才会去理会
▪ struct元素匹配较为智能,比如range(min|max)不仅支持string也支持int类型

四. struct元素可选验证

govalidator有一个bool类型的全局变量,可通过函数govalidator.SetFieldsRequiredByDefault()进行设置:

  • 当设置为true时,如果没有定义valid tag,则会提示错误
  • 当设置为false时,如果没有定义valid tag,不会提示错误。默认值就是false

另外,valid tag里,可以通过显式设置方式更细颗粒度地控制:当遇到zero value时是需要验证还是提示错误。此设置可以覆盖SetFieldsRequiredByDefault()。所以,valid tag有如下几种写法

`valid:""` // 等同于空tag,即``
`valid:"-"`
`valid:","`
`valid:",optional`
`valid:",required`

接下来,分别测试:假设一个struct元素的值为空字符""(即zero value)
govalidator.SetFieldsRequiredByDefault(true)

`valid:""`    // 报错:All fields are required to at least have one validation defined
`valid:"-"`    // true
`valid:","`    // 报错:Missing required field
`valid:",optional`    // true
`valid:",required`    // 报错:non zero value required
`valid:"ipv4"`    // 报错:Missing required field
`valid:"ipv4,optional"`    // true
`valid:"ipv4,required"`    // 报错:non zero value required
  • govalidator.SetFieldsRequiredByDefault(false)

`valid:""`    // true
`valid:"-"`    // true
`valid:","`    // true
`valid:",optional`    // true
`valid:",required`    // non zero value required
`valid:"ipv4"`    // true
`valid:"ipv4,optional"`    // true
`valid:"ipv4,required"`    // 报错:non zero value required

继续测试,当struct元素的值为不合法的ipv4地址字符串(非空字符串),如"192.168.1.1.1"
govalidator.SetFieldsRequiredByDefault(true)

`valid:""`    // 报错:All fields are required to at least have one validation defined
`valid:"-"`    // true
`valid:","`    // true
`valid:",optional`    // true
`valid:",required`    // true
`valid:"ipv4"`    // 报错:192.168.1.1.1 does not validate as ipv4
`valid:"ipv4,optional"`    // 报错:192.168.1.1.1 does not validate as ipv4
`valid:"ipv4,required"`    // 报错:192.168.1.1.1 does not validate as ipv4
  • govalidator.SetFieldsRequiredByDefault(false):测试效果和上述完全相同
// 来自github
SetNilPtrAllowedByRequired causes validation to pass when struct fields marked by required are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between nil and zero value state can use this. If disabled, both nil and zero values cause validation errors.

// 来自godoc
SetNilPtrAllowedByRequired causes validation to pass for nil ptrs when a field is set to required. The validation will still reject ptr fields in their zero value state. Example with this enabled:

type exampleStruct struct {
    Name *string `valid:"required"

With `Name` set to "", this will be considered invalid input and will cause a validation error. With `Name` set to nil, this will be considered valid by validation. By default this is disabled.

五. struct嵌套校验

嵌套元素名必须是导出型,也就是大写字母开头,举例

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

type Foo struct {
    A string `valid:"ipv4"`
    B string `valid:"mac"`
    C int `valid:"range(0|100)"`
}

type bar struct {
    X string `valid:"ipv4"`
    Foo `valid:",required"`
}

func main() {
    govalidator.SetFieldsRequiredByDefault(true)

    b := bar{
        X: "192.168.1.1",
    }

    b.Foo.A = "192.168.1.1.1"
    b.Foo.B = "aa:bb:cc:dd:ee:ff"
    b.Foo.C = 100

    result, err := govalidator.ValidateStruct(b)
    if err != nil {
        fmt.Println("error: " + err.Error())
    }
    fmt.Println(result)
}

输出

error: Foo.A: 192.168.1.1.1 does not validate as ipv4;A: 192.168.1.1.1 does not validate as ipv4
false

注意:可以给Foo设置一个元素名,但也必须是大写字母开头,比如

MyFoo Foo `valid:",required"`    // 正确,可以读取到
myFoo Foo `valid:",required"`    // 错误,无法读取到

六. 无法实现嵌套的可选校验

无法实现以嵌套为颗粒度的可选校验,比如下面这样是没有效果的

type bar struct {
    X string `valid:"ipv4"`
    Foo `valid:",optional"`    // 不可行
}

因为上面代码实际会被转换为这样

type bar struct {
    X string `valid:"ipv4"`
    Foo.A string `valid:"ipv4"`
    Foo.B string `valid:"mac"`
    Foo.C int `valid:"range(0|100)"`
}

这就导致没有办法实现Foo全校验或者全不校验

七. 个人最佳实践

建议全部显式配置校验,因为使用隐式一旦配置有误,难以及时发现

  • govalidator.SetFieldsRequiredByDefault(true)
  • valid tag写法:带上required,例如:
想做验证使用`valid:ipv4,required`
不想做验证使用`valid:",required"`

八. 其他功能

govalidator的校验功能还支持自定义tag与自定义校验函数,由于笔者尚未深度实践过,因此请参考官方github文档。

govalidator除了支持校验,还支持较为丰富的字符串裁剪、处理、正则等功能,以及若干类型转换功能,详见本文附录4、5(本文相比godoc和官网文档进行了更为细致的分类)。但笔者不推荐直接使用这些裁剪、处理、正则功能,因为实际上就是做了一层封装和一些细节处理,并不复杂,但可以学习。

笔者认为在使用govalidator的任何功能时,先看看源码,这是一个大而全的源码宝库,非常值得学习和借鉴。

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值