Go обрабатывает файлы конфигурации типа yaml

Go

 

Сперва поговорим об этом, тут используется много функций про типы отражения, можно просто начать читать код, если не знаком с отражением, то может быть не очень понятно, но в то же время и полезно лучше понять отражение в golang.Код можно получить прямо с моего адреса git:
go get github.com/pythonsite/config_yaml
Перейдите непосредственно к коду:

// 可以用于处理读yaml格式的配置文件,同时也可以用于理解golang中的反射
package config_yaml

import (
    "strings"
    "errors"
    "io/ioutil"
    "gopkg.in/yaml.v2"
    "reflect"
    "fmt"
    "strconv"
)

type ConfigEngine struct {
    data map[interface{}]interface{}
}


// 将ymal文件中的内容进行加载
func (c *ConfigEngine) Load (path string) error {
    ext := c.guessFileType(path)
    if ext == "" {
        return errors.New("cant not load" + path + " config")
    }
    return c.loadFromYaml(path)
}

//判断配置文件名是否为yaml格式
func (c *ConfigEngine) guessFileType(path string) string {
    s := strings.Split(path,".")
    ext := s[len(s) - 1]
    switch ext {
    case "yaml","yml":
        return "yaml"
    }
    return ""
}

// 将配置yaml文件中的进行加载
func (c *ConfigEngine) loadFromYaml(path string) error {
    yamlS,readErr := ioutil.ReadFile(path)
    if readErr != nil {
        return readErr
    }
    // yaml解析的时候c.data如果没有被初始化,会自动为你做初始化
    err := yaml.Unmarshal(yamlS, &c.data)
    if err != nil {
        return errors.New("can not parse "+ path + " config" )
    }
    return nil
}

// 从配置文件中获取值
func (c *ConfigEngine) Get(name string) interface{}{
    path := strings.Split(name,".")
    data := c.data
    for key, value := range path {
        v, ok := data[value]
        if !ok {
            break
        }
        if (key + 1) == len(path) {
            return v
        }
        if reflect.TypeOf(v).String() == "map[interface {}]interface {}"{
            data = v.(map[interface {}]interface {})
        }
    }
    return nil
}

// 从配置文件中获取string类型的值
func (c *ConfigEngine) GetString(name string) string {
    value := c.Get(name)
    switch value:=value.(type){
    case string:
        return value
    case bool,float64,int:
        return fmt.Sprint(value)
    default:
        return ""
    }
}

// 从配置文件中获取int类型的值
func (c *ConfigEngine) GetInt(name string) int {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        i,_:= strconv.Atoi(value)
        return i
    case int:
        return value
    case bool:
        if value{
            return 1
        }
        return 0
    case float64:
        return int(value)
    default:
        return 0
    }
}

// 从配置文件中获取bool类型的值
func (c *ConfigEngine) GetBool(name string) bool {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        str,_:= strconv.ParseBool(value)
        return str
    case int:
        if value != 0 {
            return true
        }
        return false
    case bool:
        return value
    case float64:
        if value != 0.0 {
            return true
        }
        return false
    default:
        return false
    }
}

// 从配置文件中获取Float64类型的值
func (c *ConfigEngine) GetFloat64(name string) float64 {
    value := c.Get(name)
    switch value := value.(type){
    case string:
        str,_ := strconv.ParseFloat(value,64)
        return str
    case int:
        return float64(value)
    case bool:
        if value {
            return float64(1)
        }
        return float64(0)
    case float64:
        return value
    default:
        return float64(0)
    }
}

// 从配置文件中获取Struct类型的值,这里的struct是你自己定义的根据配置文件
func (c *ConfigEngine) GetStruct(name string,s interface{}) interface{}{
    d := c.Get(name)
    switch d.(type){
    case string:
        c.setField(s,name,d)
    case map[interface{}]interface{}:
        c.mapToStruct(d.(map[interface{}]interface{}), s)
    }
    return s
}

func (c *ConfigEngine) mapToStruct(m map[interface{}]interface{},s interface{}) interface{}{
    for key, value := range m {
        switch key.(type) {
        case string:
            c.setField(s,key.(string),value)
        }
    }
    return s
}

