Skip to content

Viper

ที่อยู่ repository: spf13/viper: Go configuration with fangs (github.com)

ที่อยู่เอกสาร: spf13/viper: Go configuration with fangs (github.com)

TIP

กำลังมีการอภิปรายเกี่ยวกับการเปลี่ยนผ่านไปยัง Viper2 ผู้ที่สนใจสามารถดูได้ที่: Viper2

การติดตั้ง

go get github.com/spf13/viper

บทนำ

Viper เป็นโซลูชันการกำหนดไฟล์การกำหนดค่าที่สมบูรณ์สำหรับแอปพลิเคชัน Go สามารถจัดการความต้องการและรูปแบบการกำหนดค่าได้เกือบทุกประเภท ช่วยในการจัดการไฟล์การกำหนดค่าของโปรเจกต์ และมีคุณสมบัติพิเศษดังนี้:

  • การตั้งค่าค่าเริ่มต้น
  • รองรับรูปแบบ JSON, TOML, YAML, HCL, envfile, Java properties
  • รองรับตรวจสอบและโหลดไฟล์การกำหนดค่าใหม่แบบเรียลไทม์
  • รองรับอ่านจากตัวแปรสภาพแวดล้อม
  • รองรับอ่านการกำหนดค่าจากระบบการกำหนดค่าระยะไกลและตรวจสอบการเปลี่ยนแปลง
  • รองรับอ่านจากเครื่องหมายคำสั่ง
  • รองรับอ่านจากบัฟเฟอร์
  • รองรับแสดงค่าที่ตั้งไว้

ทางอย่างเป็นทางการระบุว่า Viper สามารถตอบสนองความต้องการการกำหนดค่าของแอปพลิเคชันทั้งหมด ผู้พัฒนาเพียงมุ่งเน้นไปที่การสร้างแอปพลิเคชัน ให้ Viper รับผิดชอบการจัดการการกำหนดค่า โปรเจกต์ที่มีชื่อเสียงหลายแห่งใช้ Viper

DANGER

Viper ไม่รับผิดชอบการเข้ารหัสและถอดรหัสไฟล์การกำหนดค่า กล่าวคือจะไม่ทำการประมวลผลความปลอดภัยใดๆ กับไฟล์การกำหนดค่า

ลำดับการอ่าน

Viper ใช้ลำดับความสำคัญต่อไปนี้ในการอ่านการกำหนดค่า:

  1. ค่าที่ตั้งไว้ชัดเจน
  2. เครื่องหมายคำสั่ง
  3. ตัวแปรสภาพแวดล้อม
  4. ไฟล์การกำหนดค่า
  5. ที่เก็บคีย์-ค่า
  6. ค่าเริ่มต้น

TIP

คีย์ในการกำหนดค่าของ Viper ไม่แยกแยะตัวพิมพ์ใหญ่-เล็ก การอภิปรายในอนาคตอาจทำให้เป็นตัวเลือก

ค่าเริ่มต้น

ระบบการกำหนดค่าที่ดีควรสนับสนุนการตั้งค่าค่าเริ่มต้น แม้บางครั้งอาจไม่จำเป็น แต่จะมีประโยชน์มากเมื่อไม่ได้ตั้งค่าไฟล์การกำหนดค่า ด้านล่างนี้เป็นตัวอย่าง

go
viper.SetDefault("filePath","./dir/img/usr")
viper.SetDefault("root","123456")

อ่านไฟล์การกำหนดค่า

Viper ต้องการการกำหนดค่าน้อยมากเพื่อรู้ว่าต้องค้นหาไฟล์การกำหนดค่าที่ไหน Viper รองรับ JSON, TOML, YAML, HCL, INI, envfile และไฟล์ JavaProperties Viper สามารถค้นหาหลายพาธได้ แต่ปัจจุบันอินสแตนซ์ Viper เดียวรองรับไฟล์การกำหนดค่าเดียวเท่านั้น Viper ไม่ได้กำหนดพาธการค้นหาเป็นค่าเริ่มต้น ทิ้งการตัดสินใจเริ่มต้นไว้ให้แอปพลิเคชัน

