Config
config - 简洁、功能完善的 Go 应用程序配置管理工具库
功能简介
- 支持多种格式:
JSON(默认),JSON5,INI,Properties,YAML,TOML,HCL,ENV,FlagsJSON内容支持注释,可以设置解析时清除注释- 其他驱动都是按需使用,不使用的不会加载编译到应用中
- 支持多个文件、多数据加载
- 支持从 OS ENV 变量数据加载配置
- 支持从远程 URL 加载配置数据
- 支持从命令行参数(
flags)设置配置数据 - 支持在配置数据更改时触发事件
- 可用事件:
set.value,set.data,load.data,clean.data,reload.data
- 可用事件:
- 支持数据覆盖合并,加载多份数据时将按key自动合并
- 支持将全部或部分配置数据绑定到结构体
config.BindStruct("key", &s)- 支持通过结构体标签
default解析并设置默认值. eg:default:"def_value" - 支持从 ENV 初始化设置字段值
default:"${APP_ENV | dev}"
- 支持通过结构体标签
- 支持通过
.分隔符来按路径获取子级值,也支持自定义分隔符。 e.gmap.keyarr.2 - 支持解析ENV变量名称。 like
shell: ${SHELL}->shell: /bin/zsh - 简洁的使用API
GetIntUintInt64StringBoolIntsIntMapStringsStringMap... - 完善的单元测试(code coverage > 95%)
只使用INI
如果你仅仅想用INI来做简单配置管理,推荐使用 gookit/ini
加载 .env 文件
gookit/ini: 提供一个子包 dotenv,支持从文件(eg .env)中导入数据到ENV
go get github.com/gookit/ini/v2/dotenv
GoDoc
安装包
go get github.com/gookit/config/v2
快速使用
这里使用yaml格式内容作为示例(testdata/yml_other.yml):
name: app2
debug: false
baseKey: value2
shell: ${SHELL}
envKey1: ${NotExist|defValue}
map1:
key: val2
key2: val20
arr1:
- val1
- val21
载入数据
示例代码请看
package main
import (
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/yaml"
)
// go run ./examples/yaml.go
func main() {
// 设置选项支持 ENV 解析
config.WithOptions(config.ParseEnv)
// 添加驱动程序以支持yaml内容解析(除了JSON是默认支持,其他的则是按需使用)
config.AddDriver(yaml.Driver)
// 加载配置,可以同时传入多个文件
err := config.LoadFiles("testdata/yml_base.yml")
if err != nil {
panic(err)
}
// fmt.Printf("config data: \n %#v\n", config.Data())
// 加载更多文件
err = config.LoadFiles("testdata/yml_other.yml")
// 也可以一次性加载多个文件
// err := config.LoadFiles("testdata/yml_base.yml", "testdata/yml_other.yml")
if err != nil {
panic(err)
}
}
使用提示:
- 可以使用
WithOptions()添加更多额外选项功能. 例如:ParseEnv,ParseDefault - 可以使用
AddDriver()添加需要使用的格式驱动器(json是默认加载的的,无需添加) - 然后就可以使用
LoadFiles()LoadStrings()等方法加载配置数据- 可以传入多个文件,也可以调用多次
- 多次加载的数据会自动按key进行合并处理
绑定数据到结构体
注意:结构体默认的绑定映射tag是
mapstructure,可以通过设置Options.TagName来更改它
type User struct {
Age int `mapstructure:"age"`
Key string `mapstructure:"key"`
UserName string `mapstructure:"user_name"`
Tags []int `mapstructure:"tags"`
}
user := User{}
err = config.BindStruct("user", &user)
fmt.Println(user.UserName) // inhere
更改结构标签名称
config.WithOptions(func(opt *Options) {
options.DecoderConfig.TagName = "config"
})
// use custom tag name.
type User struct {
Age int `config:"age"`
Key string `config:"key"`
UserName string `config:"user_name"`
Tags []int `config:"tags"`
}
user := User{}
err = config.Decode(&user)
将所有配置数据绑定到结构:
config.Decode(&myConf)
// 也可以
config.BindStruct("", &myConf)
config.MapOnExists与BindStruct一样,但仅当 key 存在时才进行映射绑定
快速获取数据
// 获取整型
age := config.Int("age")
fmt.Print(age) // 100
// 获取布尔值
val := config.Bool("debug")
fmt.Print(val) // true
// 获取字符串
name := config.String("name")
fmt.Print(name) // inhere
// 获取字符串数组
arr1 := config.Strings("arr1")
fmt.Printf("%#v", arr1) // []string{"val1", "val21"}
// 获取字符串KV映射
val := config.StringMap("map1")
fmt.Printf("%#v",val) // map[string]string{"key":"val2", "key2":"val20"}
// 值包含ENV变量
value := config.String("shell")
fmt.Print(value) // /bin/zsh
// 通过key路径获取值
// from array
value := config.String("arr1.0")
fmt.Print(value) // "val1"
// from map
value := config.String("map1.key")
fmt.Print(value) // "val2"
设置新的值
// set value
config.Set("name", "new name")
// get
name = config.String("name")
fmt.Print(name) // new name
从ENV载入数据
// os env: APP_NAME=config APP_DEBUG=true
// load ENV info
config.LoadOSEnvs(map[string]string{"APP_NAME": "app_name", "APP_DEBUG": "app_debug"})
// read
config.Bool("app_debug") // true
config.String("app_name") // "config"
从命令行参数载入数据
支持简单的从命令行 flag 参数解析,加载数据
// flags like: --name inhere --env dev --age 99 --debug
// load flag info
keys := []string{"name", "env", "age:int" "debug:bool"}
err := config.LoadFlags(keys)
// read
config.String("name") // "inhere"
config.String("env") // "dev"
config.Int("age") // 99
config.Bool("debug") // true
创建自定义实例
您可以创建自定义配置实例:
// create new instance, will auto register JSON driver
myConf := config.New("my-conf")
// create empty instance
myConf := config.NewEmpty("my-conf")
// create and with some options
myConf := config.NewWithOptions("my-conf", config.ParseEnv, config.ReadOnly)
监听配置更改
现在,您可以添加一个钩子函数来监听配置数据更改。然后,您可以执行一些自定义操作, 例如:将数据写入文件
在创建配置时添加钩子函数:
hookFn := func(event string, c *Config) {
fmt.Println("fire the:", event)
}
c := NewWithOptions("test", WithHookFunc(hookFn))
// for global config
config.WithOptions(WithHookFunc(hookFn))
之后, 当调用 LoadXXX, Set, SetData, ClearData 等方法时, 就会输出:
fire the: load.data
fire the: set.value
fire the: set.data
fire the: clean.data
监听载入的配置文件变动
想要监听载入的配置文件变动,并在变动时重新加载配置,你需要使用 https://github.com/fsnotify/fsnotify 库。 使用方法可以参考示例
package main
import (
"github.com/fsnotify/fsnotify"
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/yaml"
"github.com/gookit/goutil"
"github.com/gookit/goutil/cliutil"
)
func main() {
config.AddDriver(yaml.Driver)
config.WithOptions(
config.ParseEnv,
config.WithHookFunc(func(event string, c *config.Config) {
if event == config.OnReloadData {
cliutil.Cyanln("config reloaded, you can do something ....")
}
}),
)
// load app config files
err := config.LoadFiles(
"testdata/json_base.json",
"testdata/yml_base.yml",
"testdata/yml_other.yml",
)
if err != nil {
panic(err)
}
// mock server running
done := make(chan bool)
// watch loaded config files
err = watchConfigFiles(config.Default())
goutil.PanicErr(err)
cliutil.Infoln("loaded config files is watching ...")
<-done
}
func watchConfigFiles(cfg *config.Config) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
//noinspection GoUnhandledErrorResult
defer watcher.Close()
// get loaded files
files := cfg.LoadedFiles()
if len(files) == 0 {
return nil
}
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok { // 'Events' channel is closed
cliutil.Infoln("'Events' channel is closed ...", event)
return
}
// if event.Op > 0 {
cliutil.Infof("file event: %s\n", event)
if event.Op&fsnotify.Write == fsnotify.Write {
cliutil.Infof("modified file: %s\n", event.Name)
err := cfg.ReloadFiles()
if err != nil {
cliutil.Errorf("reload config error: %s\n", err.Error())
}
}
// }
case err, ok := <-watcher.Errors:
if ok { // 'Errors' channel is not closed
cliutil.Errorf("watch file error: %s\n", err.Error())
}
if err != nil {
cliutil.Errorf("watch file error2: %s\n", err.Error())
}
return
}
}
}()
for _, path := range files {
cliutil.Infof("add watch file: %s\n", path)
if err := watcher.Add(path); err != nil {
return err
}
}
return nil
}
同时,你需要监听 reload.data 事件:
config.WithOptions(config.WithHookFunc(func(event string, c *config.Config) {
if event == config.OnReloadData {
fmt.Println("config reloaded, you can do something ....")
}
}))
当配置发生变化并重新加载后,你可以做相关的事情,例如:重新绑定配置到你的结构体。
导出配置到文件
可以使用
config.DumpTo(out io.Writer, format string)将整个配置数据导出到指定的writer, 比如 buffer,file。
示例:导出为JSON文件
buf := new(bytes.Buffer)
_, err := config.DumpTo(buf, config.JSON)
ioutil.WriteFile("my-config.json", buf.Bytes(), 0755)
示例:导出格式化的JSON
可以设置默认变量 JSONMarshalIndent 的值 或 自定义新的 JSON 驱动程序。
config.JSONMarshalIndent = " "
示例:导出为YAML文件
_, err := config.DumpTo(buf, config.YAML)
ioutil.WriteFile("my-config.yaml", buf.Bytes(), 0755)
可用选项
// Options config options
type Options struct {
// parse env value. like: "${EnvName}" "${EnvName|default}"
ParseEnv bool
// ParseTime parses a duration string to time.Duration
// eg: 10s, 2m
ParseTime bool
// config is readonly. default is False
Readonly bool
// enable config data cache. default is False
EnableCache bool
// parse key, allow find value by key path. default is True eg: 'key.sub' will find `map[key]sub`
ParseKey bool
// tag name for binding data to struct
TagName string
// the delimiter char for split key, when `FindByPath=true`. default is '.'
Delimiter byte
// default write format. default is JSON
DumpFormat string
// default input format. default is JSON
ReadFormat string
// DecoderConfig setting for binding data to struct
DecoderConfig *mapstructure.DecoderConfig
// HookFunc on data changed.
HookFunc HookFunc
// ParseDefault tag on binding data to struct. tag: default
ParseDefault bool
}
选项: 解析默认值
NEW: 支持通过结构标签 default 解析并设置默认值
// add option: config.ParseDefault
c := config.New("test").WithOptions(config.ParseDefault)
// only set name
c.SetData(map[string]any{
"name": "inhere",
})
// age load from default tag
type User struct {
Age int `default:"30"`
Name string
Tags []int
}
user := &User{}
goutil.MustOk(c.Decode(user))
dump.Println(user)
Output:
&config_test.User {
Age: int(30),
Name: string("inhere"), #len=6
Tags: []int [ #len=0
],
},
API方法参考
载入配置
LoadData(dataSource ...any) (err error)从struct或map加载数据LoadFlags(keys []string) (err error)从命令行参数载入数据LoadOSEnvs(nameToKeyMap map[string]string)从ENV载入数据LoadExists(sourceFiles ...string) (err error)从存在的配置文件里加载数据,会忽略不存在的文件LoadFiles(sourceFiles ...string) (err error)从给定的配置文件里加载数据,有文件不存在则会panicLoadFromDir(dirPath, format string) (err error)从给定目录里加载自定格式的文件,文件名会作为 keyLoadRemote(format, url string) (err error)从远程 URL 加载配置数据LoadSources(format string, src []byte, more ...[]byte) (err error)从给定格式的字节数据加载配置LoadStrings(format string, str string, more ...string) (err error)从给定格式的字符串配置里加载配置数据LoadFilesByFormat(format string, sourceFiles ...string) (err error)从给定格式的文件加载配置LoadExistsByFormat(format string, sourceFiles ...string) error从给定格式的文件加载配置,会忽略不存在的文件
获取值
Bool(key string, defVal ...bool) boolInt(key string, defVal ...int) intUint(key string, defVal ...uint) uintInt64(key string, defVal ...int64) int64Ints(key string) (arr []int)IntMap(key string) (mp map[string]int)Float(key string, defVal ...float64) float64String(key string, defVal ...string) stringStrings(key string) (arr []string)StringMap(key string) (mp map[string]string)SubDataMap(key string) maputi.DataGet(key string, findByPath ...bool) (value any)
将数据映射到结构体:
BindStruct(key string, dst any) errorMapOnExists(key string, dst any) error
设置值
Set(key string, val any, setByPath ...bool) (err error)
有用的方法
Getenv(name string, defVal ...string) (val string)AddDriver(driver Driver)Data() map[string]anyExists(key string, findByPath ...bool) boolDumpTo(out io.Writer, format string) (n int64, err error)SetData(data map[string]any)设置数据以覆盖Config.Data
单元测试
go test -cover
// contains all sub-folder
go test -cover ./...
使用Config的项目
看看这些使用了 https://github.com/gookit/config 的项目:
- https://github.com/JanDeDobbeleer/oh-my-posh A prompt theme engine for any shell.
- + See More
Gookit 工具包
- gookit/ini INI配置读取管理,支持多文件加载,数据覆盖合并, 解析ENV变量, 解析变量引用
- gookit/rux Simple and fast request router for golang HTTP
- gookit/gcli Go的命令行应用,工具库,运行CLI命令,支持命令行色彩,用户交互,进度显示,数据格式化显示
- gookit/event Go实现的轻量级的事件管理、调度程序库, 支持设置监听器的优先级, 支持对一组事件进行监听
- gookit/cache 通用的缓存使用包装库,通过包装各种常用的驱动,来提供统一的使用API
- gookit/config Go应用配置管理,支持多种格式(JSON, YAML, TOML, INI, HCL, ENV, Flags),多文件加载,远程文件加载,数据合并
- gookit/color CLI 控制台颜色渲染工具库, 拥有简洁的使用API,支持16色,256色,RGB色彩渲染输出
- gookit/filter 提供对Golang数据的过滤,净化,转换
- gookit/validate Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器
- gookit/goutil Go 的一些工具函数,格式化,特殊处理,常用信息获取等
- 更多请查看 https://github.com/gookit
相关包
- Ini 解析 gookit/ini/parser
- Properties 解析 gookit/properties
- Yaml 解析 go-yaml
- Toml 解析 go toml
- 数据合并 mergo
- 映射数据到结构体 mapstructure
- JSON5 解析
License
MIT