GoFrame 自定義規(guī)則-規(guī)則注冊

2022-03-30 14:12 更新

雖然?gvalid?已經(jīng)內(nèi)置了常見的數(shù)十種校驗(yàn)規(guī)則,但是在部分業(yè)務(wù)場景下我們需要自定義校驗(yàn)規(guī)則,特別是一些可以重復(fù)使用的業(yè)務(wù)相關(guān)的校驗(yàn)規(guī)則。當(dāng)然,?gvalid?如此的強(qiáng)大和貼心,她已經(jīng)為您考慮得如此周全。自定義校驗(yàn)規(guī)則可以實(shí)現(xiàn)靈活性強(qiáng),可復(fù)用性高的校驗(yàn)特性。

相關(guān)數(shù)據(jù)結(jié)構(gòu)

自定義規(guī)則方法定義,以及對應(yīng)的輸入?yún)?shù)數(shù)據(jù)結(jié)構(gòu)。

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

方法參數(shù)簡要說明:

  1. 上下文參數(shù)?ctx?是必須的。
  2. ?RuleFuncInput?數(shù)據(jù)結(jié)構(gòu)說明:
    • ?Rule?表示當(dāng)前的校驗(yàn)規(guī)則,包含規(guī)則的參數(shù),例如:?required?,  ?between:1,100?,  ?length:6?等等。
    • ?Message?參數(shù)表示在校驗(yàn)失敗后返回的校驗(yàn)錯(cuò)誤提示信息。
    • ?Value?參數(shù)表示被校驗(yàn)的數(shù)據(jù)值,注意類型是一個(gè)?*gvar.Var?泛型,因此您可以傳遞任意類型的參數(shù)。
    • ?Data?參數(shù)表示校驗(yàn)時(shí)傳遞的參數(shù),例如校驗(yàn)的是一個(gè)?map?或者?struct?時(shí),往往在聯(lián)合校驗(yàn)時(shí)有用。需要注意的是,這個(gè)值是運(yùn)行時(shí)輸入的,值可能是?nil?。

自定義錯(cuò)誤默認(rèn)情況下已支持?i18n?特性,因此您只需要按照 ?gf.gvalid.rule.自定義規(guī)則名稱? 配置?i18n?轉(zhuǎn)譯信息即可,該信息在校驗(yàn)失敗時(shí)會自動從?i18n?管理器獲取后,通過?Message?參數(shù)傳入給您注冊的自定義校驗(yàn)方法中。

全局校驗(yàn)規(guī)則注冊

自定義規(guī)則分為兩種:全局規(guī)則注冊和局部規(guī)則注冊。

全局規(guī)則是全局生效的規(guī)則,注冊之后無論是使用方法還是對象來執(zhí)行數(shù)據(jù)校驗(yàn)都可以使用自定義的規(guī)則。

注冊校驗(yàn)方法:

// 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?類型的方法定義,實(shí)現(xiàn)一個(gè)您需要的校驗(yàn)方法,隨后使用?RegisterRule?注冊到?gvalid?模塊中全局管理。該注冊邏輯往往是在程序初始化時(shí)執(zhí)行。該方法在對數(shù)據(jù)進(jìn)行校驗(yàn)時(shí)將會被自動調(diào)用,方法返回?nil?表示校驗(yàn)通過,否則應(yīng)當(dāng)返回一個(gè)非空的?error?類型值。

注意事項(xiàng):自定義規(guī)則的注冊方法不支持并發(fā)調(diào)用,您需要在程序啟動時(shí)進(jìn)行注冊(例如在?boot?包中處理),無法在運(yùn)行時(shí)動態(tài)注冊,否則會產(chǎn)生并發(fā)安全問題。

示例1,訂單ID存在校驗(yàn)

在電商業(yè)務(wù)中,當(dāng)我們對訂單進(jìn)行操作時(shí),可以通過自定義規(guī)則校驗(yàn)給定的訂單?ID?是否存在,因此我們可以注冊一個(gè)?order-exist?的全局規(guī)則來實(shí)現(xiàn)。

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,用戶唯一性規(guī)則

在用戶注冊時(shí),我們往往需要校驗(yàn)當(dāng)前用戶提交的名稱/賬號是否唯一,因此我們可以注冊一個(gè)??unique-nam?e?的全局規(guī)則來實(shí)現(xiàn)。

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)
}

局部校驗(yàn)規(guī)則注冊

局部規(guī)則是僅在當(dāng)前校驗(yàn)對象下生效規(guī)則,校驗(yàn)規(guī)則是注冊到當(dāng)前使用的鏈?zhǔn)讲僮髁鞒讨卸皇侨种小?

注冊方法:

// 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?方法用于注冊單個(gè)自定義校驗(yàn)規(guī)則到當(dāng)前對象。
  • ?RuleFuncMap?方法用于注冊多個(gè)自定義校驗(yàn)規(guī)則到當(dāng)前對象。

使用示例:

我們將上面其中一個(gè)例子改為局部校驗(yàn)規(guī)則注冊。

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)
}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號