Go几种读取配置文件的方式

比较有名的方案有

使用viper管理配置

  • 支持多种配置文件格式,包括 JSON,TOML,YAML,HECL,envfile,甚至还包括Java properties
  • 支持为配置项设置默认值
  • 可以通过命令行参数覆盖指定的配置项
  • 支持参数别名

viper按照这个优先级(从高到低)获取配置项的取值:

  • explicit call to Set: 在代码逻辑中通过viper.Set()直接设置配置项的值
  • flag:命令行参数
  • env:环境变量
  • config:配置文件
  • key/value store:etcd或者consul
  • default:默认值

按照这个优先级(从高到低)获取配置项的取值:

  • explicit call to Set: 在代码逻辑中通过viper.Set()直接设置配置项的值
  • flag:命令行参数
  • env:环境变量
  • config:配置文件
  • key/value store:etcd或者consul
  • default:默认值

优先级

验证一下 viper.Set() 的优先级高于 配置文件

go 复制代码
package main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {

	loadConfig()
}

func loadConfig() {

	configVar := "shuang-config.yaml"
	configVar = "" // 这行如果注释掉,则从指定的configVar读取配置文件;否则就各种条件去找了

	viper.Set("Global.Source", "优先级最高")

	if configVar != "" {
		// SetConfigFile 显式定义配置文件的路径、名称和扩展名。
		// Viper 将使用它而不检查任何配置路径。
		viper.SetConfigFile(configVar)
	} else {

		// 如果没有显式指定配置文件,则

		// 会去下面的路径里找文件名`cui-config`的文件  name of config file (without extension)
		// 按照 []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}的顺序(居然还支持Java用的properties)
		viper.SetConfigName("cui-config")
		viper.AddConfigPath("/etc/myapp") // 找寻的路径
		viper.AddConfigPath("$HOME/.myapp/")
		viper.AddConfigPath(".")
	}

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config: %s", err))
	}

	fmt.Printf("到底用的是哪个配置文件: '%s'\n", viper.ConfigFileUsed())

	fmt.Printf("Global.Source这个字段的值为: '%s'\n", viper.GetString("global.source"))
}

输出:

go 复制代码
到底用的是哪个配置文件: '/Users/fliter/config-demo/cui-config.yaml'
Global.Source这个字段的值为: '优先级最高'

验证一下 环境变量 的优先级高于 配置文件

go 复制代码
package main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {

	loadConfig()
}

func loadConfig() {

	configVar := "shuang-config.yaml"
	configVar = "" // 这行如果注释掉,则从指定的configVar读取配置文件;否则就各种条件去找了

	viper.Set("Global.Source", "优先级最高")
	viper.AutomaticEnv()

	if configVar != "" {
		// SetConfigFile 显式定义配置文件的路径、名称和扩展名。
		// Viper 将使用它而不检查任何配置路径。
		viper.SetConfigFile(configVar)
	} else {

		// 如果没有显式指定配置文件,则

		// 会去下面的路径里找文件名`cui-config`的文件  name of config file (without extension)
		// 按照 []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}的顺序(居然还支持Java用的properties)
		viper.SetConfigName("cui-config")
		viper.AddConfigPath("/etc/myapp") // 找寻的路径
		viper.AddConfigPath("$HOME/.myapp/")
		viper.AddConfigPath(".")
	}

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config: %s", err))
	}

	fmt.Printf("到底用的是哪个配置文件: '%s'\n", viper.ConfigFileUsed())

	fmt.Printf("LANG这个字段的值为: '%s'\n", viper.GetString("LANG"))
}

viper.AutomaticEnv()会绑定所有环境变量,

如果只希望绑定特定的,可以使用SetEnvPrefix("global.source", "MYAPP_GLOAL_SOURCE"),注意这个函数不会自动加上MYAPP的前缀.


验证一下 命令行参数的优先级高于 配置文件

viper可以配合pflag来使用,pflag可以理解为标准库flag的一个增强版,viper可以绑定到pflag上

和cobra,viper一样,pflag也是同一作者的作品


验证一下 默认值的优先级低于 配置文件

go 复制代码
package main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {

	loadConfig()
}

func loadConfig() {

	configVar := "shuang-config.yaml"
	configVar = "" // 这行如果注释掉,则从指定的configVar读取配置文件;否则就各种条件去找了

	//viper.Set("Global.Source", "优先级最高")
	viper.AutomaticEnv()

	viper.SetDefault("Global.Source", "优先级最低")

	if configVar != "" {
		// SetConfigFile 显式定义配置文件的路径、名称和扩展名。
		// Viper 将使用它而不检查任何配置路径。
		viper.SetConfigFile(configVar)
	} else {

		// 如果没有显式指定配置文件,则

		// 会去下面的路径里找文件名`cui-config`的文件  name of config file (without extension)
		// 按照 []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}的顺序(居然还支持Java用的properties)
		viper.SetConfigName("cui-config")
		viper.AddConfigPath("/etc/myapp") // 找寻的路径
		viper.AddConfigPath("$HOME/.myapp/")
		viper.AddConfigPath(".")
	}

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config: %s", err))
	}

	fmt.Printf("到底用的是哪个配置文件: '%s'\n", viper.ConfigFileUsed())

	fmt.Printf("Global.Source这个字段的值为: '%s'\n", viper.GetString("Global.Source"))
}

Watch机制(配置更新后 热加载)

该机制可以监听配置文件的修改, 这样就实现了热加载,修改配置后,无需重启服务

  • 对于本地文件,是通过fsnotify实现的,然后通过一个回调函数去通知应用来reload;

  • 对于Remote KV Store,目前只支持etcd,做法比较ugly,(5秒钟)轮询一次 而不是watch api

