本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/125

假设我们有如下结构体:

type User struct {
Id int
Name string
Bio string
Email string
}

我们需要对结构体内的字段进行验证合法性:

  • Id的值在某一个范围内。
  • Name的长度在某一个范围内。
  • Email格式正确。

我们可能会这么写:

user := User{
Id: 0,
Name: "superlongstring",
Bio: "",
Email: "foobar",
} if user.Id < 1 && user.Id > 1000 {
return false
}
if len(user.Name) < 2 && len(user.Name) > 10 {
return false
}
if !validateEmail(user.Email) {
return false
}

这样的话代码比较冗余,而且如果结构体新加字段,还需要再修改验证函数再加一段if判断。这样代码比较冗余。我们可以借助golang的structTag来解决上述的问题:

type User struct {
Id int `validate:"number,min=1,max=1000"`
Name string `validate:"string,min=2,max=10"`
Bio string `validate:"string"`
Email string `validate:"email"`
}

validate:"number,min=1,max=1000"就是structTag。如果对这个比较陌生的话,看看下面这个:


type User struct {
Id int `json:"id"`
Name string `json:"name"`
Bio string `json:"about,omitempty"`
Active bool `json:"active"`
Admin bool `json:"-"`
CreatedAt time.Time `json:"created_at"`
}

写过golang的基本都用过json:xxx这个用法,json:xxx其实也是一个structTag,只不过这是golang帮你实现好特定用法的structTag。而validate:"number,min=1,max=1000"是我们自定义的structTag。

实现思路

我们定义一个接口Validator,定义一个方法Validate。再定义有具体意义的验证器例如StringValidatorNumberValidatorEmailValidator来实现接口Validator

这里为什么要使用接口?假设我们不使用接口代码会怎么写?

if tagIsOfNumber(){
validator := NumberValidator{}
}else if tagIsOfString() {
validator := StringValidator{}
}else if tagIsOfEmail() {
validator := EmailValidator{}
}else if tagIsOfDefault() {
validator := DefaultValidator{}
}

这样的话判断逻辑不能写在一个函数中,因为返回值validator会因为structTag的不同而不同,而且validator也不能当做函数参数做传递。而我们定义一个接口,所有的validator都去实现这个接口,上述的问题就能解决,而且逻辑更加清晰和紧凑。

关于接口的使用可以看下标准库的io Writer,Writer是个interface,只有一个方法Writer:

type Writer interface {
Write(p []byte) (n int, err error)
}

而输出函数可以直接调用参数的Write方法即可,无需关心到底是写到文件还是写到标准输出:

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf) //调用Write方法
p.free()
return
} //调用
Fprintf(os.Stdout, format, a...) //标准输出
Fprintf(os.Stderr, msg+"\n", args...) //标准错误输出 var buf bytes.Buffer
Fprintf(&buf, "[") //写入到Buffer的缓存中

言归正传,我们看下完整代码,代码是Custom struct field tags in Golang中给出的:

package main

