16|配置和环境(下):配置服务中的设计思路

配置文件使用yaml格式,使用go-yaml进行解析。

// go-yaml
// 序列化
func Marshal(in interface{}) (out []byte, err error)
// 反序列化
func Unmarshal(in []byte, out interface{}) (err error)
// 严格反序列化
func UnmarshalStrict(in []byte, out interface{}) (err error)

配置文件的替换

配置文件中某些变量的值,希望是环境变量,则用env(XXXX)表示,比如:

mysql:
	hostname: 127.0.0.1
	username: yejianfang
	password: env(DB_PASSWORD)
	timeout: 1
	readtime: 2.3

这样读取配置文件后,将其中这种格式的变量值用环境变量替换。

配置项的解析

根据 path 来解析某个配置项,使用点号拆分,函数递归。


// 查找某个路径的配置项
func searchMap(source map[string]interface{}, path []string) interface{} {
   if len(path) == 0 {
      return source
   }

   // 判断是否有下个路径
   next, ok := source[path[0]]
   if ok {
      // 判断这个路径是否为1
      if len(path) == 1 {
         return next
      }

      // 判断下一个路径的类型
      switch next.(type) {
      case map[interface{}]interface{}:
         // 如果是interface的map,使用cast进行下value转换
         return searchMap(cast.ToStringMap(next), path[1:])
      case map[string]interface{}:
         // 如果是map[string],直接循环调用
         return searchMap(next.(map[string]interface{}), path[1:])
      default:
         // 否则的话,返回nil
         return nil
      }
   }
   return nil
}

// 通过path获取某个元素
func (conf *HadeConfig) find(key string) interface{} {
   ...
   return searchMap(conf.confMaps, strings.Split(key, conf.keyDelim))
}

配置服务的代码实现

配置服务如下:


// HadeConfig  表示hade框架的配置文件服务
type HadeConfig struct {
   c        framework.Container    // 容器
   folder   string                 // 文件夹
   keyDelim string                 // 路径的分隔符,默认为点
   ...
   envMaps  map[string]string      // 所有的环境变量
   confMaps map[string]interface{} // 配置文件结构,key为文件名
   confRaws map[string][]byte      // 配置文件的原始信息
}

实例化逻辑如下:


// NewHadeConfig 初始化Config方法
func NewHadeConfig(params ...interface{}) (interface{}, error) {
   container := params[0].(framework.Container)
   envFolder := params[1].(string)
   envMaps := params[2].(map[string]string)
   
   // 检查文件夹是否存在
   if _, err := os.Stat(envFolder); os.IsNotExist(err) {
      return nil, errors.New("folder " + envFolder + " not exist: " + err.Error())
   }
   // 实例化
   hadeConf := &HadeConfig{
      c:        container,
      folder:   envFolder,
      envMaps:  envMaps,
      confMaps: map[string]interface{}{},
      confRaws: map[string][]byte{},
      keyDelim: ".",
      lock:     sync.RWMutex{},
   }
   // 读取每个文件
   files, err := ioutil.ReadDir(envFolder)
   if err != nil {
      return nil, errors.WithStack(err)
   }
   for _, file := range files {
      fileName := file.Name()
      err := hadeConf.loadConfigFile(envFolder, fileName)
      if err != nil {
         log.Println(err)
         continue
      }
   }
   ...
   return hadeConf, nil
}

// 读取某个配置文件
func (conf *HadeConfig) loadConfigFile(folder string, file string) error {
   conf.lock.Lock()
   defer conf.lock.Unlock()
   //  判断文件是否以yaml或者yml作为后缀
   s := strings.Split(file, ".")
   if len(s) == 2 && (s[1] == "yaml" || s[1] == "yml") {
      name := s[0]
      // 读取文件内容
      bf, err := ioutil.ReadFile(filepath.Join(folder, file))
      if err != nil {
         return err
      }
      // 直接针对文本做环境变量的替换
      bf = replace(bf, conf.envMaps)
      // 解析对应的文件
      c := map[string]interface{}{}
      if err := yaml.Unmarshal(bf, &c); err != nil {
         return err
      }
      conf.confMaps[name] = c
      conf.confRaws[name] = bf
   }
   return nil
}

传给实例化函数的参数中包含路径和环境变量,逻辑中检查配置文件夹是否存在,然后读取文件夹中的每个以 yaml 或者 yml 后缀的文件;读取之后,先用 replace 对环境变量进行一次替换;替换之后使用 go-yaml,对文件进行解析。

【小结】:

  • 配置项作为一个服务,新建实例时根据路径读取配置,并修改app的配置
  • go-yaml用来读取yaml文件
  • go-fsnotiy用来监控配置文件的更改,可以用来做热更新(不过一般程序不会做热更新,每次配置的更改都要涉及到很多方面,热更新不稳定)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值