Go viper 包


viper 配置解决方案 Go配置第三方库  //github.com/spf13/viper
功能强大 读取配置 命令行 监听配置改变  多面手 在k8s docker 都有应用
viper功能以及特性
1 设置默认值
2 从json toml yaml hci和java属性配置文件
3 从环境变量env读取值
4 远程读取配置文件
5 key不区分大小写
配置文件
statetransfer
# Should a replica attempt to fix damaged blocks?
# In general, this should be set to true, setting to false will cause
# the replica to panic, and require a human's intervention to intervene and fix the corruption
recoverdamage: true
# The number of blocks to retrieve per sync request
blocksperrequest: 20   # The maximum number of state deltas to attempt to retrieve
# If more than this number of deltas is required to play the state up to date
# then instead the state will be flagged as invalid, and a full copy of the state
# will be retrieved instead
maxdeltas: 200   # Timeouts
timeout:
# How long may returning a single block take
singleblock: 2s
# How long may returning a single state delta take
singlestatedelta: 2s
# How long may transferring the complete state take
fullstate: 60s
peer:
abcd:  3322d
代码
viper.SetConfigName(cmdRoot)
viper.AddConfigPath("./")
err := viper.ReadInConfig()
if err != nil {
fmt.Println(fmt.Errorf("Fatal error when reading %s config file: %s\n", cmdRoot, err))
}
environment := viper.GetBool("security.enabled")
fmt.Println("environment:", environment)
fullstate := viper.GetString("statetransfer.timeout.fullstate")
fmt.Println("fullstate:", fullstate)
abcValue := viper.GetString("peer.abcd")
fmt.Println("abcdValuea is :", abcValue)

配置文件对应的结构体


type Configstruct {
PostgreSQL struct {
   DSNstring `mapstructure:"dsn"`
   Automigrate bool
   DB   string
}   `mapstructure:"postgresql"`
Redis struct {
   URLstring `mapstructure:"url"`
   Pool *redis.Pool
}
}
SetDefault 设置变量
viper.SetDefault("postgresql.dsn", "postgres://localhost/loraserver_as?sslmode=disable")
viper.SetDefault("postgresql.automigrate", true)
viper.SetDefault("redis.url", "redis://localhost:6379")

解析viper数据到结构体


var config Config
viper.Unmarshal(&config)
将设置的默认配置映射到 Config 结构体对应的变量中
首先viper的SetDefault方法 用 . 作为分隔符key 将其存储到 map[string]interface{}结构内
viper.Unmarshal(&config) 根据Config结构体中定义的 mapstructure 的 tag 值进行解析 其中mapstructure是个第三方包

viper实例

type config struct {
   v  *viper.Viper;
}
Yaml配置文件 config.yaml 用于测试内容
TimeStamp: "2018-07-16 10:23:19"
Author: "WZP"
PassWd: "Hello"
Information:
  Name: "Harry"
  Age: "37"
  Alise:
  - "Lion"
  - "NK"
  - "KaQS"
  Image: "/path/header.rpg"
  Public: false
Favorite:
  Sport:
  - "swimming"
  - "football"
  Music:
  - "zui xuan min zu feng"
  LuckyNumber: 99

读取 config.yaml 程序

func LoadConfigFromYaml (c *config) error  {
   c.v = viper.New();//设置配置文件的名字
   c.v.SetConfigName("config")
   //配置文件的路径 Linux环境$GOPATH 而windows为%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中读取配置

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
     //参考网址 //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;
}//把配置文件中的数据导入IO 再从IO中读取

从环境变量中读取配置

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"));
   c.v.SetEnvPrefix("DEV");  //SetEnvPrefix 设置一个环境变量的前缀名
   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
}
SetEnvPrefix 和 AutomaticEnv 和 BindEnv 搭配使用很方便 把当前程序的环境变量都设置为xx_  方便 管理
避免和其他环境变量冲突 方便读取

方便的替换符

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;
}//有时候需要去替换key中的某些字符 转化为对应的环境变量 将 . 替换为 _ 由获取api.version变成了api_version
SetEnvPrefix 和 SetEnvKeyReplacer 一起用的时候可能会混淆

Viper别名功能

//设置重载 和别名
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对应的值也会发生变化

Viper序列化和反序列化

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)
}//把配置反序列化到结构体  或 设置序列化为 想要的类型 yaml json等

从command读取配置

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")
   pflag.CommandLine.AddGoFlagSet(flag.CommandLine) //获取标准包的flag
   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"))
}//使用标准的flag 或 viper包 pflag 建议使用 pflag

Viper监听配置文件

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;
}// 非常实用的功能 修改配置文件不用重启服务
//配置文件被修改保存后 事先注册的watch函数就回被触发 只要 添加 watch 操作就ok了

Viper拷贝子分支

