专栏首页Go实战Golang使用标签表达式校验结构体字段的有效性

Golang使用标签表达式校验结构体字段的有效性

一、背景

在服务的API接口层面,我们常常需要验证参数的有效性。 Golang中,大部分参数校验场景实际上是先将数据Bind到结构体,然后校验其字段值。

一般地,校验结构体字段值有如下两种实现方式。

  1. Case-By-Case 针对每个需校验的结构体字段分别写校验代码
    • 优点:自由灵活,适应所有场景
    • 缺点:重复且琐碎的码农工作,易使人厌烦
  2. 规则匹配,在结构体标签中设置预先支持的验证规则,如emailmax:100等形式
    • 优点:使用简单,不需要写琐碎的代码
    • 缺点:强依赖有限的规则,缺乏灵活性,无法满足复杂场景,如多字段关联验证等

思考:有没有一种方式,即简单易用(少写代码),又能满足各种复杂的校验场景?

答案是:有!结构体标签表达式 go-tagexpr 的出现,为我们提供了兼得鱼和熊掌的第三种选择。

二、认识 go-tagexpr

go-tagexpr 允许Gopher们在 struct tag 写表达式代码,并通过高性能的解释器计算其结果。

安装

go get -u github.com/bytedance/go-tagexpr

下面使用一个小示例,演示含有枚举、比较、字段关联的较复杂场景。

示例代码

import (
	"fmt"

	tagexpr "github.com/bytedance/go-tagexpr"
)

func ExampleTagexpr() {
	vm := tagexpr.New("te")
	type Meteorology struct {
		Season      string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
		Weather     string `te:"$!='snowing' || (Season)$=='winter'"`
		Temperature int    `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"`
	}
	m := &Meteorology{
		Season:      "summer",
		Weather:     "snowing",
		Temperature: 40,
	}
	r := vm.MustRun(m)
	fmt.Println(r.Eval("Season"))
	fmt.Println(r.Eval("Weather"))
	fmt.Println(r.Eval("Temperature@range"))
	fmt.Println(r.Eval("Temperature@alarm"))
	// Output:
	// true
	// false
	// false
	// Uncomfortable temperature: 40
}

代码诠释:

  • 新建一个标签名称为 te 的解释器 vm := tagexpr.New("te")
  • 定义一个结构体,添加标签表达式,并实例化一个 m 对象。其中 $ 表示当前字段值,(Season)$ 表示 Season 字段的值 type Meteorology struct { Season string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"` Weather string `te:"$!='snowing' || (Season)$=='winter'"` Temperature int `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"` } m := &Meteorology{ Season: "summer", Weather: "snowing", Temperature: 40, }
  • 将对象实例 m 放入解释器中运行,返回表达式对象 r r := vm.MustRun(m)
  • 计算 Season 字段匿名表达式($=='spring'||$=='summer'||$=='autumn'||$=='winter')的值。因字段值 summer 在穷举列表中,故表达式结果为“true” r.Eval("Season")
  • 计算 Weather 字段匿名表达式 $!='snowing' || (Season)$=='winter' 的值。因字段值为 snowing 且 Season 为 summer,故表达式结果为“false” r.Eval("Weather")
  • 计算 Temperature 字段的 range 表达式 $>=-10 && $<38 的值。因字段值为 40,超出给出的范围,所以结果为“false” r.Eval("Temperature@range")
  • 计算 Temperature 字段的 alarm 表达式 sprintf('Uncomfortable temperature: %v',$) 的值。这是一个调用内部函数的表达式,它打印并返回字符串,结果为“Uncomfortable temperature: 40” r.Eval("Temperature@alarm")

获取更多关于 go-expr 结构体标签表达式的语法知识 -> 查看这里

二、使用Validator校验

Validator 是有 go-expr 包提供的一个采用结构体标签表达式的参数校验组件。

主要特性

  • 它要求在每个待校验字段上添加结果为布尔值的匿名表达式
  • 当表达式结果为false时,表示验证不通过,此时组件将返回与该字段相关的错误信息
  • 它支持使用名称为msg且结果为字符串的表达式作为错误信息
  • 允许用户按需求自由修改错误信息的模板
  • 支持各种常见的运算符
  • 支持访问数组,切片,字典成员
  • 支持访问当前结构体中的任何字段
  • 支持访问嵌套字段,非导出字段等
  • 支持注册自定义的验证函数表达式
  • 内置len,sprintf,regexp,email,phone等函数表达式

安装

go get -u github.com/bytedance/go-tagexpr

我们基于前面示例稍作修改,来演示如何使用validator校验结构体字段的有效性。

示例代码

import (
	"fmt"

	"github.com/bytedance/go-tagexpr/validator"
)

func ExampleValidator() {
	vd := validator.New("vd")
	type Meteorology struct {
		Season      string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
		Weather     string `vd:"$!='snowing' || (Season)$=='winter'"`
		Temperature int    `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"`
		Contact     string `vd:"email($)"`
	}
	m := &Meteorology{
		Season:      "summer",
		Weather:     "rain",
		Temperature: 40,
		Contact:     "henrylee2cn@gmail.com",
	}
	err := vd.Validate(m)
	if err != nil {
		fmt.Println(err)
	}
	// Output:
	// Uncomfortable temperature: 40
}

