【sduoj】用 validator 进行接口校验

2021SC@SDUSC

引言

在 sduoj 项目中,服务端需要接收来自前端的请求,根据不同的请求来进行不同的业务处理。如果没有一个规范化的约束的话,服务端就有可能接收到不合理的请求。为了避免参数的错误导致业务逻辑的混乱,我们需要一种接口校验机制,来退回这些不合理的请求。

接口入参校验规则写在对应的校验结构体的字段标签上,在CreateTagRequest中,我们看到这个它需要三个请求体参数,分别是NameCreatedByState。在字段标签中,form中存放参数名,binding存放规则约束。

type CreateTagRequest struct {
    
    
	Name      string `form:"name" binding:"required,min=3,max=100"`
	CreatedBy string `form:"created_by" binding:"required,min=3,max=100"`
	State     uint8  `form:"state,default=1" binding:"oneof=0 1"`
}

以下是常见的标签含义。

标签 含义
require 必填
gt 大于
gte 大于等于
lt 小于
lte 小于等于
min 最小值
max 最大值
oneof 参数集内的其中之一
len 长度要求与 len 给定的一致

源码分析

BindAndValid方法主要负责错误的处理,其参数绑定和校验工作放在c.ShouldBind中。在错误处理中,该方法从上下文中获取trans所对应的值,将错误信息进行一定的语言转化工作,并将这些错误信息存入ValidErrors中。

func BindAndValid(c *gin.Context, v interface{
    
    }) (bool, ValidErrors) {
    
    
	var errs ValidErrors
	err := c.ShouldBind(v)
	if err != nil {
    
    
		v := c.Value("trans")
		trans, _ := v.(ut.Translator)
		verrs, ok := err.(val.ValidationErrors)
		if !ok {
    
    
			return false, errs
		}

		for key, value := range verrs.Translate(trans) {
    
    
			errs = append(errs, &ValidError{
    
    
				Key:     key,
				Message: value,
			})
		}

		return false, errs
	}

	return true, nil
}

validErrors[]*validError的别名,在这里我们实现了*ValidErrorError方法,而error接口只有Error方法,也就是说,*ValidError实现了error接口。

type ValidError struct {
    
    
	Key     string
	Message string
}

type ValidErrors []*ValidError

func (v *ValidError) Error() string {
    
    
	return v.Message
}

ShouldBind会根据Context的类型自动选择参数绑定引擎,比如我们的Context类型为 application/json 的话,我们就会选择 JSON 绑定引擎。

func (c *Context) ShouldBind(obj interface{
    
    }) error {
    
    
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.ShouldBindWith(obj, b)
}

ShouldBindWith会使用具体的绑定引擎来绑定通过的结构体指针。

func (c *Context) ShouldBindWith(obj interface{
    
    }, b binding.Binding) error {
    
    
	return b.Bind(c.Request, obj)
}

Value方法返回了与key的上下文有关的值。它先检测key的值是否为 0,如果是的话,就把c.Request返回出去;如果不是的话,就断言key是字符串类型,并将相关的值返回,如果key不是字符串类型,就返回nil

func (c *Context) Value(key interface{
    
    }) interface{
    
    } {
    
    
	if key == 0 {
    
    
		return c.Request
	}
	if keyAsString, ok := key.(string); ok {
    
    
		val, _ := c.Get(keyAsString)
		return val
	}
	return nil
}

Get方法返回了给定key所映射的值,期间涉及到一次加锁和解锁操作,这样可以保证获取这个值的操作是原子的。

func (c *Context) Get(key string) (value interface{
    
    }, exists bool) {
    
    
	c.mu.RLock()
	value, exists = c.Keys[key]
	c.mu.RUnlock()
	return
}

validator中默认的错误信息是英文,但我们的错误信息不一定要用英文,这里可以换成简体中文。这里是自定义了一个中间件,根据locale选择语言,然后将trans存放在上下文中。

func Translations() gin.HandlerFunc {
    
    
	return func(c *gin.Context) {
    
    
		uni := ut.New(en.New(), zh.New(), zh_Hant_TW.New())
		locale := c.GetHeader("locale")
		trans, _ := uni.GetTranslator(locale)
		v, ok := binding.Validator.Engine().(*validator.Validate)
		if ok {
    
    
			switch locale {
    
    
			case "zh":
				_ = zh_translations.RegisterDefaultTranslations(v, trans)
			case "en":
				_ = en_translations.RegisterDefaultTranslations(v, trans)
			default:
				_ = zh_translations.RegisterDefaultTranslations(v, trans)
			}
			c.Set("trans", trans)
		}

		c.Next()
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_45922876/article/details/120970207