ด้านล่างนี้เป็นตัวอย่างการใช้ Viper อ่านไฟล์การกำหนดค่า ไม่จำเป็นต้องระบุพาธแบบเต็ม แต่ควรให้ไฟล์การกำหนดค่าอย่างน้อยหนึ่งไฟล์เมื่อใช้งาน

go
func TestReadConfigFile(t *testing.T) {
   viper.SetConfigName("config.yml") // อ่านไฟล์การกำหนดค่าชื่อ config ไม่ได้ตั้งนามสกุลไฟล์เฉพาะ
   viper.SetConfigType("yaml")       // เมื่อไม่ได้ตั้งนามสกุลไฟล์เฉพาะ ต้องระบุประเภทไฟล์
   viper.AddConfigPath("./")         // ค้นหาในโฟลเดอร์ปัจจุบัน
   viper.AddConfigPath("$HOME/")     // ใช้ตัวแปร
   viper.AddConfigPath(".")          // ค้นหาในไดเรกทอรีทำงาน
   err := viper.ReadInConfig() // อ่านการกำหนดค่า
   if err != nil {
      log.Fatalln(err)
   }
}

ยังสามารถจัดการกรณีที่ไม่พบไฟล์การกำหนดค่าแยกต่างหาก

go
if err := viper.ReadInConfig(); err != nil {
  if _, ok := err.(viper.ConfigFileNotFoundError); ok {
    // ไม่พบไฟล์การกำหนดค่า
  } else {
    // ข้อผิดพลาดประเภทอื่น
  }
}

ต่อไปนี้เป็นฟังก์ชันทั้งหมดสำหรับเข้าถึงการกำหนดค่า

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}

เมื่อเข้าถึงการกำหนดค่าแบบซ้อน ใช้ตัวคั่น . เช่น:

{
  "server":{
    "database":{
      "url": "mysql...."
    }
  }
}

สามารถเข้าถึงแบบซ้อนได้ด้วย GetString("server.database.url")

เขียนไฟล์การกำหนดค่า

Viper ให้ชุดฟังก์ชันเพื่อให้นักพัฒนาสามารถเขียนการกำหนดค่าที่เก็บในรันไทม์ลงในไฟล์การกำหนดค่า

go
// WriteConfig เขียนการกำหนดค่าลงในไฟล์การกำหนดค่าเดิม หากไม่มีจะเกิดข้อผิดพลาด หากมีจะทับ
func WriteConfig() error { return v.WriteConfig() }

// SafeWriteConfig เขียนการกำหนดค่าอย่างปลอดภัยลงในไฟล์การกำหนดค่าเดิม หากไม่มีจะเขียน หากมีจะไม่ทับ
func SafeWriteConfig() error { return v.SafeWriteConfig() }

// WriteConfigAs เขียนการกำหนดค่าปัจจุบันลงในไฟล์ที่ระบุ หากไฟล์ไม่มีจะเกิดข้อผิดพลาด หากมีจะทับการกำหนดค่าเดิม
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }

// SafeWriteConfigAs หากไฟล์ที่ระบุมีอยู่ จะไม่ทับไฟล์การกำหนดค่าเดิม หากไฟล์มีอยู่จะเกิดข้อผิดพลาด
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }

ด้านล่างนี้เป็นตัวอย่างบางตัว:

go
func TestWritingConfig(t *testing.T) {
   viper.WriteConfig() // เขียนการกำหนดค่าลงในไฟล์การกำหนดค่าเดิม ไฟล์การกำหนดค่าเหล่านี้ควรถูกกำหนดไว้ล่วงหน้าด้วย 'viper.AddConfigPath()' และ 'viper.SetConfigName'
   viper.SafeWriteConfig()
   viper.WriteConfigAs("/path/to/my/.config")
   viper.SafeWriteConfigAs("/path/to/my/.config") // เนื่องจากไฟล์ที่ระบุมีอยู่ จะเกิดข้อผิดพลาด
   viper.SafeWriteConfigAs("/path/to/my/.other_config")
}

