go-yaml 就是非常通用的一个 Go 解析库
    https://github.com/go-yaml/yaml
    Marshal 表示序列化一个结构成为 YAML 格式;
    Unmarshal 表示反序列化一个 YAML 格式文本成为一个结构;
    还有一个 UnmarshalStrict 函数,表示严格反序列化,比如如果 YAML 格式文件中包含重复 key 的字段,那么使用 UnmarshalStrict 函数反序列化会出现错误。
    替换配置

    1. // replace 表示使用环境变量maps替换context中的env(xxx)的环境变量
    2. func replace(content []byte, maps map[string]string) []byte {
    3. if maps == nil {
    4. return content
    5. }
    6. // 直接使用ReplaceAll替换。这个性能可能不是最优,但是配置文件加载,频率是比较低的,可以接受
    7. for key, val := range maps {
    8. reKey := "env(" + key + ")"
    9. content = bytes.ReplaceAll(content, []byte(reKey), []byte(val))
    10. }
    11. return content
    12. }

    函数递归逻辑

    1. // 查找某个路径的配置项
    2. func searchMap(source map[string]interface{}, path []string) interface{} {
    3. if len(path) == 0 {
    4. return source
    5. }
    6. // 判断是否有下个路径
    7. next, ok := source[path[0]]
    8. if ok {
    9. // 判断这个路径是否为1
    10. if len(path) == 1 {
    11. return next
    12. }
    13. // 判断下一个路径的类型
    14. switch next.(type) {
    15. case map[interface{}]interface{}:
    16. // 如果是interface的map,使用cast进行下value转换
    17. return searchMap(cast.ToStringMap(next), path[1:])
    18. case map[string]interface{}:
    19. // 如果是map[string],直接循环调用
    20. return searchMap(next.(map[string]interface{}), path[1:])
    21. default:
    22. // 否则的话,返回nil
    23. return nil
    24. }
    25. }
    26. return nil
    27. }
    28. // 通过path获取某个元素
    29. func (conf *HadeConfig) find(key string) interface{} {
    30. ...
    31. return searchMap(conf.confMaps, strings.Split(key, conf.keyDelim))
    32. }

    读取ymal文件

    1. // NewHadeConfig 初始化Config方法
    2. func NewHadeConfig(params ...interface{}) (interface{}, error) {
    3. container := params[0].(framework.Container)
    4. envFolder := params[1].(string)
    5. envMaps := params[2].(map[string]string)
    6. // 检查文件夹是否存在
    7. if _, err := os.Stat(envFolder); os.IsNotExist(err) {
    8. return nil, errors.New("folder " + envFolder + " not exist: " + err.Error())
    9. }
    10. // 实例化
    11. hadeConf := &HadeConfig{
    12. c: container,
    13. folder: envFolder,
    14. envMaps: envMaps,
    15. confMaps: map[string]interface{}{},
    16. confRaws: map[string][]byte{},
    17. keyDelim: ".",
    18. lock: sync.RWMutex{},
    19. }
    20. // 读取每个文件
    21. files, err := ioutil.ReadDir(envFolder)
    22. if err != nil {
    23. return nil, errors.WithStack(err)
    24. }
    25. for _, file := range files {
    26. fileName := file.Name()
    27. err := hadeConf.loadConfigFile(envFolder, fileName)
    28. if err != nil {
    29. log.Println(err)
    30. continue
    31. }
    32. }
    33. ...
    34. return hadeConf, nil
    35. }
    36. // 读取某个配置文件
    37. func (conf *HadeConfig) loadConfigFile(folder string, file string) error {
    38. conf.lock.Lock()
    39. defer conf.lock.Unlock()
    40. // 判断文件是否以yaml或者yml作为后缀
    41. s := strings.Split(file, ".")
    42. if len(s) == 2 && (s[1] == "yaml" || s[1] == "yml") {
    43. name := s[0]
    44. // 读取文件内容
    45. bf, err := ioutil.ReadFile(filepath.Join(folder, file))
    46. if err != nil {
    47. return err
    48. }
    49. // 直接针对文本做环境变量的替换
    50. bf = replace(bf, conf.envMaps)
    51. // 解析对应的文件
    52. c := map[string]interface{}{}
    53. if err := yaml.Unmarshal(bf, &c); err != nil {
    54. return err
    55. }
    56. conf.confMaps[name] = c
    57. conf.confRaws[name] = bf
    58. }
    59. return nil
    60. }

    逻辑非常清晰。先检查配置文件夹是否存在,然后读取文件夹中的每个以 yaml 或者 yml 后缀的文件;读取之后,先用 replace 对环境变量进行一次替换;替换之后使用 go-yaml,对文件进行解析。
    配置文件热更新

    这个时候,是否需要重新启动一次程序再加载一次配置文件呢?这当然是没有问题的,但是更为强大的是,我们可以自动监控配置文件目录下的所有文件,当配置文件有修改和更新的时候,能自动更新程序中的配置文件信息,也就是实现配置文件热更新。这个热更新看起来很麻烦,其实在 Golang 中是非常简单的事情。我们使用 fsnotify 库能很方便对一个文件夹进行监控,当文件夹中有文件增 / 删 / 改的时候,会通过 channel 进行事件回调。这个库的使用方式很简单。大致思路就是先使用 NewWatcher 创建一个监控器 watcher,然后使用 Add 来监控某个文件夹,通过 watcher 设置的 events 来判断文件是否有变化,如果有变化,就进行对应的操作,比如更新内存中配置服务存储的 map 结构

    1. // NewHadeConfig 初始化Config方法
    2. func NewHadeConfig(params ...interface{}) (interface{}, error) {
    3. ...
    4. // 监控文件夹文件
    5. watch, err := fsnotify.NewWatcher()
    6. if err != nil {
    7. return nil, err
    8. }
    9. err = watch.Add(envFolder)
    10. if err != nil {
    11. return nil, err
    12. }
    13. go func() {
    14. defer func() {
    15. if err := recover(); err != nil {
    16. fmt.Println(err)
    17. }
    18. }()
    19. for {
    20. select {
    21. case ev := <-watch.Events:
    22. {
    23. //判断事件发生的类型,如下5种
    24. // Create 创建
    25. // Write 写入
    26. // Remove 删除
    27. path, _ := filepath.Abs(ev.Name)
    28. index := strings.LastIndex(path, string(os.PathSeparator))
    29. folder := path[:index]
    30. fileName := path[index+1:]
    31. if ev.Op&fsnotify.Create == fsnotify.Create {
    32. log.Println("创建文件 : ", ev.Name)
    33. hadeConf.loadConfigFile(folder, fileName)
    34. }
    35. if ev.Op&fsnotify.Write == fsnotify.Write {
    36. log.Println("写入文件 : ", ev.Name)
    37. hadeConf.loadConfigFile(folder, fileName)
    38. }
    39. if ev.Op&fsnotify.Remove == fsnotify.Remove {
    40. log.Println("删除文件 : ", ev.Name)
    41. hadeConf.removeConfigFile(folder, fileName)
    42. }
    43. }
    44. case err := <-watch.Errors:
    45. {
    46. log.Println("error : ", err)
    47. return
    48. }
    49. }
    50. }
    51. }()
    52. return hadeConf, nil
    53. }
    1. // 删除文件的操作
    2. func (conf *HadeConfig) removeConfigFile(folder string, file string) error {
    3. conf.lock.Lock()
    4. defer conf.lock.Unlock()
    5. s := strings.Split(file, ".")
    6. // 只有yaml或者yml后缀才执行
    7. if len(s) == 2 && (s[1] == "yaml" || s[1] == "yml") {
    8. name := s[0]
    9. // 删除内存中对应的key
    10. delete(conf.confRaws, name)
    11. delete(conf.confMaps, name)
    12. }
    13. return nil
    14. }

    这里注意下,由于在运行时增加了对 confMaps 的写操作,所以需要对 confMaps 进行锁设置,以防止在写 confMaps 的时候,读操作进入读取了错误信息。

    1. // HadeConfig 表示hade框架的配置文件服务
    2. type HadeConfig struct {
    3. ...
    4. lock sync.RWMutex // 配置文件读写锁
    5. ...
    6. }
    7. // 读取某个配置文件
    8. func (conf *HadeConfig) loadConfigFile(folder string, file string) error {
    9. conf.lock.Lock() defer conf.lock.Unlock() ...
    10. }
    11. // 通过path来获取某个配置项
    12. func (conf *HadeConfig) find(key string) interface{} {
    13. conf.lock.RLock()
    14. defer conf.lock.RUnlock() ...
    15. }