配置文件使用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用来监控配置文件的更改,可以用来做热更新(不过一般程序不会做热更新,每次配置的更改都要涉及到很多方面,热更新不稳定)