美文网首页Golang/Gin 学习
Golang-03 自定义validator,实现java注解功

Golang-03 自定义validator,实现java注解功

作者: 国服最坑开发 | 来源:发表于2019-08-26 11:32 被阅读0次
    0x00 About

    接口开发中, 比较常用的操作就是对输入的参数Bean进行字段属性值校验.
    Java中, 有Annotation(注解)可以让我们方便的在的类上面添加校验信息,
    那么在Go中应该如何做到这一点呢?

    0x01 structTag

    我们先来看一下strcut的语法定义:https://golang.org/ref/spec#Struct_types


    StructType = "struct" "{" { FieldDecl ";" } "}" .
    FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
    EmbeddedField = [ "*" ] TypeName .
    Tag = string_lit .</pre>


    也就是说, struct字段的后面, 可以添加一个字符串, 称之为Tag

    然后, 通过go语言中的reflect反射机制, 就可以读取相应字段的Tag信息了.
    接下来, 找一段代码直观感受一下:

    type InParam struct {
        StudentName string `json:"name" cc:"str,min=5,max=15"`
        Score       int    `json:"score" cc:"num,min=0,max=100"`
    }
    
    package main
    
    import (
        "fmt"
        "reflect"
    )
    func main() {
        t := reflect.TypeOf(&InParam{"18", 25})
        field := t.Elem().Field(0)
    
        jsonName := field.Tag.Get("json")
        cc := field.Tag.Get("cc")
    
        fmt.Printf("%s(%s): %s", field.Name, jsonName, cc)
    }
    

    输出结果:
    StudentName(name): str,min=5,max=15

    需要注意的地方: reflect.TypeOf 的参数只能接入对象.

    0x02 Validator

    那么接下来的工作, 就是根据Tag内容开发一个公共函数.
    这里提供了两种验证器:stringnumber, 分别验证其长度范围和取值范围.

    package main
    
    import (
        "fmt"
        "reflect"
        "strings"
    )
    
    // 定义接口
    type CCValid interface {
        Validate(interface{}) (bool, error)
    }
    
    // 定义三个验证器
    type CCDefaultValid struct {
    }
    type CCNumberValid struct {
        Min int
        Max int
    }
    type CCStringValid struct {
        Min int
        Max int
    }
    
    // 三个验证器实现接口方法
    func (c CCNumberValid) Validate(obj interface{}) (bool, error) {
        v := obj.(int)
        if v < c.Min || v > c.Max {
            return false, fmt.Errorf(":int value should in range (%d, %d)", c.Min, c.Max)
        }
        return true, nil
    }
    func (c CCStringValid) Validate(obj interface{}) (bool, error) {
        l := len(obj.(string))
        if l < c.Min || l > c.Max {
            return false, fmt.Errorf(":string length should in range (%d, %d)", c.Min, c.Max)
        }
        return true, nil
    }
    
    func (c CCDefaultValid) Validate(obj interface{}) (bool, error) {
        return true, nil
    }
    
    var tagName = "cc"
    
    //  公共方法,对外提供检验处理
    func CCValidate(s interface{}) []error {
        var errs []error
        v := reflect.ValueOf(s)
    
        for i := 0; i < v.NumField(); i++ {
            tag := v.Type().Field(i).Tag.Get(tagName)
            if tag == "" || tag == "-" {
                continue
            }
    
            validator := parseValidatorFromTag(tag)
            valid, err := validator.Validate(v.Field(i).Interface())
            if !valid && err != nil {
                errs = append(errs, fmt.Errorf("%s%s", v.Type().Field(i).Name, err.Error()))
            }
        }
    
        return errs
    }
    
    // 从Tag字符串里分析出使用哪个验证器,并赋值
    func parseValidatorFromTag(tag string) CCValid {
        args := strings.Split(tag, ",")
        switch args[0] {
        case "num":
            v := CCNumberValid{}
            fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &v.Min, &v.Max)
            return v
        case "str":
            v := CCStringValid{}
            fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &v.Min, &v.Max)
            return v
        }
        return CCDefaultValid{}
    }
    
    
    0x03 在gin中的使用

    我们开发校验器的目的, 当然, 是为了能在gin中使用啦
    直接上代码, 理解起来不难:

    
    type Result struct {
        code int
        msg  string
        data interface{}
    }
    
    func OK(msg string) Result {
        return Result{0, msg, nil}
    }
    
    func NG(msg string) Result {
        return Result{1, msg, nil}
    }
    
    
        // 解析JSON请求
        r.POST("/login", func(c *gin.Context) {
            var header MyHeader
            var param Login
    
            // 解析Header
            if c.BindHeader(&header) != nil {
                c.JSON(200, NG("invalid header"))
                return
            }
    
            // 解析JSON
            if c.BindJSON(&param) != nil {
                c.JSON(200, NG("invalid json param"))
                return
            }
    
            // 验证
            for _, firstErr := range CCValidate(param) {
                c.JSON(200, NG(firstErr.Error()))
                return
            }
    
            c.JSON(200, gin.H{"hello": param.Username, "world": param.Password, "from": header.From})
        })
    

    当验证出错时, 会向客户端返回第一个发现的错误.

    0x04 TODO

    现在的代码来看, 业务功能还没开发呢, 已经写了这么多行代码了. 一点都不优雅.
    接下来要考虑, 能不能利用中间件, 完成这些前置操作.

    相关文章

      网友评论

        本文标题:Golang-03 自定义validator,实现java注解功

        本文链接:https://www.haomeiwen.com/subject/clcrectx.html