ตรวจสอบและโหลดการกำหนดค่าใหม่

Viper ช่วยให้แอปพลิเคชันอ่านไฟล์การกำหนดค่าแบบไดนามิกในรันไทม์ กล่าวคือไม่ต้องรีสตาร์ทแอปพลิเคชันก็สามารถทำให้การกำหนดค่าที่อัปเดตมีผล และไม่พลาดทุกรายละเอียดที่เปลี่ยนแปลง เพียงบอกอินสแตนซ์ Viper ให้ตรวจสอบการเปลี่ยนแปลงการกำหนดค่า หรือสามารถให้ฟังก์ชันแก่ viper เพื่อเรียกใช้ฟังก์ชันนั้นเมื่อมีการเปลี่ยนแปลง

go
func TestWatchingConfig(t *testing.T) {
  viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("ไฟล์การกำหนดค่าถูกเปลี่ยนแปลง:", e.Name)
  })
  viper.WatchConfig()
}

นามแฝง

go
func TestAliases(t *testing.T) {
   viper.RegisterAlias("a", "b")
   viper.Set("a", 1)
   viper.Set("b", 2) // จะทับการกำหนดค่าของ a
   fmt.Println(viper.GetInt("a"))
}

แยกโครงสร้างย่อย

ได้กล่าวไปแล้วว่าใช้ตัวคั่น . เพื่อเข้าถึงการกำหนดค่าแบบซ้อน ที่จริงแล้วยังสามารถใช้ฟังก์ชัน viper.Sub() เพื่อแยกโครงสร้างย่อยได้ ค่าที่ส่งกลับเป็นอินสแตนซ์ Viper ดังตัวอย่างต่อไปนี้:

yaml
cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
go
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // หากไม่มีอยู่จะส่งคืน nil
  panic("การกำหนดค่า cache1 ไม่มีอยู่")
}

ตั้งค่าตัวคั่นแบบซ้อน

เมื่อต้องการให้คีย์ที่ระบุมี . จำเป็นต้องระบุตัวคั่นอื่นด้วยตนเองเพื่อป้องกันการแยกวิเคราะห์ผิดพลาด เช่น:

go
viper.KeyDelimiter("/") // ตั้งค่าตัวคั่นเป็น /

การแยกวิเคราะห์อนุกรม

Viper ให้ฟังก์ชันสองฟังก์ชันเพื่อแยกวิเคราะห์การกำหนดค่าเป็นโครงสร้างหรือ map รองรับโครงสร้างแบบซ้อน:

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error
go
type config struct {
  Port int
  Name string
  PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
  t.Fatalf("ไม่สามารถแยกวิเคราะห์อนุกรมเป็นโครงสร้าง, %v", err)
}

การสร้างอนุกรม

สร้างอนุกรมการกำหนดค่าปัจจุบันเป็นสตริงในรูปแบบเฉพาะเพื่อเก็บในไฟล์การกำหนดค่า โดยปกติรองรับ JSON, TOML, YAML, HCL, envfile, Java properties

TIP

Viper รองรับรูปแบบการสร้างอนุกรมแบบกำหนดเอง Decoding custom formats with Viper - Márk Sági-Kazár (sagikazarmark.hu)

go
import (
  yaml "gopkg.in/yaml.v2"
  // ...
)

func yamlStringSettings() string {
  c := viper.AllSettings()
  bs, err := yaml.Marshal(c)
  if err != nil {
    log.Fatalf("ไม่สามารถสร้างอนุกรมการกำหนดค่าเป็น YAML: %v", err)
  }
  return string(bs)
}

หลายอินสแตนซ์

โดยปกติแล้วใช้อินสแตนซ์ทั่วโลกที่ Viper ให้มาก็เพียงพอแล้ว แต่เนื่องจากอินสแตนซ์เดียวสามารถแมปกับไฟล์การกำหนดค่าเดียวได้ สามารถสร้างหลายอินสแตนซ์เพื่อดำเนินการมากขึ้น เช่น:

go
x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

Golang by www.golangdev.cn edit