// 这部分代码是重点,需要多看看
func (c *ConfigEngine) setField(obj interface{},name string,value interface{}) error {
    // reflect.Indirect 返回value对应的值
    structValue := reflect.Indirect(reflect.ValueOf(obj))
    structFieldValue := structValue.FieldByName(name)

    // isValid 显示的测试一个空指针
    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj",name)
    }

    // CanSet判断值是否可以被更改
    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    // 获取要更改值的类型
    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)

    if structFieldType.Kind() == reflect.Struct && val.Kind() == reflect.Map {
        vint := val.Interface()

        switch vint.(type) {
        case map[interface{}]interface{}:
            for key, value := range vint.(map[interface{}]interface{}) {
                c.setField(structFieldValue.Addr().Interface(), key.(string), value)
            }
        case map[string]interface{}:
            for key, value := range vint.(map[string]interface{}) {
                c.setField(structFieldValue.Addr().Interface(), key, value)
            }
        }

    } else {
        if structFieldType != val.Type() {
            return errors.New("Provided value type didn't match obj field type")
        }

        structFieldValue.Set(val)
    }

    return nil
}

Сначала напишите пример использования вышеуказанного пакета: Во-первых, содержимое конфигурационного файла yaml, вот краткое описание содержимого:

Site:
    HttpPort: 8080
    HttpsOn: false
    Domain: "pythonsite.com"
    HttpsPort: 443
Nginx:
    Port: 80
    LogPath:  "/opt/log/nginx.log"
    Path: "/opt/nginx/"
SiteName: "this is my web site"
SiteAddr: "BeiJing"

Код тестовой программы:

package main

import (
    "github.com/pythonsite/config_yaml"
    "fmt"

)

type SiteConfig struct {
    HttpPort    int
    HttpsOn     bool
    Domain      string
    HttpsPort   int
}

type NginxConfig struct {
    Port int
    LogPath string
    Path    string
}


func main() {
    c2 := config_yaml.ConfigEngine{}
    c2.Load("test.yaml")

    siteConf := SiteConfig{}
    res := c2.GetStruct("Site",&siteConf)
    fmt.Println(res)

    nginxConfig := NginxConfig{}
    res2 := c2.GetStruct("Nginx",&nginxConfig)
    fmt.Println(res2)

    siteName := c2.GetString("SiteName")
    siteAddr := c2.GetString("SiteAddr")
    fmt.Println(siteName,siteAddr)

}

Эффект следующий:

Я чувствую себя очень хорошо.

Добавьте некоторые точки знаний (см. Библию Go)

значение интерфейса

Значение интерфейса состоит из двух частей: конкретного типа и значения этого типа, и эти две концепции называются динамическим типом и динамическим значением интерфейса.
В языке Go переменные всегда инициализируются до того, как мы сможем их использовать, даже если тип интерфейса не является исключением.
Нулевое значение для значения интерфейса является значением типа, а содержимое значения равно нулю.

Для значений интерфейса мы можем использовать ==nil или !=nil, чтобы определить, является ли значение интерфейса пустым.
var w io.Writer
w = os.Stdout
При назначении через w = os.Stdout вызывается неявное преобразование конкретного типа в тип интерфейса, что эквивалентно явному использованию io.Writer(os.Stdout).
После присваивания динамический тип значения интерфейса w устанавливается равным дескриптору типа указателя *os.Stdout, а его динамическое значение является копией os.Stdout.

Обычно во время компиляции мы не знаем, каков динамический тип значения интерфейса, поэтому вызов интерфейса должен использовать динамическое выделение. потому что
Он не вызывается напрямую, поэтому компилятор должен сгенерировать код в методе Write дескриптора типа, а затем косвенно вызвать этот адрес.
Получатель этого вызова является копией динамического значения интерфейса, os.Stdout. Эффект аналогичен следующему прямому вызову:
os.Stdout.Write([]byte("hello")) эквивалентно w.Write([]byte("hello"))

отражение

Функция Reflect.TypeOf принимает любой тип interface{} и возвращает Reflect.Type, соответствующий динамическому типу:

