golang轻量级可扩展配置读取插件库koanf的使用
Golang轻量级可扩展配置读取插件库koanf的使用
简介
koanf 是一个用于从不同来源读取不同格式配置的Go库。它是spf13/viper的一个更简洁、更轻量的替代方案,具有更好的抽象和可扩展性,依赖更少。
koanf v2提供了多种模块(Providers)用于从文件、命令行标志、环境变量、Vault和S3等来源读取配置,以及解析JSON、YAML、TOML、Hashicorp HCL等格式(Parsers)。可以轻松插入自定义解析器和提供程序。
安装
# 安装核心库
go get -u github.com/knadh/koanf/v2
# 安装必要的Provider(s)
# 可用: file, env/v2, posflag, basicflag, confmap, rawbytes,
# structs, fs, s3, appconfig/v2, consul/v2, etcd/v2, vault/v2, parameterstore/v2
# 例如: go get -u github.com/knadh/koanf/providers/s3
# 例如: go get -u github.com/knadh/koanf/providers/consul/v2
go get -u github.com/knadh/koanf/providers/file
# 安装必要的Parser(s)
# 可用: toml, toml/v2, json, yaml, dotenv, hcl, hjson, nestedtext
# go get -u github.com/knadh/koanf/parsers/$parser
go get -u github.com/knadh/koanf/parsers/toml
概念
koanf.Provider
是一个通用接口,提供配置,例如来自文件、环境变量、HTTP源或任何地方的配置。koanf.Parser
是一个通用接口,它接受原始字节,解析并返回嵌套的map[string]interface{}
。- 加载到koanf后,配置值通过带分隔符的键路径语法查询。例如:
app.server.port
。 - 可以从多个源加载配置并合并到一个koanf实例中。
从文件读取配置
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
// 全局koanf实例。使用"."作为键路径分隔符。这可以是"/"或任何字符。
var k = koanf.New(".")
func main() {
// 加载JSON配置
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 加载YAML配置并合并到先前加载的配置中
k.Load(file.Provider("mock/mock.yml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
}
监听文件变化
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
var k = koanf.New(".")
func main() {
// 加载JSON配置
f := file.Provider("mock/mock.json")
if err := k.Load(f, json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 加载YAML配置并合并
k.Load(file.Provider("mock/mock.yml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
// 监听文件变化
f.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("watch error: %v", err)
return
}
log.Println("config changed. Reloading ...")
k = koanf.New(".")
k.Load(f, json.Parser())
k.Print()
})
// 阻止程序退出
log.Println("waiting forever. Try making a change to mock/mock.json to live reload")
<-make(chan bool)
}
从命令行读取
package main
import (
"fmt"
"log"
"os"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
flag "github.com/spf13/pflag"
)
var k = koanf.New(".")
func main() {
// 使用POSIX兼容的pflag库
f := flag.NewFlagSet("config", flag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
// 配置文件和参数
f.StringSlice("conf", []string{"mock/mock.toml"}, "path to one or more .toml config files")
f.String("time", "2020-01-01", "a time string")
f.String("type", "xxx", "type of the app")
f.Parse(os.Args[1:])
// 加载配置文件
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := k.Load(file.Provider(c), toml.Parser()); err != nil {
log.Fatalf("error loading file: %v", err)
}
}
// 加载命令行标志
if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil {
log.Fatalf("error loading config: %v", err)
}
fmt.Println("time is = ", k.String("time"))
}
从环境变量读取
package main
import (
"fmt"
"log"
"strings"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/env/v2"
"github.com/knadh/koanf/providers/file"
)
var k = koanf.New(".")
func main() {
// 加载JSON配置
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 加载环境变量
k.Load(env.Provider(".", env.Opt{
Prefix: "MYVAR_",
TransformFunc: func(k, v string) (string, any) {
// 转换键名
k = strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(k, "MYVAR_")), "_", ".")
// 转换值为切片(如果包含空格)
if strings.Contains(v, " ") {
return k, strings.Split(v, " ")
}
return k, v
},
}), nil)
fmt.Println("name is =", k.String("parent1.child1.name"))
fmt.Println("time is =", k.Time("time", time.DateOnly))
fmt.Println("ids are =", k.Strings("parent1.child1.grandchild1.ids"))
}
从S3桶读取
// 从S3加载JSON配置
if err := k.Load(s3.Provider(s3.Config{
AccessKey: os.Getenv("AWS_S3_ACCESS_KEY"),
SecretKey: os.Getenv("AWS_S3_SECRET_KEY"),
Region: os.Getenv("AWS_S3_REGION"),
Bucket: os.Getenv("AWS_S3_BUCKET"),
ObjectKey: "dir/config.json",
}), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
从原始字节读取
package main
import (
"fmt"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/rawbytes"
)
var k = koanf.New(".")
func main() {
b := []byte(`{"type": "rawbytes", "parent1": {"child1": {"type": "rawbytes"}}}`)
k.Load(rawbytes.Provider(b), json.Parser())
fmt.Println("type is = ", k.String("parent1.child1.type"))
}
解组和编组
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/file"
)
var (
k = koanf.New(".")
parser = json.Parser()
)
func main() {
// 加载JSON配置
if err := k.Load(file.Provider("mock/mock.json"), parser); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 解组到结构体
type childStruct struct {
Name string `koanf:"name"`
Type string `koanf:"type"`
Empty map[string]string `koanf:"empty"`
GrandChild struct {
Ids []int `koanf:"ids"`
On bool `koanf:"on"`
} `koanf:"grandchild1"`
}
var out childStruct
// 快速解组
k.Unmarshal("parent1.child1", &out)
fmt.Println(out)
// 带配置的解组
out = childStruct{}
k.UnmarshalWithConf("parent1.child1", &out, koanf.UnmarshalConf{Tag: "koanf"})
fmt.Println(out)
// 将实例编组回JSON
b, _ := k.Marshal(parser)
fmt.Println(string(b))
}
从嵌套映射和结构体读取
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
)
var k = koanf.New(".")
func main() {
// 使用confmap提供程序加载默认值
k.Load(confmap.Provider(map[string]interface{}{
"parent1.name": "Default Name",
"parent3.name": "New name here",
}, "."), nil)
// 加载JSON配置
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 加载YAML配置并合并
k.Load(file.Provider("mock/mock.yml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
}
合并行为
默认情况下,当使用koanf.New(delim)
创建Koanf时,最新加载的配置将与先前的配置合并。如果不需要这种行为,可以设置StrictMerge: true
。
package main
import (
"errors"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/maps"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
var conf = koanf.Conf{
Delim: ".",
StrictMerge: true,
}
var k = koanf.NewWithConf(conf)
func main() {
yamlPath := "mock/mock.yml"
if err := k.Load(file.Provider(yamlPath), yaml.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
jsonPath := "mock/mock.json"
if err := k.Load(file.Provider(jsonPath), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
}
自定义合并策略
可以通过WithMergeFunc
选项提供自定义合并函数来改变默认的合并行为。
package main
import (
"errors"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/maps"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
var conf = koanf.Conf{
Delim: ".",
StrictMerge: true,
}
var k = koanf.NewWithConf(conf)
func main() {
yamlPath := "mock/mock.yml"
if err := k.Load(file.Provider(yamlPath), yaml.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
jsonPath := "mock/mock.json"
if err := k.Load(file.Provider(jsonPath), json.Parser(), koanf.WithMergeFunc(func(src, dest map[string]interface{}) error {
// 自定义逻辑,将值从src复制到dst
return nil
})); err != nil {
log.Fatalf("error loading config: %v", err)
}
}
koanf是一个功能强大且灵活的配置管理库,可以帮助您轻松管理应用程序的配置,支持多种配置源和格式,并提供了丰富的API来操作和查询配置数据。
更多关于golang轻量级可扩展配置读取插件库koanf的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang轻量级可扩展配置读取插件库koanf的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang轻量级配置读取库Koanf使用指南
Koanf是一个轻量级、可扩展的Go配置读取库,支持多种配置格式和来源,设计简洁高效。下面我将详细介绍Koanf的核心特性和使用方法。
核心特性
- 支持多种配置格式:JSON, YAML, TOML, HCL, envfile, Java properties等
- 多配置源支持:文件、环境变量、命令行参数、内存、S3等
- 轻量级无依赖
- 支持配置合并和覆盖
- 类型安全的值读取
- 配置变更监听
基本使用
安装
go get github.com/knadh/koanf/v2
简单示例
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/file"
)
func main() {
var k = koanf.New(".")
// 加载JSON配置文件
if err := k.Load(file.Provider("config.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 读取配置值
fmt.Println("server port:", k.Int("server.port"))
fmt.Println("database host:", k.String("database.host"))
}
多配置源支持
Koanf支持从多种来源加载配置:
package main
import (
"fmt"
"log"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/posflag"
"github.com/spf13/pflag"
)
func main() {
var k = koanf.New(".")
// 1. 从JSON文件加载
if err := k.Load(file.Provider("config.json"), json.Parser()); err != nil {
log.Printf("error loading json config: %v", err)
}
// 2. 从YAML文件加载并合并
if err := k.Load(file.Provider("config.yaml"), yaml.Parser()); err != nil {
log.Printf("error loading yaml config: %v", err)
}
// 3. 从环境变量加载
// 将环境变量前缀为APP_的加载到配置中,并将_转换为.
k.Load(env.Provider("APP_", ".", func(s string) string {
return s
}), nil)
// 4. 从命令行参数加载
flags := pflag.NewFlagSet("config", pflag.ContinueOnError)
flags.String("server.host", "localhost", "server host")
flags.Int("server.port", 8080, "server port")
flags.Parse([]string{"--server.port", "9090"})
k.Load(posflag.Provider(flags, ".", k), nil)
// 读取配置
fmt.Println("Final server config:",
k.String("server.host"),
k.Int("server.port"))
}
配置读取方法
Koanf提供了多种类型安全的读取方法:
// 基本类型
k.String("path.to.key") // string
k.Int("path.to.key") // int
k.Float64("path.to.key") // float64
k.Bool("path.to.key") // bool
// 切片类型
k.Strings("path.to.key") // []string
k.Ints("path.to.key") // []int
k.Float64s("path.to.key") // []float64
k.Bools("path.to.key") // []bool
// 时间类型
k.Time("path.to.key") // time.Time
k.Duration("path.to.key") // time.Duration
// 原始值
k.Get("path.to.key") // interface{}
// 映射到结构体
type Config struct {
Port int `koanf:"port"`
Host string `koanf:"host"`
}
var cfg Config
k.Unmarshal("server", &cfg)
监听配置变更
Koanf支持配置变更监听:
package main
import (
"log"
"time"
"github.com/knadh/koanf/v2"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/file"
)
func main() {
var k = koanf.New(".")
// 创建一个文件provider并启用watch
fp := file.Provider("config.json")
// 加载初始配置
if err := k.Load(fp, json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// 监听文件变更
go func() {
for {
time.Sleep(time.Second)
if err := fp.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("watch error: %v", err)
return
}
// 重新加载配置
if err := k.Load(fp, json.Parser()); err != nil {
log.Printf("error reloading config: %v", err)
}
log.Println("config reloaded")
}); err != nil {
log.Printf("error setting up watch: %v", err)
}
}
}()
// 主程序逻辑
for {
log.Println("Current port:", k.Int("server.port"))
time.Sleep(5 * time.Second)
}
}
高级特性
自定义Provider
type MyProvider struct {
data map[string]interface{}
}
func (p *MyProvider) Read() (map[string]interface{}, error) {
return p.data, nil
}
func (p *MyProvider) ReadBytes() ([]byte, error) {
return json.Marshal(p.data)
}
// 使用自定义provider
myProvider := &MyProvider{
data: map[string]interface{}{
"server": map[string]interface{}{
"port": 8080,
"host": "localhost",
},
},
}
k.Load(myProvider, nil)
配置剪裁
// 只保留指定前缀的配置
k2 := k.Cut("server")
// 复制配置
k3 := k.Copy()
最佳实践
- 配置优先级:通常按照默认配置→文件配置→环境变量→命令行参数的顺序加载,后面的配置覆盖前面的
- 配置结构:使用嵌套结构组织配置,如
server.port
、database.host
等 - 类型安全:尽量使用类型安全的读取方法如
Int()
、String()
等 - 配置验证:加载配置后应该验证关键配置项是否存在
- 敏感信息:不要将敏感信息如密码直接放在配置文件中,考虑使用secret管理工具
Koanf是一个功能强大但保持简洁的配置库,非常适合需要灵活配置管理的Go应用程序。它的模块化设计使得可以轻松扩展支持新的配置格式和来源。