go 复制代码
package main

import (
	"fmt"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

func main() {

	loadConfig()
}

func loadConfig() {

	configVar := "shuang-config.yaml"
	configVar = "" // 这行如果注释掉,则从指定的configVar读取配置文件;否则就各种条件去找了

	if configVar != "" {
		// SetConfigFile 显式定义配置文件的路径、名称和扩展名。
		// Viper 将使用它而不检查任何配置路径。
		viper.SetConfigFile(configVar)
	} else {

		// 如果没有显式指定配置文件,则

		// 会去下面的路径里找文件名`cui-config`的文件  name of config file (without extension)
		// 按照 []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}的顺序(居然还支持Java用的properties)
		viper.SetConfigName("cui-config")
		viper.AddConfigPath("/etc/myapp") // 找寻的路径
		viper.AddConfigPath("$HOME/.myapp/")
		viper.AddConfigPath(".")
	}

	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Printf("配置文件 %s 发生了更改!!! 最新的Global.Source这个字段的值为 %s:", e.Name, viper.GetString("Global.Source"))
	})

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config: %s", err))
	}

	fmt.Printf("到底用的是哪个配置文件: '%s'\n", viper.ConfigFileUsed())

	fmt.Printf("Global.Source这个字段的值为: '%s'\n", viper.GetString("Global.Source"))

	time.Sleep(10000e9)
}

Go viper 配置文件读取工具

动态获取配置文件(viper)


configor

Configor: 一个Golang配置工具,支持YAML,JSON,TOML,Shell环境,支持热加载

出自jinzhu大佬

GO 复制代码
package main

import (
	"fmt"

	"github.com/jinzhu/configor"
)

type Config struct {
	APPName string `default:"app name"`
	DB      struct {
		Name     string
		User     string `default:"root"`
		Password string `required:"true" env:"DBPassword"`
		Port     uint   `default:"3306"`
	}
	Contacts []struct {
		Name  string
		Email string `required:"true"`
	}
}

func main() {
	var conf = Config{}
	err := configor.Load(&conf, "config.yml")

	// err := configor.New(&configor.Config{Debug: true}).Load(&conf, "config.yml")  // 测试模式,也可以通过环境变量开启测试模式(CONFIGOR_DEBUG_MODE=true go run main.go ),这样就无需修改代码

	//err := configor.New(&configor.Config{Verbose: true}).Load(&conf, "config.yml") // 模式,也可以通过环境变量开启详细模式(CONFIGOR_VERBOSE_MODE=true go run main.go ),这样就无需修改代码
	if err != nil {
		panic(err)
	}
	fmt.Printf("%v \n", conf)
}

开启 测试模式 or 详细模式

既可以在代码中显式开启,如 err := configor.New(&configor.Config{Debug: true}).Load(&conf, "config.yml")

也可以通过环境变量开启,如 CONFIGOR_DEBUG_MODE=true go run main.go

加载多个配置文件

go 复制代码
// application.yml 的优先级 大于 database.json, 排在前面的配置文件优先级大于排在后的的配置
configor.Load(&Config, "application.yml", "database.json")

根据环境变量加载配置文件 or 从shell加载配置项

详细可参考 Golang Configor 配置文件工具

热更新

go 复制代码
package main

import (
	"fmt"
	"time"

	"github.com/jinzhu/configor"
)

type Config struct {
	APPName string `default:"app name"`
	DB      struct {
		Name     string
		User     string `default:"root"`
		Password string `required:"true" env:"DBPassword"`
		Port     uint   `default:"3306"`
	}
	Contacts []struct {
		Name  string
		Email string `required:"true"`
	}
}

func main() {
	var conf = Config{}

	// reload模式,可实现热加载

	err := configor.New(&configor.Config{
		AutoReload:         true,
		AutoReloadInterval: time.Second,
		AutoReloadCallback: func(config interface{}) {
			// config发生变化后出发什么操作
			fmt.Printf("配置文件发生了变更%#v\n", config)
		},
	}).Load(&conf, "config.yml")

	// 无reload模式
	//err := configor.Load(&conf, "config.yml")

	// err := configor.New(&configor.Config{Debug: true}).Load(&conf, "config.yml")  // 测试模式,也可以通过环境变量开启测试模式(CONFIGOR_DEBUG_MODE=true go run main.go ),这样就无需修改代码

	//err := configor.New(&configor.Config{Verbose: true}).Load(&conf, "config.yml") // 模式,也可以通过环境变量开启详细模式(CONFIGOR_VERBOSE_MODE=true go run main.go ),这样就无需修改代码
	if err != nil {
		panic(err)
	}
	fmt.Printf("%v \n", conf)

	time.Sleep(100000e9)
}

完整代码

相关推荐
snakeshe10108 小时前
深入理解 Java 注解:从原理到实战
后端
Lucaju8 小时前
吃透 Spring AI Alibaba 多智能体|四大协同模式+完整代码
后端
Nyarlathotep01138 小时前
Redis的对象(5):有序集合对象
redis·后端
Java水解8 小时前
Spring Boot 消息队列与异步处理
spring boot·后端
桦说编程8 小时前
AI 真的让写代码变快了吗?
后端
AskHarries9 小时前
openclaw升级和参数调整
后端·ai编程
creaDelight9 小时前
基于 Django 5.x 的全功能博客系统 DjangoBlog 深度解析
后端·python·django
Rust语言中文社区10 小时前
【Rust日报】 Danube Messaging - 云原生消息平台
开发语言·后端·rust
菜鸟程序员专写BUG11 小时前
SpringBoot 接口返回异常全集|JSON解析失败/响应乱码/状态码错误完美解决
spring boot·后端·json