t := reflect.TypeOf(3)
fmt.Println(t.String())
fmt.Println(t)

Мы можем взглянуть на подробный метод Reflect.TypeOf:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

Вы можете видеть, что тип параметра является интерфейсом {}. Как упоминалось выше в значении интерфейса, неявное преобразование этого конкретного значения, то есть 3, которые мы передали, создаст значение интерфейса, содержащее две части информации: 3 динамический тип этой переменной, здесь int 3 динамическое значение этой переменной, здесь 3

Reflect.ValueOf принимает любой тип interface{} и возвращает соответствующий динамический тип, Reflect.Value. и
Как и в случае с Reflect.TypeOf, результат, возвращаемый с помощью Reflect.ValueOf, также относится к определенному типу, но Reflect.Value также может
для хранения значения интерфейса

v := reflect.ValueOf(3)
fmt.Println(v)
fmt.Println(v.String())

v2 := reflect.ValueOf("abc")
fmt.Println(v2)
fmt.Println(v2.String())

Здесь следует отметить, что если Value не содержит строку, String просто возвращает определенный тип.Метод Type для Value будет возвращать Reflect.Type, соответствующий конкретному типу:

t = v.Type()
fmt.Println(t)
fmt.Println(t.String())

Конечно, мы также можем обратить операцию обратно к Reflect.Value.Interface, а затем реализовать Reflect.Value и interface{}, утверждая, что оба могут сохранить любое значение. Разница в том, что пустой интерфейс скрывает соответствующее представление значение. и все общедоступные методы, поэтому только мы знаем, что конкретный динамический тип может использовать утверждение типа для доступа к внутреннему значению, нет ничего особенного в том, чтобы делать с внутренним значением. Напротив, у значения есть много методов для проверки его содержимого, независимо от его конкретного типа

Виды, использующие Reflect.Value, ограничены: Bool, String и базовые типы всех числовых типов, совокупные типы для Array и Struct, ссылки для Chan, Func, Ptr, Slice и Map, типы интерфейса и недопустимые типы. которые представляют нулевые значения. (Пустое значение Reflect.Value соответствует Invalid недопустимым типам.)

Измените значение с помощью Reflect.Value

Некоторые из Reflect.Values ​​являются адресуемыми, другие — нет, например:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    a := reflect.ValueOf(2)
    b := reflect.ValueOf(x)
    c := reflect.ValueOf(&x)
    d := c.Elem()
    fmt.Println(a.CanAddr()) // false
    fmt.Println(b.CanAddr()) // false
    fmt.Println(c.CanAddr()) // false
    fmt.Println(d.CanAddr()) // true

}

Все отражения.Значения, возвращаемые Reflect.ValueOf(x), являются нежелательными адресами. Но для d он генерируется путем разыменования c, указывающего на другую переменную, поэтому он является адресуемым. Мы можем получить значение адресуемого адреса, соответствующего любой переменной x, вызвав Reflect.ValueOf(&x).Elem().

Для доступа к переменной из Reflect.Value соответствующего адресуемого адреса переменной требуется три шага. Первым шагом является вызов метода Addr(), который возвращает значение, содержащее указатель на переменную. Затем вызывается метод Interface() для Value, то есть возвращается интерфейс {}, который обычно содержит указатель на переменную. Наконец, если мы знаем тип переменной, мы можем использовать механизм утверждения типа, чтобы заставить результирующий интерфейс типа interface{} быть обычным указателем типа. Таким образом, мы можем обновить переменную с помощью этого обычного указателя:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    d := reflect.ValueOf(&x).Elem()
    px := d.Addr().Interface().(*int)
    *px = 3
    fmt.Print(x)
}

Конечно, это можно изменить и другим способом:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    d := reflect.ValueOf(&x).Elem()
    //px := d.Addr().Interface().(*int)
    //*px = 3
    d.Set(reflect.ValueOf(4))
    fmt.Print(x)
}

А вот прямой Set сообщит об ошибке, потому что присваивание, которое мы только что здесь использовали, тоже типа int, если мы используем строку, то будет паника.

 

Всех усилий стоит ждать с нетерпением, и каждая мечта должна быть орошена!