自定义规则-规则注册

相关数据结构

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

// 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。

自定义规则-规则注册 - 图1 提示

自定义错误默认情况下已支持 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 类型值。

自定义规则-规则注册 - 图2 注意

注意事项:自定义规则的注册方法不支持并发调用,您需要在程序启动时进行注册(例如在 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)
}