Bootstrap

使用viper管理配置文件 并 实现使用环境变量覆盖配置文件

需求:
使用viper管理配置文件。项目部署后,通过修改环境变量,以达到使用环境变量中的配置 覆盖 config file中配置的目的。

一、使用viper来管理配置文件

// 代码结构

   ################# conf/app1.yaml #################
    TEST_ENV: 12345
    
    TEST_GROUPS:
      USER : user
      ROLE : role
      MANER : maner
     pkg/setting/section.go ///
    // 该文件中定义了与yaml配置文件中相对应的结构体
    
    package setting
    
    // TODO:mapstructure 是否可以使用‘.’来映射配置文件中的嵌套类型;如 TEST_GROUPS.USER; 【通过测试,该写法不可取】
    type Groups struct {
   
    	User  string `mapstructure:"USER"`
    	Role  string `mapstructure:"ROLE"`
    	Maner string `mapstructure:"MANER"`
    }
    
    type Config struct {
   
    	TestEnv string `mapstructure:"TEST_ENV"`
    }

     pkg/setting/setting.go ///
    // 在该文件中对配置文件进行初始化
    
    package setting
    
    type Setting struct {
   
    	vp *viper.Viper
    }
    
    var (
      ConfigSetting = &Config{
   }
      GroupsSetting = &Groups{
   }
    )
    
    
    func NewSetting() (*Setting, error) {
   
      vp := viper.New()
    	vp.SetConfigName("app")
    	vp.AddConfigPath("conf/")
    	vp.SetConfigType("yaml")
    	vp.AddConfigPath(".")
      // 设置为true 可自动获取相应的环境变量
    	vp.AutomaticEnv()
      // 设置与本项目相关的环境变量的前缀
      vp.SetEnvPrefix("app")
      err := vp.ReadInConfig()
    
      if err != nil {
   
    		return nil, err
    	}
      
      return &Setting{
   vp}, nil
    }
    
    func Setup() error {
   
      setting, err := NewSetting()
      if err != nil {
   
        return err
      }
      
      // 将配置文件按照 mapstructure 映射 读取到相应的变量中
      err = setting.vp.Unmarshal(&ConfigSetting)
      if err != nil {
   
        return err
      }
      
      // 将配置文件 按照 父节点读取到相应的struct中
      err = setting.vp.UnmarshalKey("groups", &GroupsSetting)
      if err != nil {
   
        return err
      }
    }

通过 EXPORT APP_TEST_ENV=000233设置环境变量,覆盖配置文件中的 TEST_ENV

     main.go ///
    package main
    
    func main() {
   
      
      setting.Setup()
      fmt.Println(setting.ConfigSetting)
      fmt.Println(setting.GroupsSetting)
    }
    
    /*
    the output is:
    &{000233}
    &{user role maner}
    */

实验验证成功!

二、对于非嵌套型配置

可直接通过 viper.Unmarshal(&ConfigSetting) 进行读取,方法执行时,如果 AutomaticEnv = true,且 viper.SetEnvPrefix(“app”) 设置成功。那么在加载配置文件的时候,会自动使用env中的配置将配置文件中的配置覆盖掉;其配置文件与env中项的对应关系为:

APP_TEST_ENV(环境变量中的配置,必须为全部大写) <=> Test_Env(此处为配置文件中的项,大小写不敏感)

总结:
在非嵌套型的配置中,可以通过设置 EnvPrefix 和 AutomaticEnv 来自动使用env覆盖config中的配置项

三、对于嵌套型配置

通过 viper.UnmarshalKey(“groups”, &GroupsSetting) 将配置文件中 groups 下的全部配置加载到 GroupsSetting 中。

若环境变量中存在了 APP_GROUPS = xxx ;此时会出现报错,因为上述方法执行的时候,会将环境变量中的配置读取出来,并替换掉配置文件中groups下的全部数据。(注意,通过env读取到的groups数据,全部为string类型的;而在配置文件中读取的数据为map型的)此时报错信息为:

 '' expected a map, got 'string'

对于该问题,可以通过自定义 DecoderConfigOption 钩子函数来解决问题。// TODO 还未完成

-------------------------------------补充Hook函数解决方案----------------------------------------

3-1、首先对UnmarshalKey源码进行分析

源码分析

// 1. 首先,调用UnmarshalKey("groups", GroupsSetting)
err = setting.vp.UnmarshalKey("groups", GroupsSetting)

// 2. 接下来 UnmarshalKey 函数的实现为
func (v *Viper) UnmarshalKey(key string, rawVal interface{
   }, opts ...DecoderConfigOption) error {
   
	return decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
}

// 2.1 通过 v.Get(key) 来构造decode函数的第一个参数
func (v *Viper) Get(key string) interface{
   } {
   
	lcaseKey := strings.ToLower(key)
  // 注意 此处find函数并未拿出来,但在根据key读取数据的时候,优先级为 flag > env > config file > key/value store.
  // 因此,若配置同时存在于env中,那么会直接覆盖掉配置文件中的配置
	val := v.find(lcaseKey, true)
	if val == nil {
   
		return nil
	}
  ...
}

// 2.1.1 find(key string, flagDefault bool) 核心函数, 按照如下优先级读取配置
// overrides > flag > env > config > key/value store > default
func (v *Viper) find(lcaseKey string, flagDefault bool) interface{
   
;