- Created by 郭强, last modified by 黄骞 on Dec 12, 2021
相关数据结构
自定义规则方法定义,以及对应的输入参数数据结构。
// 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
方法参数简要说明:
- 上下文参数
ctx
是必须的。 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 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
3 Comments
王一飞
强烈建议支持通过给结构体绑定方法的方式来校验结构体,现在这种使用tag进行校验的方式扩展性太差了
Victor
RuleFuncInput 数据结构应该再加一个字段 Field 表示当前请求的字段名,因为很多请求结构体会共用一套校验规则.这样可以把字段名放在结果的错误提示上.
(虽然用message也可以,但这样会使每个用rule的地方都要写一遍message.)
yaji
RuleFuncInput Value 字段 传什么数据判断都是isStruct 为true,没法判断值的类型,传个数组进来in.value.isSlice() 会直接报错