最近在学习和试用一些开源的项目,主要是一些用来快速开发 Web 服务的脚手架之类的项目,一方面是为了能够了解 Go 开发项目的一般思路,另一方面是为了从这些开源框架中找到共同点,从而积累一些比较通用的用法。这篇主要是学习到了比较通用的配置文件读取的方法。

每个项目肯定有读取配置文件的模块,现在主流的配置文件应该就是 yaml 配置了,这也是我最喜欢的配置文件格式。经过比较,我发现 Go 里面主流的配置文件读取使用的是第三方库 Viper,当然,这个库支持各种配置文件格式,并不限于 yaml

命令行读取配置文件路径

一般项目运行的时候可以使用命令行参数来指定配置文件路径,这是很多命令行工具都具备的能力,比如下面这种:

./server -c ./conf/conf.yaml

关于如何定义命令行参数并从参数中获取值,之前我记录过一个开源的命令行库 cli,但是其实那个库比较适合命令比较多的命令行工具,而如果是命令需要读取的参数很少的时候,用 cli 感觉大材小用了。

当然,你也可以不用任何库来读取命令行参数,直接使用 args 也是可以的,但是我还是喜欢可以自己定义参数,这样不仅灵活,而且可以有一个帮助信息让用户知道有什么命令参数。

我发现了一个标准库可以做这种简单的命令行参数定义,就是 flag库,直接来一个例子:

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 定义命令行参数
    var (
        envConf  = flag.String("conf", "config/local.yml", "config path, eg: -conf=./config/local.yml")
        logLevel = flag.String("loglevel", "info", "log level, eg: -loglevel=debug")
        port     = flag.Int("port", 8080, "server port, eg: -port=8080")
    )

    // 解析命令行参数
    flag.Parse()

    // 打印解析结果
    fmt.Println("Config File Path:", *envConf)
    fmt.Println("Log Level:", *logLevel)
    fmt.Println("Server Port:", *port)
}

正如例子的注释,定义一个参数就很简单的一行命令,然后你可以定义多个参数,最后解析一下命令行就可以了。

相较于 cli 库,这个 flag 是一个标准库,而且具备的命令行功能也挺够用的,特别是对于一些非命令行工具的项目,又要使用到一些命令行的参数,那这个 flag 库肯定是首选。

Viper 的使用

直接看代码:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/viper"
)

func NewConfig(p string) *viper.Viper {
    // 这里是先读取环境变量的配置地址,环境变量的配置路径优先级更高
    envConf := os.Getenv("APP_CONF")
    if envConf == "" {
        envConf = p
    }
    fmt.Println("load conf file:", envConf)
    return getConfig(envConf)
}

func getConfig(path string) *viper.Viper {
    conf := viper.New()
    conf.SetConfigFile(path)
    err := conf.ReadInConfig()
    if err != nil {
        panic(err)
    }
    return conf
}

func main() {
    envConf := "config/local.yml"

    conf := NewConfig(envConf)

    version := conf.GetString("app.version")

    // 设置一个默认值,如果没有读取到配置文件的内容就使用默认值
    conf.SetDefault("app.key", "abc")
    key := conf.GetString("app.key")

    fmt.Println(version)
    fmt.Println(key)

}

我对这个库的几个认知(使用技巧):

  • 可以结合命令行工具使用,用命令行传入配置文件路径,也可以直接读取环境变量的配置文件路径
  • 读取配置项的时候如果没有读取到,则会得到一个该类型的默认值,所以有必要的时候,可以自行设置一个默认值,优先级为:配置值 > 默认值 > 类型默认值

总结

在项目里,如果对命令行参数有一定要求但是不需要复杂的子命令行,则可以使用标准库的 flag 来读取命令行参数;对于项目的配置文件的解析,可以使用 viper 库,支持各种常用的配置文件的解析。