golang轻量级可扩展配置读取插件库koanf的使用

Golang轻量级可扩展配置读取插件库koanf的使用

简介

koanf logo

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

1 回复

更多关于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()

最佳实践

  1. 配置优先级:通常按照默认配置→文件配置→环境变量→命令行参数的顺序加载,后面的配置覆盖前面的
  2. 配置结构:使用嵌套结构组织配置,如server.portdatabase.host
  3. 类型安全:尽量使用类型安全的读取方法如Int()String()
  4. 配置验证:加载配置后应该验证关键配置项是否存在
  5. 敏感信息:不要将敏感信息如密码直接放在配置文件中,考虑使用secret管理工具

Koanf是一个功能强大但保持简洁的配置库,非常适合需要灵活配置管理的Go应用程序。它的模块化设计使得可以轻松扩展支持新的配置格式和来源。

回到顶部