代码诠释:

  • 新建一个标签名称为 vd 的校验器 vd := validator.New("vd")
  • 定义一个结构体,在标签上添加校验表达式,并使用 m 实例进行测试。 type Meteorology struct { Season string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"` Weather string `vd:"$!='snowing' || (Season)$=='winter'"` Temperature int `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"` Contact string `vd:"email($)"` } m := &Meteorology{ Season: "summer", Weather: "rain", Temperature: 40, Contact: "henrylee2cn@gmail.com", }
  • 校验实例 m 的各字段值是否有效,如果无效,则返回error信息 err := vd.Validate(m)

注册自己的校验函数

可能你已注意到 email($) 这个表达式,它是默认注册的一个函数表达式,用于验证邮箱的有效性。其实我们也可以定义自己通用的函数表达式,以便较少标签中的代码量,增加代码复用性。

下面以 email 函数的实现为例,演示如何注册自己的校验函数:

var pattern = "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"

emailRegexp := regexp.MustCompile(pattern)

validator.RegValidateFunc("email", func(args ...interface{}) bool {
	if len(args) != 1 {
		return false
	}
	s, ok := args[0].(string)
	if !ok {
		return false
	}
	return emailRegexp.MatchString(s)
}, true)

其中,validator.RegValidateFunc 的定义如下:

func RegValidateFunc(funcName string, fn func(args ...interface{}) bool, force ...bool) error

RegValidateFunc的force可选参数,表示是否强制覆盖已经注册了的同名函数。

**结论:**validator的使用方法非常简单、灵活且具有良好的扩展性,能够轻松满足各种复杂的验证场景。

获取更多关于 validator 校验器的语法知识 -> 查看这里

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Go unsafe进阶】执行空接口中的函数:除了断言与反射,你还有更好的选择

    希望可以在包外执行add函数,怎么办?此处,因为该函数签名是不可导出的,所以,正常思路是使用反射,代码可能是这样:

    henrylee2cn
  • Golang浮点型的默认舍入规则——四舍六入五成双

    四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则,又名银行家舍入法。它比通常用的四舍五入法更加精确。

    henrylee2cn
  • golang之runtime.SetFinalizer

    在实际的编程中,我们都希望每个对象释放时执行一个方法,在该方法内执行一些计数、释放或特定的要求,以往都是在对象指针置nil前调用一个特定的方法,golang提供...

    henrylee2cn
  • 用Visual Studio调试linux程序

    用Visual Studio调试linux程序?你真的没看错,这个是真的,不是标题党。当然如果你说VS2015及以上版本自带的linux调试插件,那就算了。这些...

    范蠡
  • Android 必知必会 - DialogFragment 使用总结

    Android 官方推荐使用 DialogFragment 来代替 Dialog ,可以让它具有更高的可复用性(降低耦合)和更好的便利性(很好的处理屏幕翻转的情...

    他叫自己MR.张
  • HTML5骇客帝国文字矩阵效果——简单易学

    大家是不是经常在电影中看到如图的文字特效呢,其实我们也可以做,而且原理非常简单,不想多看的小伙伴呢,也可以直接拿走代码,哈哈哈。

    浩Coding
  • 如何编写一个支持 Krew 的 kubectl 插件

    Krew 是一个用来管理 Kubectl 插件的工具,名字大概来自于 OS X 下著名的软件包管理器 Homebrew,使用 Krew 能够方便的查找、安装和使...

    崔秀龙
  • 动态规划之 0-1背包问题及改进

    有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。在选择装...

    欠扁的小篮子
  • 作为刚培训出来的程序员,面试的时候被问到的好多技术都不会,越面试越被打击该怎么办?

    作为一个在软件行业已经混了十几年的老司机,初学者参加面试收到打击是很正常的事情,这个和是不是参加培训没有太多直接的关系,关键还是经验能力上的问题,基本上入行之前...

    程序员互动联盟
  • 微信小程序开发手册离线版本-下载

    https://mp.weixin.qq.com/debug/wxadoc/dev/

    tonglei0429

扫码关注云+社区

领取腾讯云代金券