什么是Viper
Viper是Go应用程序的完整配置解决方案,包括12-Factor应用程序。它旨在在应用程序中工作,并可以处理所有类型的配置需求和格式。它支持:
- 设置默认值
- 从JSON,TOML,YAML,HCL和Java属性配置文件中读取
- 实时观看和重新读取配置文件(可选)
- 从环境变量中读取
- 从远程配置系统(etcd或Consul)读取,并观察变化
- 从命令行标志读取
- 从缓冲区读取
- 设置显式值
Viper可以被认为是所有应用程序配置需求的注册表。
为什么要使用Viper
在构建现代应用程序时,您不必担心配置文件格式; 你可以专注于构建出色的软件。 Viper 可以做如下工作:
- 加载并解析JSON、TOML、YAML、HCL 或 Java properties 格式的配置文件
- 可以为各种配置项设置默认值
- 可以在命令行中指定配置项来覆盖配置值
- 提供了别名系统,可以不破坏现有代码来实现参数重命名
- 可以很容易地分辨出用户提供的命令行参数或配置文件与默认相同的区别
Viper读取配置信息的优先级顺序,从高到低,如下:
- 显式调用Set函数
- 命令行参数
- 环境变量
- 配置文件
- key/value 存储系统
- 默认值
- Viper 的配置项的key不区分大小写。
快速使用
安装:
go get github.com/spf13/viper
示例代码:
定义一个类型:
type config struct {
v *viper.Viper;
}
用于测试的Yaml配置文件 config.yaml
TimeStamp: "2021-07-21 15:23:19"
Author: "GK"
Information:
Name: "Harry"
Age: "37"
Alise:
- "xx"
- "NK"
- "GGGG"
Image: "/path/header.rpg"
Favorite:
Sport:
- "swimming"
- "baskteball"
LuckyNumber: 99
读取yaml配置文件
func LoadConfigFromYaml (c *config) error {
c.v = viper.New();
//设置配置文件的名字
c.v.SetConfigName("config")
//添加配置文件所在的路径,注意在Linux环境下%GOPATH要替换为$GOPATH
c.v.AddConfigPath("%GOPATH/src/")
c.v.AddConfigPath("./")
//设置配置文件类型
c.v.SetConfigType("yaml");
if err := c.v.ReadInConfig(); err != nil{
return err;
}
log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name"));
return nil;
}
注意:如果不用AddConfigPath去指定路径,它会在程序执行的目录去寻找config.yaml
从IO中读取配置
//由IO读取配置
func ReadConfigFormIo(c *config) error {
c.v = viper.New()
if f, err := os.Open("config.yaml"); err != nil{
log.Printf("filure: %s", err.Error());
return err;
}else {
confLength, _ :=f.Seek(0,2);
//注意,通常写c++的习惯害怕读取字符串的时候越界,都会多留出一个NULL在末尾,但是在这里不行,会报出如下错误:
//While parsing config: yaml: control characters are not allowed
//错误参考网址:https://stackoverflow.com/questions/33717799/go-yaml-control-characters-are-not-allowed-error
configData := make([]byte, confLength);
f.Seek(0, 0);
f.Read(configData);
log.Printf("%s\n", string(configData))
c.v.SetConfigType("yaml");
if err := c.v.ReadConfig(bytes.NewBuffer(configData)); err != nil{
log.Fatalf(err.Error());
}
}
log.Printf("age: %s, name: %s \n", c.v.Get("information.age"), c.v.Get("information.name"));
return nil;
}
从环境变量中读取配置
//读取本地的环境变量
func EnvConfigPrefix(c *config) error {
c.v = viper.New();
//BindEnv($1,$2)
// 如果只传入一个参数,则会提取指定的环境变量$1,如果设置了前缀,则会自动补全 前缀_$1
//如果传入两个参数则不会补全前缀,直接获取第二参数中传入的环境变量$2
os.Setenv("LOG_LEVEL", "INFO");
if nil == c.v.Get("LOG_LEVEL ") {
log.Printf("LOG_LEVEL is nil");
}else {
return ErrorNotMacth;
}
//必须要绑定后才能获取
c.v.BindEnv("LOG_LEVEL");
log.Printf("LOG_LEVEL is %s", os.Getenv("log_level"));
//会获取所有的环境变量,同时如果过设置了前缀则会自动补全前缀名
c.v.AutomaticEnv();
//环境变量前缀大小写不区分
os.Setenv("DEV_ADDONES","none");
log.Printf("DEV_ADDONES: %s", c.v.Get("dev_addones"));
//SetEnvPrefix会设置一个环境变量的前缀名
c.v.SetEnvPrefix("DEV");
os.Setenv("DEV_MODE", "true");
//此时会自动补全前缀,实际去获取的是DEV_DEV_MODE
if nil == c.v.Get("dev_mode"){
log.Printf("DEV_MODE is nil") ;
}else {
return ErrorNotMacth;
}
//此时我们直接指定了loglevel所对应的环境变量,则不会去补全前缀
c.v.BindEnv("loglevel", "LOG_LEVEL");
log.Printf("LOG_LEVEL: %s", c.v.Get("loglevel")) ;
return nil
}
方便的替换符
func EnvCongiReplacer(c *config, setPerfix bool) error {
c.v = viper.New();
c.v.AutomaticEnv();
c.v.SetEnvKeyReplacer(strings.NewReplacer(".","_"));
os.Setenv("API_VERSION","v0.1.0");
//Replacer和prefix一起使用可能会冲突,比如我下面的例子
//因为会自动补全前缀最终由获取API_VERSION变成API_API_VERSION
if setPerfix{ c.v.SetEnvPrefix("api");}
if s := c.v.Get("api.version"); s==nil{
return ErrorNoxExistKey
}else {
log.Printf("%s", c.v.Get("api.version"));
}
return nil;
}
别名功能
//设置重载 和别名
func SetAndAliases(c *config) error {
c.v = viper.New();
c.v.Set("Name","wzp");
c.v.RegisterAlias("id","Name");
c.v.Set("id","Mr.Wang");
//我们可以发现当别名对应的值修改之后,原本的key也发生变化
log.Printf("id %s, name %s",c.v.Get("id"),c.v.Get("name") );
return nil;
}
我们可以为key设置别名,当别名的值被重置后,原key对应的值也会发生变化。
序列化和反序列化
type favorite struct {
Sports []string;
Music []string;
LuckyNumber int;
}
type information struct {
Name string;
Age int;
Alise []string;
Image string;
Public bool
}
type YamlConfig struct {
TimeStamp string
Author string
PassWd string
Information information
Favorite favorite;
}
//将配置解析为Struct对象
func UmshalStruct(c *config) error {
LoadConfigFromYaml(c);
var cf YamlConfig
if err := c.v.Unmarshal(&cf); err != nil{
return err;
}
return nil;
}
func YamlStringSettings(c *config) string {
c.v = viper.New();
c.v.Set("name", "wzp");
c.v.Set("age", 18);
c.v.Set("aliase",[]string{"one","two","three"})
cf := c.v.AllSettings()
bs, err := yaml.Marshal(cf)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}
func JsonStringSettings(c *config) string {
c.v = viper.New();
c.v.Set("name", "wzp");
c.v.Set("age", 18);
c.v.Set("aliase",[]string{"one","two","three"})
cf := c.v.AllSettings()
bs, err := json.Marshal(cf)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}
从command Line中读取配置
func main() {
flag.String("mode","RUN","please input the mode: RUN or DEBUG");
pflag.Int("port",1080,"please input the listen port");
pflag.String("ip","127.0.0.1","please input the bind ip");
//获取标准包的flag
pflag.CommandLine.AddGoFlagSet(flag.CommandLine);
pflag.Parse();
//BindFlag
//在pflag.Init key后面使用
viper.BindPFlag("port", pflag.Lookup("port"));
log.Printf("set port: %d", viper.GetInt("port"));
viper.BindPFlags(pflag.CommandLine);
log.Printf("set ip: %s", viper.GetString("ip"));
}
监听配置文件
//监听配置文件的修改和变动
func WatchConfig(c *config) error {
if err := LoadConfigFromYaml(c); err !=nil{
return err;
}
ctx, cancel := context.WithCancel(context.Background());
c.v.WatchConfig()
//监听回调函数
watch := func(e fsnotify.Event) {
log.Printf("Config file is changed: %s \n", e.String())
cancel();
}
c.v.OnConfigChange(watch);
<-ctx.Done();
return nil;
}