Go 项目如何加载 config.json 格式的配置文件

不管是 web,api 项目,还是 jobs,微服务,控制台程序,我们都会用到配置文件,go 项目和很多 go 开源框架也并没有默认的标准配置文件。所以,我们自己需要实现,比如 xml,json,yaml等等格式配置文件的读取。这篇文章主要关注 json 格式的配置文件,网上能找到goconfig的相关仓库,比如:但是笔者还是打算自己写个简单易用的。
github.com/Unknwon/goconfig
github.com/spf13/viper

json 对象的基础类型是数字,字符串,布尔,其他复合类型是这些类型的组合,在我们的项目配置文件中 config.json,常常会包含数据库的配置,比如mysql,redis等等,看本网站的一份配置文件:
{
"mysql": {
"hrefs": "root:pwd123456@tcp(192.168.111.151:3306)/hrefs?charset=utf8",
"MaxIdleConns": 5,
"MaxOpenConns": 50,
"gorp": {
"trace-on": false
}
},
"redis": {
"host": "192.168.111.151",
"port": 6379
}
}

这份配置文件不是简单的key/value结果,而是包含复合类型,比如 redis 节点包含一个 json,这比单一的key/value结果要有更好的可读性,也更加优雅。我们的如何取值呢,先看看下面的方法,具体实现细节,我在最后边提供全部代码。
config.Int("mysql:MaxIdleConns", 5)
config.Int("mysql:MaxOpenConns", 50)
config.Bool("mysql:gorp:trace-on", false)
config.String("redis:host", "192.168.111.150") config.Int("redis:port", 6379))
每个方法都包含两个参数,第一个参数是key,比如mysql:gorp:trace-on,包含3个段,中间用冒号(:)隔开每个层级,配置文件在每次应用启动时将 config.json 按照上面的方式拼接 key 并保存在一个map[string]interface{}中,每次方法调用就直接从map对象中取。

我们现在知道如何从 config.json 中取到对应的值,现在要看看实现细节,这个包仅仅包含一个文件,没有依赖外部的包,你可以将其复制到你的项目直接使用,这个包核心代码就是loadConfigs方法,通过递归方式加载所有配置节点的最里层的值
package config

import (
"encoding/json"
"os"
)

const (
defaultConfigPath = "conf/config.json"
)

var (
raw map[string]interface{}
config = make(map[string]interface{})
)

func loadConfigs(prefix string, o interface{}) {
if m, ok := o.(map[string]interface{}); ok {
if len(prefix) > 0 {
prefix = prefix + ":"
}

for k, v := range m {
key := prefix + k
loadConfigs(key, v)
}
} else {
config[prefix] = o
}
}

func LoadConfigs() (err error) {
file, err := os.Open(defaultConfigPath)
if err != nil {
panic(err)
return
}

decoder := json.NewDecoder(file)
err = decoder.Decode(&raw)
loadConfigs("", raw)
return
}

func String(key string, defaultValue string) (value string) {
value, _ = getString(key, defaultValue)
return
}

func getString(key string, defaultValue string) (value string, found bool) {
v, found := config[key]
if !found {
return defaultValue, false
}
if value, ok := v.(string); ok {
return value, ok
} else {
return defaultValue, false
}
}

func Int(key string, defaultValue int) (value int) {
value, _ = getInt(key, defaultValue)
return
}

func getInt(key string, defaultValue int) (value int, found bool) {
v, found := config[key]
if !found {
value = defaultValue
return
}

if v64, found := v.(float64); found {
return int(v64), found
} else {
return defaultValue, false
}
}

func Bool(key string, defaultValue bool) (value bool) {
value, _ = getBool(key, defaultValue)
return
}

func getBool(key string, defaultValue bool) (value bool, found bool) {
v, found := config[key]
if !found {
value = defaultValue
return
}

if b, ok := v.(bool); ok {
return b, ok
} else if i, ok := v.(int); ok {
if i == 1 {
return true, true
} else if i == 0 {
return false, true
} else {
return defaultValue, false
}
} else {
return defaultValue, false
}
}

使用包的方式之前必须要初始化,初始化通常在应用程序启动时执行,你可把它放在main函数中,也可以放在你的启动初始化的任何包中。
config.LoadConfigs()

总结:对应一些中小型应用程序,微服务,jobs,这个配置包已经够用了,需要扩充数据类型,自行扩充也容易,比如数组、浮点类型等等。这篇文章不涉及分布式配置,读者需要可以自行实现,比如笔者曾经试过将配置写入consul,然后从consul读取,来支持微服务的分布式读取。
Posted by 何敏 on 2020-02-08 05:07:55