在项目开发中,配置文件的合理的获取和更新是一个基本的功能。项目上线后修改配置项,修改配置之后如果还重新启动项目才能生效,这样效果并不好。为了能够在不影响项目正常运行的情况下修改配置项,就需要用到配置热更新。例如:上线后想要修改日志的级别,可以直接修改配置文件,项目自动扫描配置文件,如果发现文件被修改,则重新获取配置信息。读取配置和配置热更新有两种方式:

  • 方式一:调用github.com/fsnotify/fsnotify包,监控配置变化,并使用其他的包来读取文件
  • 方式二:调用github.com/spf13/viper包,这个包是由Steve Francia开发,提供了监控和配置的设置、获取的方法

一、方式一的使用

  • main.go文件
package main

import (
    "file-store/handler"
    "file-store/models"
    "file-store/utils"
    "fmt"
    "log"
    "net/http"

    "github.com/fsnotify/fsnotify"
)

func main() {
    configPath := "/Users/apple/workplace/file-store/config/config.yaml"
    configDir := "/Users/apple/workplace/file-store/config"

    // 加载配置文件
    _ = utils.InitConfig(configPath, "config")
    fmt.Printf("配置为:%s\n", utils.Conf.Token.Salt)

    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    done := make(chan bool)
    go func() {
        for {
            select {
            case event := <-watcher.Events:
                if event.Op&fsnotify.Write == fsnotify.Write {
                    _ = utils.InitConfig(configPath, "config")
                    fmt.Printf("更新配置为:%s\n", utils.Conf.Token.Salt)
                }
            case err := <-watcher.Errors:
                log.Println("error:", err)
            }
        }
    }()
    fmt.Printf("数据库配置为:%s, %s\n", utils.Conf.Mysql.Drive, utils.Conf.Mysql.Address)
    // 数据库操作
    models.MysqlInit(utils.Conf.Mysql.Drive, utils.Conf.Mysql.Address)
    // 监听端口
    err = http.ListenAndServe("127.0.0.1:8000", nil)
    if err != nil {
        fmt.Printf("Failed to start server, err %s", err.Error())
    }

    // 监控文件
    err = watcher.Add(configPath)
    if err != nil {
        log.Fatal(err)
    }
    // 监控文件夹
    err = watcher.Add(configDir)
    if err != nil {
        log.Fatal(err)
    }
    <-done
}
  • config.go:该文件用于获取配置信息,读取配置用到两个包:ioutil读取配置,gopkg.in/yaml.v2用于数据的转换
package utils

import (
    "fmt"
    "io/ioutil"
    "sync"

    "gopkg.in/yaml.v2"
)

type Config struct {
    File  *File  `yaml:"file"`
    Mysql *Mysql `yaml:"mysql"`
    Token *Token `yaml:"token"`
}

type File struct {
    Path string `yaml:"path"`
}

type Mysql struct {
    Drive   string `yaml:"drive"`
    Address string `yaml:"address"`
}

type Token struct {
    Salt  string `yaml:"salt"`
    Issue string `yaml:"issue"`
}

var Conf *Config

// InitConfig 读取yaml配置文件
func InitConfig(configPath, configName string) error {
    var locker = new(sync.RWMutex)
    yamlFile, err := ioutil.ReadFile(configPath)
    if err != nil {
        panic(err)
    }
    locker.Lock()
    err1 := yaml.Unmarshal(yamlFile, &Conf)
    if err1 != nil {
        panic(err)
    }
    locker.Unlock()
    fmt.Println(Conf.Token.Salt)
    return nil
}

二、方式二的使用

  • config.go文件
package utils

import (
    "fmt"

    "github.com/spf13/viper"
)

type Config struct {
    File  *File  `yaml:"file"`
    Mysql *Mysql `yaml:"mysql"`
    Token *Token `yaml:"token"`
}

type File struct {
    Path string `yaml:"path"`
}

type Mysql struct {
    Drive   string `yaml:"drive"`
    Address string `yaml:"address"`
}

type Token struct {
    Salt  string `yaml:"salt"`
    Issue string `yaml:"issue"`
}

// 全局配置
var config = new(Config)

// InitConfig 读取yaml配置文件
func InitConfig(configPath, configName, configType string) error {
    viper.SetConfigName(configName) // 配置文件名
    viper.SetConfigType(configType) // 配置文件类型,例如:toml、yaml等
    viper.AddConfigPath(configPath) // 查找配置文件所在的路径,多次调用可以添加多个配置文件搜索的目录
    // 读取配置文件配置,并处理错误
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            return err
        }
    }
    // 监控配置文件变化
    viper.WatchConfig()
    viper.Unmarshal(config)
    if err := validateConfig(config); err != nil {
        return err
    }
    return nil
}

// 获取全局配置
func GetConfig() *Config {
    return config
}

// validateConfig:校验配置信息
func validateConfig(conf *Config) error {
    var (
        file    = conf.File.Path
        drive   = conf.Mysql.Drive
        address = conf.Mysql.Address
        salt    = conf.Token.Salt
        issue   = conf.Token.Issue
    )
    if file == "" {
        return fmt.Errorf("invalid file path: %s\n", file)
    }
    if drive == "" {
        return fmt.Errorf("invalid drive: %s\n", drive)
    }
    if address == "" {
        return fmt.Errorf("invalid address: %s\n", address)
    }
    if salt == "" {
        return fmt.Errorf("invalid salt: %s\n", salt)
    }
    if issue == "" {
        return fmt.Errorf("invalid issue: %s\n", issue)
    }
    return nil
}
  • main.go文件
package main

import (
    "file-store/handler"
    "file-store/models"
    "file-store/utils"
    "fmt"
    "net/http"
)

func main() {
    configPath := "/Users/apple/workplace/file-store/config"
    // 配置初始化
    err := utils.InitConfig(configPath, "config", "yaml")
    if err != nil {
        fmt.Printf("Failed to init config, err is %s\n", err)
    }
    // 获取全局配置
    conf := utils.GetConfig()
    fmt.Println(conf.File.Path)
    // 数据库操作
    models.MysqlInit(conf.Mysql.Drive, conf.Mysql.Address)
    // 监听端口
    err = http.ListenAndServe("127.0.0.1:8000", nil)
    if err != nil {
        fmt.Printf("Failed to start server, err %s", err.Error())
    }
}

苦心僧
18 声望2 粉丝