好大一棵樹

pardon110發表於2020-02-18

夠浪的web路由超過十條,再使用官方庫就不那麼合適了。gin框架用了httprouter,另外比較好的是mux。
每個http謂詞方法都會在合適的時候構建一棵壓縮字典樹,大多數框架路由請求用到了這種神操作。

請求校驗

動波拳開路的箭頭型程式碼

func register(req RegisterReq) error{
    if len(req.Username) > 0 {
        if len(req.PasswordNew) > 0 && len(req.PasswordRepeat) > 0 {
            if req.PasswordNew == req.PasswordRepeat {
                if emailFormatValid(req.Email) {
                    createUser()
                    return nil
                } else {
                    return errors.New("invalid email")
                }
            } else {
                return errors.New("password and reinput must be the same")
            }
        } else {
            return errors.New("password and password reinput must be longer than 0")
        }
    } else {
        return errors.New("length of username cannot be 0")
    }
}

校驗原理

碼上說話,給待校驗物件(結構體)打上標籤,標籤內帶驗證規則,透過反射解析規則

package main

import (
    "fmt"
    "reflect"
    "regexp"
    "strconv"
    "strings"
)

// 重構波動拳開路的箭頭程式碼
// 以請求校驗程式碼示例,使用validator校驗器原理

// Nested 結構體
type Nested struct {
    Email string `validate:"email"`
}

// T 結構體
type T struct {
    Age    int `validate:"eq=10"`
    Nested Nested
}

// 郵箱方法驗證
func validateEmail(input string) bool {
    if pass, _ := regexp.MatchString(
        `^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, input,
    ); pass {
        return true
    }
    return false
}

// 樹形驗證資料成員
func validate(v interface{}) (bool, string) {
    validateResult := true
    errmsg := "success"

    // 反射介面值資料型別
    vt := reflect.TypeOf(v)
    // 反射介面值資料值
    vv := reflect.ValueOf(v)
    // 遍歷介面值結構體欄位
    for i := 0; i < vv.NumField(); i++ {
        // 返回struct.v 欄位物件
        fieldVal := vv.Field(i)
        // 獲取欄位標籤物件並取對應的字串值
        tagContent := vt.Field(i).Tag.Get("validate")
        // 獲取欄位值的在go中的原始型別種類
        k := fieldVal.Kind()

        switch k {
        case reflect.Int:
            // 斷言資料值並返回相應的資料值
            val := fieldVal.Int()
            // 分割標籤內容,獲取結構標籤中值
            tagValStr := strings.Split(tagContent, "=")
            tagVal, _ := strconv.ParseInt(tagValStr[1], 10, 64)
            if val != tagVal {
                errmsg = "validate int failed, tag is: " + strconv.FormatInt(
                    tagVal, 10,
                )
                validateResult = false
            }
        case reflect.String:
            val := fieldVal.String()
            tagValStr := tagContent
            switch tagValStr {
            case "email":
                // 郵件方法驗證
                nestedResult := validateEmail(val)
                if nestedResult == false {
                    errmsg = "validate mail failed, field val is: " + val
                    validateResult = false
                }
            }
        // 內嵌結構體種類判斷
        case reflect.Struct:
            // 進行嘗試優先遍歷,先獲取該變數介面值,可從介面值獲取型別及值及方法集資訊
            valInter := fieldVal.Interface()
            // 進行遞迴
            nestedResult, msg := validate(valInter)
            if nestedResult == false {
                validateResult = false
                errmsg = msg
            }
        }
    }
    return validateResult, errmsg
}

func main() {
    var a = T{Age: 9, Nested: Nested{Email: "abc@abc.com"}}

    validateResult, errmsg := validate(a)
    fmt.Println(validateResult, errmsg)
}

反射影響效能? 可以避免反射思路,使用Go內建的Parser對原始碼進行掃描,然後根據結構體的定義生成校驗程式碼

壓縮字典樹

每個節點上不只儲存一個字母了,減少樹的層數,同時因為每個節點上資料儲存也比通常的字典樹要多,所以程式的區域性性較好(一個節點的path載入到cache即可進行多個字元的對比),從而對CPU快取友好。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章