ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-02-23 23:33:23  阅读:286  来源: 互联网

标签:reflect return 配置文件 err vp field 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 还未完成

四、针对于嵌套型配置的解决方案

使用反射技术来完成。如果使用如下方案,那么在初始化viper的时候,无需设置 envpreifx 和 automaticenv

     pkg/setting/section.go ///
    // ReadSection 读取相关配置文件,并使用环境变量中存在的配置项 覆盖掉 配置文件中的相关配置项
    func (s *Setting) ReadSection(k string, v interface{}) error {
    
    	err := s.vp.UnmarshalKey(k, v)
    	if err != nil {
    		return err
    	}
    
    	fields := reflect.ValueOf(v).Elem()
    	fieldTypes := fields.Type()
    	fmt.Println(k)
    	for i := 0; i < fields.NumField(); i++ {
    
    		field := fields.Field(i)
    		fieldType := fieldTypes.Field(i)
    
    		uCaseKey := strings.ToUpper(k)
    		uCaseName := strings.ToUpper(fieldType.Name)
    
    		fmt.Println("  ", fieldType.Name, "-->", field.Interface())
    
    		envName := strings.Join([]string{uCaseKey, uCaseName}, "_")
    
    		if s.vp.Get(envName) != nil {
    			// 目前能自动适配Bool, int, uint, string, float32 五种基本类型;
    			// TODO 暂无法适配time.Duration类型
          // decode代码参考viper源码进行了修改
    			decode(envName, s.vp.Get(envName), field)
    		}
    	}
    
    	return nil
    }
    
    func decode(name string, input interface{}, field reflect.Value) error {
    
    	var err error
    	outputKind := getKind(field)
    	dataVal := reflect.Indirect(reflect.ValueOf(input))
    
    	switch outputKind {
    	case reflect.Bool:
    		b, err := strconv.ParseBool(dataVal.String())
    		if err == nil {
    			field.SetBool(b)
    		} else if dataVal.String() == "" {
    			field.SetBool(false)
    		} else {
    			return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
    		}
    	case reflect.String:
    		field.SetString(dataVal.String())
    	case reflect.Int:
    		str := dataVal.String()
    		if str == "" {
    			str = "0"
    		}
    		i, err := strconv.ParseInt(str, 0, field.Type().Bits())
    		if err == nil {
    			field.SetInt(i)
    		} else {
    			return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
    		}
    
    	case reflect.Uint:
    		str := dataVal.String()
    		if str == "" {
    			str = "0"
    		}
    
    		i, err := strconv.ParseUint(str, 0, field.Type().Bits())
    		if err == nil {
    			field.SetUint(i)
    		} else {
    			return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
    		}
    	case reflect.Float32:
    		str := dataVal.String()
    		if str == "" {
    			str = "0"
    		}
    		f, err := strconv.ParseFloat(str, field.Type().Bits())
    		if err == nil {
    			field.SetFloat(f)
    		} else {
    			return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
    		}
    	default:
    		// If we reached this point then we weren't able to decode it
    		return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
    	}
    
    	return err
    }
    
    func getKind(val reflect.Value) reflect.Kind {
    	kind := val.Kind()
    
    	switch {
    	case kind >= reflect.Int && kind <= reflect.Int64:
    		return reflect.Int
    	case kind >= reflect.Uint && kind <= reflect.Uint64:
    		return reflect.Uint
    	case kind >= reflect.Float32 && kind <= reflect.Float64:
    		return reflect.Float32
    	default:
    		return kind
    	}
    }
   

标签:reflect,return,配置文件,err,vp,field,viper,环境变量
来源: https://blog.csdn.net/zzzzz_zzz___rss/article/details/123101711

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有