Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

自定义规则注册

虽然gvalid已经内置了常见的数十种校验规则,但是在部分业务场景下我们需要自定义校验规则,特别是一些可以重复使用的业务相关的校验规则。当然,gvalid如此的强大,她已经为您考虑得如此周全。校验规则注册:如此的强大,她已经为您考虑得如此周全。自定义规则分为两种:全局规则注册和局部规则注册。

全局规则注册

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

注册校验方法:

Code Block
languagego
// 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

校验方法定义:

Code Block
languagego
// RuleFunc is the custom function for data validation.
// The parameter 
<rule>
`rule` specifies the validation rule string, like "required", "between:1,100", etc.
// The parameter 
<value>
`value` specifies the value for this rule to validate.
// The parameter 
<message>
`message` specifies the custom error message or configured i18n message for this rule.
// The parameter 
<params>
`data` specifies
all
 the `data` which is passed to the
parameters that needs.
 Validator. It might be type of map/struct or a nil value.
// You can ignore the parameter 
<params>
`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, 
params
data 
map[string]
interface{}) error

简要说明:

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

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


Note

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

示例1,用户唯一性规则

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

Code Block
languagego
package main

import (
	"context"
	"fmt"

	"github.com/gogf/gf/errorsutil/gerrorgvalid"

	"github.com/gogf/gf/frameerrors/ggerror"
	"github.com/gogf/gf/utilframe/gconvg"
	"github.com/gogf/gf/util/gvalidgconv"
)

// 用户唯一性校验规则: unique-name

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, paramsdata map[string]interface{}) error {
	var (user := &User{}
		idif v, ok := gconvdata.Int(params["Id"])(*User); ok {
		nameuser = gconv.String(value)v
	)}
	// SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx
	count, err := g.Model("user").
		Ctx(ctx).
		WhereNot("id", iduser.Id).
		WhereNot("name", namegconv.String(value)).
		Count()
	if err != nil {
		return err
	}
	if count > 0 {
		return gerror.New(message)
	}
	return nil
}

func main() {
	// 注册自定义校验规则
	rule := "unique-name"
	if err := gvalid.RegisterRule(rule, UniqueNameChecker); err != nil {
		panic(err)
	}
	type User struct {
		Id   int
		Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"`
		Pass string `v:"required|length:6,18"`
	}
	user := &User{
		Id:   1,
		Name: "john",
		Pass: "123456",
	}
	err := g.Validator().CheckStruct(user)
	fmt.Println(err.Error())

}

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

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

Code Block
languagego
package main

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

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

func init() {
	"github.com/gogf/gf/util/gvalid"
)

// 自定义的必需规则校验rule := "required"
	if err := gvalid.RegisterRule(rule, RequiredChecker); err != nil {
		panic(err)
	}
}

func RequiredChecker(ctx context.Context, rule string, value interface{}, message string, paramsdata map[string]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() {
	// 注册自定义校验规则,覆盖原有的required校验规则
	rule := "required"
	if err := gvalid.RegisterRule(rule, RequiredChecker); err != nil {
		panic(err)
	}
	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{}))
}

执行后,终端输出:

Code Block
languagexml
It's required
It's required
It's required










Panel
titleContent Menu

Table of Contents