func TestSubConfig(t *testing.T)  {
   c := config{};
   LoadConfigFromYaml(&c);
   sc := c.v.Sub("information");
   sc.Set("age", 80);
   scs,_:=yaml.Marshal(sc.AllSettings())
   t.Log(string(scs));
   t.Logf("age: %d", c.v.GetInt("information.age"));
}//拷贝一个子分支 用途 是 可以复制一份配置 在修改拷贝的时候原配置不会被修改 修改的配置出现了问题 方便回滚

Viper获取配置项

//测试各种 get 类型
func TestGetValues(t *testing.T)  {
   c := &config{}
   if err := LoadConfigFromYaml(c); err != nil{
     t.Fatalf("%s: %s",t.Name(), err.Error());
   }
   if info := c.v.GetStringMap("information"); info != nil{
     t.Logf("%T", info);
   }
   if aliases := c.v.GetStringSlice("information.aliases"); aliases != nil{
     for _, a := range  aliases{
       t.Logf("%s",a);
     }
   }
   timeStamp := c.v.GetTime("timestamp");
   t.Logf("%s", timeStamp.String());
   if public := c.v.GetBool("information.public"); public{
     t.Logf("the information is public");
   }
   age := c.v.GetInt("information.age");
   t.Logf("%s age  is %d", c.v.GetString("information.name"), age);
}//如果 用Get获取的返回值 是interface{}类型 还要手动转化 可以直接指定类型去获取 方便快捷
viper 有从 etcd 提取配置 及自定义flage的功能

Viper其他应用

Unmarshal Struct 足够好用  这个配置文件和当前的新版本不匹配  实际生产中 讲究向下兼容
var yamlConfig =  YamlConfig{};
ycType := reflect.TypeOf(yamlConfig);
for i := 0 ; i < ycType.NumField();i++{
   name := ycType.Field(i).Name;
   element := reflect.ValueOf(yamlConfig).Field(i).Interface();
   if err = config.UnmarshalKey(name, element); err != nil{
      logger.Errorf("Error reading configuration:", err);
   }
}
从最外围的结构体中找出子元素的名称和interface
分别解析 某一项缺失了 也可以及时提醒用户 或者设置缺省配置 viper实例
/go/src/fetcher/main.go 内容
package main
import (
   "context"
   "fmt"
   "github.com/fsnotify/fsnotify"
   "github.com/spf13/pflag"
   "github.com/spf13/viper"
)
type CompanyInfomation struct {
   Name         string
   MarketCapitalization int64
   EmployeeNum      int64
   Department      []interface{}
   IsOpen        bool
}
type YamlSetting struct {
   TimeStamp     string
   Address      string
   Postcode      int64
   CompanyInfomation CompanyInfomation
}
func parseYaml(v *viper.Viper) {
   var yamlObj YamlSetting
   if err := v.Unmarshal(&yamlObj); err != nil {
     fmt.Printf("err:%s", err)
   }
   fmt.Println(yamlObj)
}
func main() {
   pflag.String("hostAddress", "127.0.0.1", "Server running address")
   pflag.Int64("port", 8080, "Server running port")
   pflag.Parse()
   viper.BindPFlags(pflag.CommandLine)
   fmt.Printf("hostAddress: %s, port: %s",viper.GetString("hostAddress"),viper.GetString("port"))
    v := viper.New()     // 读取yaml文件
   v.SetConfigName("config")//设置读取的配置文件  
   v.AddConfigPath("./config/")//添加读取的配置文件路径   
   v.AddConfigPath("%GOPATH/src/")//windows环境下%GOPATH,linux环境下为$GOPATH
   v.SetConfigType("yaml")//设置文件类型
   if err := v.ReadInConfig(); err != nil {
     fmt.Printf("err: %s\n", err)
   }
   fmt.Printf(`
     TimeStamp:%s
     CompanyInfomation.Name:%s
     CompanyInfomation.Department:%s
     `,
     v.Get("Timestamp"),
     v.Get("CompanyInfomation.Name"),
     v.Get("CompanyInfomation.Department"),
   )
   parseYaml(v)  //创建一个信道等待关闭(模拟服务器环境)
   ctx, _ := context.WithCancel(context.Background())
   //cancel可以关闭信道  //ctx, cancel := context.WithCancel(context.Background())
   //设置监听回调函数
   v.OnConfigChange(func(e fsnotify.Event) {
     fmt.Printf("config is change: %s \n", e.String())
   })   
   v.WatchConfig() //开始监听
   <-ctx.Done() //信道不会主动关闭,可以主动调用cancel关闭
}
配置文件/go/src/fetcher/config/config.yaml
TimeStamp: "2019-03-10 10:09:23"
Address: "ShangHai"
Postcode: 518000
CompanyInfomation:
  Name: "Sunny"
  MarketCapitalization: 50000000
  EmployeeNum: 200
  Department:
   - "Finance"
   - "Design"
   - "Program"
   - "Sales"
  IsOpen: false