器 | Go四种方式读取环境配置文件

3,800 阅读3分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

配置文件在一个项目中至关重要,如何高效的读取配置尤为关键。如果不采用任何三方库,最常用的方式是通过文件I/O获取文件的内容,但我毕竟只是想要读取个配置文件,这还要进行文件操作,未免有些造轮子了。而且,文件的格式多种多样,如何格式的读取文件内容和根据不同的环境读取也是一个不小的挑战。这篇文章我介绍四种读取配置文件的方式,请按照各自的需求选择。

一、四种读取方式简介

比较出众的有两种,包括ViperGoDotEnv,这两种方式受众相当的广泛,ViperGithub的star最多,文档更加全面,GoDotEnv次之。另外还有两个比较小众轻量级的,包括gonfiggo-akka。分别的介绍请参考Github介绍。

go-akka

Currently Not Available !!!

Github传送带

godotenv(Available)

A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)

Github传送带

gonfig

gonfig is a lightweight Golang package for intergrating both JSON configs and enviornment variables into one config object.

Github传送带

viper(Available,Most stars)

Viper is heading towards v2 and we would love to hear what you would like to see in it.

Github传送带

二、go-akka实践

配置文件config.yaml

port: 8081
debug: true

解析配置config.go

import "github.com/go-akka/configuration"type Config struct {
    Port  uint16 `yaml:"port"`
    Debug bool   `yaml:"debug"`
    Raw   *configuration.Config
}
​
func (c *Config) parseConfig(path string) *Config {
    c.Raw = configuration.LoadConfig(path)
    c.Port = uint16(c.Raw.GetInt32("port", 8008))
    c.Debug = c.Raw.GetBoolean("debug", false)
    return c
}

测试config_test.go

func TestConfig(t *testing.T) {
    assert.NotPanics(t, func() {
        cfg := new(Config).parseConfig("./cfg.yaml")
        fmt.Printf(" port=%d,debug=%t", cfg.Port, cfg.Debug)
    })
}

三、godotenv实践

测试文件 .env

Key1=value1
Key2 =value2

解析配置config.go

func init() {
    if err := godotenv.Load("./.env"); err != nil {
        log.Fatal("Error loading .env file")
    }
}
​
func ReadEnv() {
    value1 := os.Getenv("Key1")
    value2 := os.Getenv("Key2")
    value3 := os.Getenv("Key3")
    if value1 == "" || value2 == "" {
        panic("load error")
    }
    fmt.Printf("key1=%s,key2=%s,key3=%s", value1, value2, value3)
}
​

测试文件config_test.go

func TestEnv(t *testing.T) {
    assert.NotPanics(t, func() {
        ReadEnv()
    })
}

四、gonfig 实践

配置文件

dev_config.json

{
    "DB_USERNAME": "test",
    "DB_PASSWORD": "test",
    "DB_PORT": "3306",
    "DB_HOST": "127.0.0.1",
    "DB_NAME": "test"
}

prd_config.json

{
    "DB_USERNAME": "prd_test",
    "DB_PASSWORD": "prd_test",
    "DB_PORT": "3306",
    "DB_HOST": "127.0.0.1",
    "DB_NAME": "prd_test"
}

配置解析config.go

type Configuration struct {
    DB_USERNAME string
    DB_PASSWORD string
    DB_PORT     string
    DB_HOST     string
    DB_NAME     string
}
​
func ParseConfig(params ...string) Configuration {
    configuration := Configuration{}
    env := "dev"
    if len(params) > 0 {
        env = params[0]
    }
    fName := fmt.Sprintf("./%s_config.json", env)
    if err := gonfig.GetConf(fName, &configuration); err != nil {
        panic(err)
    }
    return configuration
}

测试文件config_test.go

func TestConfig(t *testing.T) {
    assert.NotPanics(t, func() {
        fmt.Println("Dev Conf :")
        cfg := ParseConfig()
        fmt.Printf("userName=%s,password=%s,port=%s,host=%s,name=%s \n", cfg.DB_USERNAME, cfg.DB_PASSWORD, cfg.DB_PORT, cfg.DB_HOST, cfg.DB_NAME)
        cfg = ParseConfig("prd")
        fmt.Printf("userName=%s,password=%s,port=%s,host=%s,name=%s \n", cfg.DB_USERNAME, cfg.DB_PASSWORD, cfg.DB_PORT, cfg.DB_HOST, cfg.DB_NAME)
    })
​
}

五、viper实践

配置文件config.yml

server:
  port: 8080

database:
  dbname:
  dbuser: "dbuser"
  dbpassword: "dbpassword"

EXAMPLE_VAR: "variable from config.yml"
EXAMPLE_PATH: "path from config.yml"

解析配置config.go

type Configurations struct {
    Server       ServerConfigurations
    Database     DatabaseConfigurations
    EXAMPLE_PATH string
    EXAMPLE_VAR  string
}
​
// ServerConfigurations exported
type ServerConfigurations struct {
    Port int
}
​
// DatabaseConfigurations exported
type DatabaseConfigurations struct {
    DBName     string
    DBUser     string
    DBPassword string
}
​
func readConfig() {
    viper.SetConfigName("config")
    viper.AddConfigPath(".")
    viper.AutomaticEnv()
    viper.SetConfigType("yml")
    var configuration Configurations
    if err := viper.ReadInConfig(); err != nil {
        fmt.Printf("Error reading config file, %s", err)
    }
    viper.SetDefault("database.dbname", "test_db")
    err := viper.Unmarshal(&configuration)
    if err != nil {
        fmt.Printf("Unable to decode into struct, %v", err)
    }
    fmt.Printf("reading using model:\n database=%s,port=%d,path=%s,var=%s \n",
        configuration.Database.DBName,
        configuration.Server.Port,
        configuration.EXAMPLE_PATH,
        configuration.EXAMPLE_VAR)
    fmt.Printf("reading without model:\n database=%s,port=%d,path=%s,var=%s \n",
        viper.GetString("database.dbname"),
        viper.GetInt("server.port"),
        viper.GetString("EXAMPLE_PATH"),
        viper.GetString("EXAMPLE_VAR"))
}

测试文件config_test.go

func TestConfig(t *testing.T) {
    assert.NotPanics(t, func() {
        readConfig()
    })
}

六、总结

四种读取环境配置的实践按照各自情况挑选吧,生产环境建议还是使用vipergodotenv,毕竟千锤百炼要可靠很多,还有友情提示永远不要忘记在.gitignore 中包含你的环境变量文件

github repo


哥佬倌,莫慌到走!觉好留个赞,探讨上评论。欢迎关注实践go语言专栏gothing,手把手一起学习go。也欢迎关注我,一定做一个长更的好男人。