import (
"fmt"
"reflect"
"regexp"
"strings"
) const tagName = "validate" //邮箱验证正则
var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`) //验证接口
type Validator interface {
Validate(interface{}) (bool, error)
} type DefaultValidator struct {
} func (v DefaultValidator) Validate(val interface{}) (bool, error) {
return true, nil
} type StringValidator struct {
Min int
Max int
} func (v StringValidator) Validate(val interface{}) (bool, error) {
l := len(val.(string)) if l == 0 {
return false, fmt.Errorf("cannot be blank")
} if l < v.Min {
return false, fmt.Errorf("should be at least %v chars long", v.Min)
} if v.Max >= v.Min && l > v.Max {
return false, fmt.Errorf("should be less than %v chars long", v.Max)
} return true, nil
} type NumberValidator struct {
Min int
Max int
} func (v NumberValidator) Validate(val interface{}) (bool, error) {
num := val.(int) if num < v.Min {
return false, fmt.Errorf("should be greater than %v", v.Min)
} if v.Max >= v.Min && num > v.Max {
return false, fmt.Errorf("should be less than %v", v.Max)
} return true, nil
} type EmailValidator struct {
} func (v EmailValidator) Validate(val interface{}) (bool, error) {
if !mailRe.MatchString(val.(string)) {
return false, fmt.Errorf("is not a valid email address")
}
return true, nil
} func getValidatorFromTag(tag string) Validator {
args := strings.Split(tag, ",") switch args[0] {
case "number":
validator := NumberValidator{}
//将structTag中的min和max解析到结构体中
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "string":
validator := StringValidator{}
fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)
return validator
case "email":
return EmailValidator{}
} return DefaultValidator{}
} func validateStruct(s interface{}) []error {
errs := []error{} v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ {
//利用反射获取structTag
tag := v.Type().Field(i).Tag.Get(tagName) if tag == "" || tag == "-" {
continue
} validator := getValidatorFromTag(tag) valid, err := validator.Validate(v.Field(i).Interface())
if !valid && err != nil {
errs = append(errs, fmt.Errorf("%s %s", v.Type().Field(i).Name, err.Error()))
}
} return errs
} type User struct {
Id int `validate:"number,min=1,max=1000"`
Name string `validate:"string,min=2,max=10"`
Bio string `validate:"string"`
Email string `validate:"email"`
} func main() {
user := User{
Id: 0,
Name: "superlongstring",
Bio: "",
Email: "foobar",
} fmt.Println("Errors:")
for i, err := range validateStruct(user) {
fmt.Printf("\t%d. %s\n", i+1, err.Error())
}
}

代码很好理解,结构也很清晰,不做过多解释了_

github上其实已经有现成的验证包了govalidator,支持内置支持的验证tag和自定义验证tag:

package main

import (
"github.com/asaskevich/govalidator"
"fmt"
"strings"
) type Server struct {
ID string `valid:"uuid,required"`
Name string `valid:"machine_id"`
HostIP string `valid:"ip"`
MacAddress string `valid:"mac,required"`
WebAddress string `valid:"url"`
AdminEmail string `valid:"email"`
} func main() {
server := &Server{
ID: "123e4567-e89b-12d3-a456-426655440000",
Name: "IX01",
HostIP: "127.0.0.1",
MacAddress: "01:23:45:67:89:ab",
WebAddress: "www.example.com",
AdminEmail: "admin@exmaple.com",
} //自定义tag验证函数
govalidator.TagMap["machine_id"] = govalidator.Validator(func(str string) bool {
return strings.HasPrefix(str, "IX")
}) if ok, err := govalidator.ValidateStruct(server); err != nil {
panic(err)
} else {
fmt.Printf("OK: %v\n", ok)
}
}

参考资料:

golang 如何验证struct字段的数据格式的更多相关文章

  1. golang自定义struct字段标签

    原文链接: https://sosedoff.com/2016/07/16/golang-struct-tags.html struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从 ...

  2. golang常用库:字段参数验证库-validator

    背景 在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作.比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全 ...

  3. Golang面向对象编程-struct(结构体)

    Golang面向对象编程-struct(结构体) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是面向对象编程 面向对象编程(Object Oriented Program ...

  4. .Net高级进阶,WebApi和MVC进行模型验证的时候,教你如何自由控制需要进行验证的字段?

    现在,你有一个MVC架构的web项目,你要完成一个注册功能. 前台传了3个值到你的控制器,分别是账号.密码.邮箱. 如图:现在你要在控制器里面判断,账号名称.密码.邮箱不能为空,并且名称和密码不超过1 ...

  5. django 表单验证和字段验证

    表单验证和字段验证 表单验证发生在数据验证之后.如果你需要自定义这个过程,有几个不同的地方可以修改,每个地方的目的不一样.表单处理过程中要运行三种类别的验证方法.它们通常在你调用表单的is_valid ...

  6. golang sqlx查询时, struct字段冲突

    type TA struct { Id int64 `db:"id"` } type TB struct { Id int64 `db:"id"` } type ...

  7. GO开发[五]:golang结构体struct

    Go结构体struct Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性 ...

  8. MVC ValidationAttribute 验证一个字段必须大于另一个字段

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] pu ...

  9. Golang结构体struct的使用(结构体嵌套, 匿名结构体等)

    转自: https://studygolang.com/articles/11313 golang中是没有class的,但是有一个结构体struct,有点类似,他没有像java,c++中继承的概念,但 ...

随机推荐

  1. Redis+Tomcat+Nginx集群实现Session共享,Tomcat Session共享

    Redis+Tomcat+Nginx集群实现Session共享,Tomcat Session共享 ============================= 蕃薯耀 2017年11月27日 http: ...

  2. python学习笔记 list

    1.list中的任一元素可以是任一类型.可以是混合的,如,前两个字符串后面的是数字.都是可以的. 2.可以用-1表示最后一个元素. 3.注意不要越界. 4.len(mates) 用来计算list的大小 ...

  3. JAVA基础5——与String相关的系列(2)

    差异点比较 String使用+直接拼接 这种情况需要分两种情况来讨论: 1. 都是确定的字符串常量之间进行的+号拼接的时候,由于在编译器就可以确定其具体值了,所以编译器在编译期的时候就会把这些常量拼接 ...

  4. Jenkins与网站代码上线解决方案

    1.1 前言 Jenkins是一个用Java编写的开源的持续集成工具.在与Oracle发生争执后,项目从Hudson项目独立. Jenkins提供了软件开发的持续集成服务.它运行在Servlet容器中 ...

  5. 【面试问题】——秋招面试中遇到的一些问题&思维导图&反思

    前言:秋招也跑了挺多的公司,虽然都是招Web前端,但是不同的公司,因为需求和目的不同,面试的考察点也是各不相同.我没有实习经验,只有自己学东学西比较杂也比较浅的一些知识积累可以用,这个过程我发现了自己 ...

  6. 漫谈 SLAM 技术(上)

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:解洪文 导语 随着最近几年机器人.无人机.无人驾驶.VR/AR的火爆,SLAM技术也为大家熟知,被认为是这些领域的关键技术之一.本文对S ...

  7. 【深度学习系列】用PaddlePaddle和Tensorflow实现AlexNet

    上周我们用PaddlePaddle和Tensorflow实现了图像分类,分别用自己手写的一个简单的CNN网络simple_cnn和LeNet-5的CNN网络识别cifar-10数据集.在上周的实验表现 ...

  8. 企业级缓存系统varnish应用

    场景 随着公司业务快速发展,公司的电子商务平台已经聚集了很多的忠实粉丝,公司也拿到了投资,这时老板想通过一场类似双十一的活动,进行一场大的促销,届时会有非常多的粉丝访问网站,你的总监与市场部门开完会后 ...

  9. Neutron控制节点集群

    #Neutron控制节点集群 openstack pike 部署 目录汇总 http://www.cnblogs.com/elvi/p/7613861.html #.Neutron控制节点集群 #本实 ...

  10. Java基础(二)-static关键字分析

    static关键字是我们在编程中经常会使用到的,但有些可能只知其然而不知其所以然.下面介绍static关键字的作用再通过例子结合说明. static关键字共有五种作用(先说明static所修饰的不会改 ...