Skip to main content
Version: 1.16.x

自定义规则分为两种:全局规则注册和局部规则注册。

全局校验规则注册

全局规则是全局生效的规则,注册之后无论是使用方法还是对象来执行数据校验都可以使用自定义的规则。

注册校验方法:

// RegisterRule registers custom validation rule and function for package.
// It returns error if there's already the same rule registered previously.
func RegisterRule(rule string, f RuleFunc) error

校验方法定义:

// RuleFunc is the custom function for data validation.
// The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc.
// The parameter `value` specifies the value for this rule to validate.
// The parameter `message` specifies the custom error message or configured i18n message for this rule.
// The parameter `data` specifies the `data` which is passed to the Validator. It might be type of map/struct or a nil value.
// You can ignore the parameter `data` if you do not really need it in your custom validation rule.
type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error

简要说明:

  1. 您需要按照 RuleFunc 类型的方法定义,实现一个您需要的校验方法,随后使用 RegisterRule 注册到 gvalid 模块中全局管理。该注册逻辑往往是在程序初始化时执行。该方法在对数据进行校验时将会被自动调用,方法返回 nil 表示校验通过,否则应当返回一个非空的 error 类型值。
  2. 参数说明:
    • ctx 表示当前链路的上下文变量。
    • rule 参数表示当前的校验规则,包含规则的参数,例如: required, between:1,100, length:6 等等。
    • value 参数表示被校验的数据值,注意类型是一个 interface{},因此您可以传递任意类型的参数,并在校验方法内部按照程序使用约定自行进行转换(往往使用 gconv 模块实现转换)。
    • message 参数表示在校验失败后返回的校验错误提示信息,往往在 struct 定义时使用 gvalid tag 来通过标签定义。
    • data 参数表示校验时传递的参数,例如校验的是一个 map 或者 struct 时,往往在联合校验时有用。需要注意的是,这个值是运行时输入的,值可能是 nil

自定义错误默认情况下已支持 i18n 特性,因此您只需要按照 gf.gvalid.rule.自定义规则名称 配置 i18n 转译信息即可,该信息在校验失败时自动从 i18n 管理器获取后,通过 message 参数传入给您注册的自定义校验方法中。

注意事项:自定义规则的注册方法不支持并发调用,您需要在程序启动时进行注册(例如在 boot 包中处理),无法在运行时动态注册,否则会产生并发安全问题。

示例1,用户唯一性规则

在用户注册时,我们往往需要校验当前用户提交的名称/账号是否唯一,因此我们可以注册一个 unique-name 的全局规则来实现。

package main

import (
"context"
"fmt"
"github.com/gogf/gf/util/gvalid"

"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)

type User struct {
Id int
Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"`
Pass string `v:"required|length:6,18"`
}

func init() {
rule := "unique-name"
if err := gvalid.RegisterRule(rule, UniqueNameChecker); err != nil {
panic(err)
}
}

func UniqueNameChecker(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
user := &User{}
if v, ok := data.(*User); ok {
user = v
}
// SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx
count, err := g.Model("user").
Ctx(ctx).
WhereNot("id", user.Id).
WhereNot("name", gconv.String(value)).
Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New(message)
}
return nil
}

func main() {
user := &User{
Id: 1,
Name: "john",
Pass: "123456",
}
err := g.Validator().CheckStruct(user)
fmt.Println(err)
}

示例2,自定义覆盖 required 规则

默认情况下, gvalid 的内置规则是不支持 mapslice 类型的 required 规则校验,因此我们可以自行实现,然后覆盖原有规则(这里需要用到反射)。其他的规则也可以按照此逻辑自定义覆盖。

package main

import (
"context"
"errors"
"fmt"
"github.com/gogf/gf/util/gvalid"
"math"
"reflect"

"github.com/gogf/gf/frame/g"
)

func init() {
rule := "required"
if err := gvalid.RegisterRule(rule, RequiredChecker); err != nil {
panic(err)
}
}

func RequiredChecker(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
reflectValue := reflect.ValueOf(value)
if reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
isEmpty := false
switch reflectValue.Kind() {
case
reflect.Bool:
isEmpty = !reflectValue.Bool()

case
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64:
isEmpty = reflectValue.Int() == 0

case
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr:
isEmpty = reflectValue.Uint() == 0

case
reflect.Float32,
reflect.Float64:
isEmpty = math.Float64bits(reflectValue.Float()) == 0

case
reflect.Complex64,
reflect.Complex128:
c := reflectValue.Complex()
isEmpty = math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0

case
reflect.String,
reflect.Map,
reflect.Array,
reflect.Slice:
isEmpty = reflectValue.Len() == 0
}
if isEmpty {
return errors.New(message)
}
return nil
}

func main() {
fmt.Println(g.Validator().Rules("required").Messages("It's required").CheckValue(""))
fmt.Println(g.Validator().Rules("required").Messages("It's required").CheckValue([]string{}))
fmt.Println(g.Validator().Rules("required").Messages("It's required").CheckValue(map[string]int{}))
}

执行后,终端输出:

It's required
It's required
It's required

局部校验规则注册

局部规则是仅在当前校验对象下生效规则,校验规则是注册到对象下而不是全局中。

注册方法:

// RuleFunc registers one custom rule function to current Validator.
func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator

// RuleFuncMap registers multiple custom rule functions to current Validator.
func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator

简要介绍:

  • RuleFunc 方法用于注册单个自定义校验规则到当前对象。
  • RuleFuncMap 方法用于注册多个自定义校验规则到当前对象。

使用示例:

我们将上面的例子改为局部校验规则注册。

package main

import (
"context"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)

type User struct {
Id int
Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"`
Pass string `v:"required|length:6,18"`
}

func UniqueNameChecker(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
user := &User{}
if v, ok := data.(*User); ok {
user = v
}
// SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx
count, err := g.Model("user").
Ctx(ctx).
WhereNot("id", user.Id).
WhereNot("name", gconv.String(value)).
Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New(message)
}
return nil
}

func main() {
user := &User{
Id: 1,
Name: "john",
Pass: "123456",
}
err := g.Validator().RuleFunc("unique-name", UniqueNameChecker).CheckStruct(user)
fmt.Println(err)
}