Go 處理yaml類型的配置文件

来源:https://www.cnblogs.com/zhaof/archive/2018/04/26/8955332.html
-Advertisement-
Play Games

先說一下,這裡用到了很多關於反射類型的功能,可能剛開始看代碼,如果對反射不熟悉的可能會不是非常清晰,但是同時也是為了更好的理解golang中的反射,同時如果後面想在代碼中可以直接從我的git地址get:go get github.com/pythonsite/config_yaml直接上代碼: 先寫 ...


 

先說一下,這裡用到了很多關於反射類型的功能,可能剛開始看代碼,如果對反射不熟悉的可能會不是非常清晰,但是同時也是為了更好的理解golang中的反射,同時如果後面想在代碼中可以直接從我的git地址get:
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 或者!=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)
}

可以看到參數類型是一個interface{},就像上面在介面值中說的這裡會將這個具體的值即我們傳入的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 只是返回具體的類型.
Value 的 Type 方法將返回具體類型所對應的 reflect.Type:

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

當然我們也可以逆操作回去reflect.Value.Interface,然後通過斷言的方式實現
一個 reflect.Value 和 interface{} 都能保存任意的值. 所不同的是, 一個空的介面隱藏了值對應的 表示方式和所有的公開的方法, 因此只有我們知道具體的動態類型才能使用類型斷言來訪問內部的值, 對於內部值並沒有特別可做的事情. 相比之下, 一個 Value 則有很多方法來檢查其內容, 無論它的具體類型是什麼

使用 reflect.Value 的 Kind
kinds類型卻是有限的: 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)返回的 reflect.Value都是不可取地址的。但是對於d,它是c的解引用方式生成的,指向另一個變數,因此是可 取地址的。我們可以通過調用reflect.ValueOf(&x).Elem(),來獲取任意變數x對應的可取地址的 Value。

要從變數對應的可取地址的reflect.Value來訪問變數需要三個步驟。第一步是調用Addr()方法,它返回 一個Value,裡面保存了指向變數的指針。然後是在Value上調用Interface()方法,也就是返回一個 interface{},裡面通用包含指向變數的指針。最後,如果我們知道變數的類型,我們可以使用類型的斷 言機制將得到的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類型,如果我們用字元串的肯定就panic了

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如果不加單引號會使得除變數和int類型的值不能傳遞 轉發和重定向的區別 轉發需要填寫完整路徑,重定向只需要寫相對路徑。原因是重定向是一次請求之內已經定位到了伺服器端,轉發則需要兩次請求每次都需要完整的路徑。 Request和response在解決中文亂碼時的區別 Request只需要規定編碼集,而r ...
  • 不知道你有沒有發現現在身邊學java的人越來越越多呢?其實在小編高考的時候,身邊選電腦專業的同學非常少。別誤會,就是幾年前而已。可能是因為小編是小縣城的,身邊很多人甚至都不知道有程式員這一職業。現在學java的人越來越多,學好java真的很有錢途嗎? 為什麼學java的人越來越多? 小編認為一個是 ...
  • 之前用過 Laravel,框架自帶的 artisan 命令,用得爽到爆。現在工作需要,要學習 FuelPHP,首先看到框架目錄結構,有 coposer.json 框架可以用 composer 管理,一定也有自己的命令工具。 對於新手來說,不妨先用命令自動生成文件,然後看這些生成的文件瞭解基本的 CR ...
  • 配置Windows2008伺服器openjdk時候出現這問題 原因是CLASSPATH配置出了問題,網上錯誤配置太多,一錯傳10,10傳百 CLASSPATH 外話,配置時候還需 JAVA_HOME Path ...
  • 代碼呢分兩部分,一部分是client端跟server端兩個。你只需要想辦法讓小伙伴運行你的client端腳本就OK啦。不過在此之前你一定要在你的電腦上運行server端哦~這樣子的話,client端會在你的小伙伴電腦上隨機生成一個密碼然後通過socket發給server端也就是發給你哦~ ...
  • 一、簡介: Zookeeper是一個分散式協調服務,提供的服務如下: 命名服務:類似於DNS,但僅對於節點 配置管理:服務配置信息的管理 集群管理:Dubbo使用Zookeeper實現服務治理 分散式鎖:選舉一個leader,這樣某一時刻只有一個服務在幹活,當leader出問題時釋放鎖,立即切到另一 ...
  • 本文要點剛要: (一)讀文本文件格式的數據函數:read_csv,read_table 1.讀不同分隔符的文本文件,用參數sep 2.讀無欄位名(表頭)的文本文件 ,用參數names 3.為文本文件制定索引,用index_col 4.跳行讀取文本文件,用skiprows 5.數據太大時需要逐塊讀取文 ...
  • 練習 8.1: 修改clock2來支持傳入參數作為埠號,然後寫一個clockwall的程式,這個程式可以同時與多個clock伺服器通信,從多伺服器中讀取時間,並且在一個表格中一次顯示所有服務傳回的結果,類似於你在某些辦公室里看到的時鐘牆。如果你有地理學上分散式的伺服器可以用的話,讓這些伺服器跑在不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...