相关数据结构

自定义规则方法定义,以及对应的输入参数数据结构。

// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc.
type RuleFuncInput struct {
	// Rule specifies the validation rule string, like "required", "between:1,100", etc.
	Rule string

	// Message specifies the custom error message or configured i18n message for this rule.
	Message string

	// Value specifies the value for this rule to validate.
	Value *gvar.Var

	// Data specifies the `data` which is passed to the Validator. It might be a 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.
	Data *gvar.Var
}

// RuleFunc is the custom function for data validation.
type RuleFunc func(ctx context.Context, in RuleFuncInput) error

方法参数简要说明:

  1. 上下文参数ctx是必须的。
  2. RuleFuncInput数据结构说明:
    • Rule表示当前的校验规则,包含规则的参数,例如:required,  between:1,100,  length:6等等。
    • Message参数表示在校验失败后返回的校验错误提示信息。
    • Value参数表示被校验的数据值,注意类型是一个*gvar.Var泛型,因此您可以传递任意类型的参数。
    • Data参数表示校验时传递的参数,例如校验的是一个map或者struct时,往往在联合校验时有用。需要注意的是,这个值是运行时输入的,值可能是nil

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

全局校验规则注册

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

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

注册校验方法:

// RegisterRule registers custom validation rule and function for package.
func RegisterRule(rule string, f RuleFunc) {
	customRuleFuncMap[rule] = f
}

// RegisterRuleByMap registers custom validation rules using map for package.
func RegisterRuleByMap(m map[string]RuleFunc) {
	for k, v := range m {
		customRuleFuncMap[k] = v
	}
}

您需要按照RuleFunc类型的方法定义,实现一个您需要的校验方法,随后使用RegisterRule注册到gvalid模块中全局管理。该注册逻辑往往是在程序初始化时执行。该方法在对数据进行校验时将会被自动调用,方法返回nil表示校验通过,否则应当返回一个非空的error类型值。

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

示例1,订单ID存在校验

在电商业务中,当我们对订单进行操作时,可以通过自定义规则校验给定的订单ID是否存在,因此我们可以注册一个order-exist的全局规则来实现。

package main

import (
	"context"
	"fmt"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/util/gvalid"
	"time"
)

type Request struct {
	OrderId     int64 `v:"required|order-exist"`
    ProductName string
	Amount      int64
	// ...
}

func init() {
	rule := "order-exist"
	gvalid.RegisterRule(rule, RuleOrderExist)
}

func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error {
	// SELECT COUNT(*) FROM `order` WHERE `id` = xxx
	count, err := g.Model("order").
		Ctx(ctx).
		Cache(gdb.CacheOption{
			Duration: time.Hour,
			Name:     "",
			Force:    false,
		}).
		WhereNot("id", in.Value.Int64()).
		Count()
	if err != nil {
		return err
	}
	if count == 0 {
		return gerror.Newf(`invalid order id "%d"`, in.Value.Int64())
	}
	return nil
}

func main() {
	var (
		ctx = gctx.New()
		req = &Request{
			OrderId:     65535,
			ProductName: "HikingShoe",
			Amount:      10000,
		}
	)
	err := g.Validator().CheckStruct(ctx, req)
	fmt.Println(err)
}

示例2,用户唯一性规则

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

package main

import (
	"context"
	"fmt"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/util/gvalid"
	"time"
)

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

func init() {
	rule := "unique-name"
	gvalid.RegisterRule(rule, RuleUniqueName)
}

func RuleUniqueName(ctx context.Context, in gvalid.RuleFuncInput) error {
	var user *User
	if err := in.Data.Scan(&user); err != nil {
		return gerror.Wrap(err, `Scan data to user failed`)
	}
	// SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx
	count, err := g.Model("user").
		Ctx(ctx).
		Cache(gdb.CacheOption{
			Duration: time.Hour,
			Name:     "",
			Force:    false,
		}).
		WhereNot("id", user.Id).
		WhereNot("name", user.Name).
		Count()
	if err != nil {
		return err
	}
	if count > 0 {
		if in.Message != "" {
			return gerror.New(in.Message)
		}
		return gerror.Newf(`user name "%s" is already token by others`, user.Name)
	}
	return nil
}

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

局部校验规则注册

局部规则是仅在当前校验对象下生效规则,校验规则是注册到当前使用的链式操作流程中而不是全局中。

注册方法:

// 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/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/util/gvalid"
	"time"
)

type Request struct {
	OrderId     int64
	ProductName string
	Amount      int64
	// ...
}

func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error {
	// SELECT COUNT(*) FROM `order` WHERE `id` = xxx
	count, err := g.Model("order").
		Ctx(ctx).
		Cache(gdb.CacheOption{
			Duration: time.Hour,
			Name:     "",
			Force:    false,
		}).
		WhereNot("id", in.Value.Int64()).
		Count()
	if err != nil {
		return err
	}
	if count == 0 {
		return gerror.Newf(`invalid order id "%d"`, in.Value.Int64())
	}
	return nil
}

func main() {
	var (
		ctx = gctx.New()
		req = &Request{
			OrderId:     65535,
			ProductName: "HikingShoe",
			Amount:      10000,
		}
	)
	err := g.Validator().RuleFunc("order-exist", RuleOrderExist).Data(req).Run(ctx)  
	fmt.Println(err)
}
Content Menu

  • No labels

6 Comments

  1. 强烈建议支持通过给结构体绑定方法的方式来校验结构体,现在这种使用tag进行校验的方式扩展性太差了

  2. RuleFuncInput 数据结构应该再加一个字段 Field 表示当前请求的字段名,因为很多请求结构体会共用一套校验规则.这样可以把字段名放在结果的错误提示上.

    (虽然用message也可以,但这样会使每个用rule的地方都要写一遍message.)

    1. 支持,看到一些内置的校验规则也使用了 the {field}  value `{value}` is invalid 这样的message,其中的参数值填充在哪里没看到?

      如果能通过模板参数的方式自动填充这两个值就好了 

  3. RuleFuncInput Value 字段 传什么数据判断都是isStruct 为true,没法判断值的类型,传个数组进来in.value.isSlice() 会直接报错

  4. 郭强 示例1:订单ID存在校验,这里不应该用 WhereNot ,应该用 Where

    func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error {
    	// SELECT COUNT(*) FROM `order` WHERE `id` = xxx
    	count, err := g.Model("order").
    		Ctx(ctx).
    		Cache(gdb.CacheOption{
    			Duration: time.Hour,
    			Name:     "",
    			Force:    false,
    		}).
    		WhereNot("id", in.Value.Int64()).
    		Count()
    	if err != nil {
    		return err
    	}
    	if count == 0 {
    		return gerror.Newf(`invalid order id "%d"`, in.Value.Int64())
    	}
    	return nil
    }
    
  5. 自定义规则没法设置成类似: length:1,10  的动态方式,比如,gf 没有提供限制 array 长度的验证规则,我想自定义 array-length:min,max 规则,目前没法实现,看了内部的实现是 builtin.Rule 接口

    为何不把这个接口开放出来给用户使用??用户也可以实现这个接口,实现更自由的自定义规则,我觉得这应该很有必要 郭强