Project Icon

koanf

Go语言灵活多源配置管理库

koanf是一个Go语言多源配置管理库,支持从文件、命令行、环境变量等多种来源读取配置。它可解析JSON、YAML、TOML等格式,提供清晰抽象和良好扩展性。koanf依赖少,易于集成自定义解析器和提供者,适合需要灵活配置管理的Go项目使用。

koanf

koanf 是一个用于在 Go 应用程序中从不同来源读取不同格式配置的库。它是 spf13/viper 的替代方案,更加简洁、轻量,具有更好的抽象和可扩展性,依赖项也更少。

koanf v2 有多个模块(Providers)用于从各种来源读取配置,如文件、命令行标志、环境变量、Vault 和 S3,以及用于解析(Parsers)JSON、YAML、TOML、Hashicorp HCL 等格式。它很容易插入自定义解析器和提供者。

providers 和 parsers 中的所有外部依赖都与核心分离,可以根据需要单独安装。

运行测试 GoDoc

安装

# 安装核心库
go get -u github.com/knadh/koanf/v2

# 安装必要的 Provider(s)
# 可用的有:file, env, 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

查看所有内置 Providers 和 Parsers 的列表。

目录

概念

  • koanf.Provider 是一个通用接口,提供配置,例如从文件、环境变量、HTTP 源或任何地方。配置可以是解析器可以解析的原始字节,也可以是可以直接加载的嵌套 map[string]interface{}
  • koanf.Parser 是一个通用接口,接收原始字节,解析并返回嵌套的 map[string]interface{}。例如 JSON 和 YAML 解析器。
  • 一旦加载到 koanf 中,配置值可以通过分隔的键路径语法进行查询。例如:app.server.port。可以选择任何分隔符。
  • 可以从多个来源加载配置并合并到一个 koanf 实例中,例如,先从文件加载,然后用命令行标志覆盖某些值。

通过这两个接口实现,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("加载配置错误:%v", err)
	}

	// 加载 YAML 配置并合并到之前加载的配置中(因为我们可以这样做)
	k.Load(file.Provider("mock/mock.yml"), yaml.Parser())

	fmt.Println("父级的名称是 = ", k.String("parent1.name"))
	fmt.Println("父级的 ID 是 = ", k.Int("parent1.id"))
}

监视文件变化

一些提供者暴露了 Watch() 方法,使提供者能够监视配置变化并触发回调以重新加载配置。 如果在 koanf 对象执行 Load() 时有并发的 *Get() 调用发生,这不是线程安全的。这种情况需要互斥锁定。

file, appconfig, vault, consul 提供者都有 Watch() 方法。

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 配置
	f := file.Provider("mock/mock.json")
	if err := k.Load(f, json.Parser()); err != nil {
		log.Fatalf("加载配置错误:%v", err)
	}

	// 加载 YAML 配置并合并到之前加载的配置中(因为我们可以这样做)
	k.Load(file.Provider("mock/mock.yml"), yaml.Parser())

	fmt.Println("父级的名称是 = ", k.String("parent1.name"))
	fmt.Println("父级的 ID 是 = ", k.Int("parent1.id"))

	// 监视文件并在变化时获得回调。回调可以执行任何操作,
	// 比如重新加载配置。
	// 文件提供者总是返回一个 nil `event`。
	f.Watch(func(event interface{}, err error) {
		if err != nil {
			log.Printf("监视错误:%v", err)
			return
		}

		// 丢弃旧配置并加载新副本。
		log.Println("配置已更改。正在重新加载...")
		k = koanf.New(".")
		k.Load(f, json.Parser())
		k.Print()
	})

	// 要停止文件监视器,调用:
	// f.Unwatch()

	// 永久阻塞(并手动对 mock/mock.json 进行更改)以
	// 重新加载配置。
	log.Println("永久等待中。尝试对 mock/mock.json 进行更改以实时重新加载")
	<-make(chan bool)
}

从命令行读取

以下示例展示了 posflag.Provider 的使用,它是 spf13/pflag 库的包装器,这是一个高级命令行库。对于 Go 内置的 flag 包,使用 basicflag.Provider

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/knadh/koanf/v2"
	"github.com/knadh/koanf/parsers/toml"

	// TOML 版本 2 可在以下位置获得:
	// "github.com/knadh/koanf/parsers/toml/v2"

	"github.com/knadh/koanf/providers/file"
	"github.com/knadh/koanf/providers/posflag"
	flag "github.com/spf13/pflag"
)

// 全局 koanf 实例。使用 "." 作为键路径分隔符。这可以是 "/" 或任何字符。
var k = koanf.New(".")

func main() {
	// 使用符合 POSIX 标准的 pflag 库代替 Go 的 flag 库。
	f := flag.NewFlagSet("config", flag.ContinueOnError)
	f.Usage = func() {
		fmt.Println(f.FlagUsages())
		os.Exit(0)
	}
	// 要加载到 koanf 中的一个或多个配置文件的路径以及一些配置参数。
	f.StringSlice("conf", []string{"mock/mock.toml"}, "一个或多个 .toml 配置文件的路径")
	f.String("time", "2020-01-01", "时间字符串")
	f.String("type", "xxx", "应用程序类型")
	f.Parse(os.Args[1:])
```go
// 加载命令行中提供的配置文件。
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
    if err := k.Load(file.Provider(c), toml.Parser()); err != nil {
        log.Fatalf("加载文件出错: %v", err)
    }
}

// "time"和"type"可能已从配置文件加载,但它们
// 仍可以被命令行中的值覆盖。
// 内置的posflag.Provider接收来自spf13/pflag库的flagset。
// 将Koanf实例传递给posflag有助于处理默认命令行标志值,
// 这些值在之前加载的提供程序的conf映射中可能不存在。
if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil {
    log.Fatalf("加载配置出错: %v", err)
}

fmt.Println("time是 = ", 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"
    "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("加载配置出错: %v", err)
    }

    // 加载环境变量并合并到已加载的配置中。
    // "MYVAR"是用于过滤环境变量的前缀。
    // "."是用于表示环境变量中键层次结构的分隔符。
    // (可选的,或可以为nil)函数可用于转换环境变量名称,例如将其转为小写。
    //
    // 例如,环境变量:MYVAR_TYPE和MYVAR_PARENT1_CHILD1_NAME
    // 将被合并到配置文件中的"type"和嵌套的"parent1.child1.name"键中,
    // 因为我们将键转为小写,将`_`替换为`.`,并去掉MYVAR_前缀,
    // 所以只剩下"parent1.child1.name"。
    k.Load(env.Provider("MYVAR_", ".", func(s string) string {
        return strings.Replace(strings.ToLower(
            strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)
    }), nil)

    fmt.Println("name是 = ", k.String("parent1.child1.name"))
}

你也可以使用env.ProviderWithValue,通过回调函数同时修改键和值, 以返回字符串以外的类型。例如,这里用空格分隔的环境变量值被返回为字符串切片或数组。 例如:MYVAR_slice=a b c变成slice: [a, b, c]

    k.Load(env.ProviderWithValue("MYVAR_", ".", func(s string, v string) (string, interface{}) {
        // 去掉MYVAR_前缀并转为小写,同时将键中的_字符替换为.(koanf分隔符)。
        key := strings.Replace(strings.ToLower(strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)

        // 如果值中包含空格,则按空格将值分割成切片。
        if strings.Contains(v, " ") {
            return key, strings.Split(v, " ")
        }

        // 否则,返回普通字符串。
        return key, v
    }), nil)

从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("加载配置出错: %v", err)
}

读取原始字节

内置的rawbytesProvider可用于从源(如数据库或HTTP调用)读取任意字节。

package main

import (
    "fmt"

    "github.com/knadh/koanf/v2"
    "github.com/knadh/koanf/parsers/json"
    "github.com/knadh/koanf/providers/rawbytes"
)

// 全局koanf实例。使用.作为键路径分隔符。这可以是/或任何其他字符。
var k = koanf.New(".")

func main() {
    b := []byte(`{"type": "rawbytes", "parent1": {"child1": {"type": "rawbytes"}}}`)
    k.Load(rawbytes.Provider(b), json.Parser())
    fmt.Println("type是 = ", k.String("parent1.child1.type"))
}

解析和序列化

Parser可用于根据字段标签将Koanf实例中的值解析并扫描到结构体中, 也可用于将Koanf实例序列化回字节,例如,重新转为JSON或YAML,以便写回文件。

package main

import (
    "fmt"
    "log"

    "github.com/knadh/koanf/v2"
    "github.com/knadh/koanf/parsers/json"
    "github.com/knadh/koanf/providers/file"
)

// 全局koanf实例。使用.作为键路径分隔符。这可以是/或任何其他字符。
var (
    k      = koanf.New(".")
    parser = json.Parser()
)

func main() {
    // 加载JSON配置。
    if err := k.Load(file.Provider("mock/mock.json"), parser); err != nil {
        log.Fatalf("加载配置出错: %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。
    // parser实例可以是任何类型,例如:json.Parser()、yaml.Parser()等。
    b, _ := k.Marshal(parser)
    fmt.Println(string(b))
}

使用平面路径解析

有时需要将各种嵌套结构中的一组键解析到一个平面目标结构中。 这可以通过UnmarshalConf.FlatPaths标志实现。

package main

import (
    "fmt"
    "log"

    "github.com/knadh/koanf/v2"
    "github.com/knadh/koanf/parsers/json"
    "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("加载配置出错: %v", err)
    }

    type rootFlat struct {
        Type                        string            `koanf:"type"`
        Empty                       map[string]string `koanf:"empty"`
        Parent1Name                 string            `koanf:"parent1.name"`
        Parent1ID                   int               `koanf:"parent1.id"`
        Parent1Child1Name           string            `koanf:"parent1.child1.name"`
        Parent1Child1Type           string            `koanf:"parent1.child1.type"`
        Parent1Child1Empty          map[string]string `koanf:"parent1.child1.empty"`
        Parent1Child1Grandchild1IDs []int             `koanf:"parent1.child1.grandchild1.ids"`
        Parent1Child1Grandchild1On  bool              `koanf:"parent1.child1.grandchild1.on"`
    }

    // 使用FlatPaths: True解析整个根。
    var o1 rootFlat
    k.UnmarshalWithConf("", &o1, koanf.UnmarshalConf{Tag: "koanf", FlatPaths: true})
    fmt.Println(o1)

// 解析"parent1"的子结构。 type subFlat struct { Name string koanf:"name" ID int koanf:"id" Child1Name string koanf:"child1.name" Child1Type string koanf:"child1.type" Child1Empty map[string]string koanf:"child1.empty" Child1Grandchild1IDs []int koanf:"child1.grandchild1.ids" Child1Grandchild1On bool koanf:"child1.grandchild1.on" }

var o2 subFlat k.UnmarshalWithConf("parent1", &o2, koanf.UnmarshalConf{Tag: "koanf", FlatPaths: true}) fmt.Println(o2) }


#### 从嵌套映射中读取

内置的`confmap`提供程序接受一个`map[string]interface{}`,可以加载到koanf实例中。

```go
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"
)

// 全局koanf实例。使用"."作为键路径分隔符。这可以是"/"或任何字符。
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("加载配置时出错: %v", err)
	}

	// 加载YAML配置并合并到先前加载的配置中(因为我们可以)。
	k.Load(file.Provider("mock/mock.yml"), yaml.Parser())

	fmt.Println("parent的名字是 = ", k.String("parent1.name"))
	fmt.Println("parent的ID是 = ", k.Int("parent1.id"))
}

从结构体中读取

内置的structs提供程序可用于从结构体中读取数据并加载到koanf实例中。

package main

import (
	"fmt"

	"github.com/knadh/koanf/v2"
	"github.com/knadh/koanf/providers/structs"
)

// 全局koanf实例。使用"."作为键路径分隔符。这可以是"/"或任何字符。
var k = koanf.New(".")

type parentStruct struct {
	Name   string      `koanf:"name"`
	ID     int         `koanf:"id"`
	Child1 childStruct `koanf:"child1"`
}
type childStruct struct {
	Name        string            `koanf:"name"`
	Type        string            `koanf:"type"`
	Empty       map[string]string `koanf:"empty"`
	Grandchild1 grandchildStruct  `koanf:"grandchild1"`
}
type grandchildStruct struct {
	Ids []int `koanf:"ids"`
	On  bool  `koanf:"on"`
}
type sampleStruct struct {
	Type    string            `koanf:"type"`
	Empty   map[string]string `koanf:"empty"`
	Parent1 parentStruct      `koanf:"parent1"`
}

func main() {
	// 使用structs提供程序加载默认值。
	// 我们向提供程序提供一个结构体以及结构体标签`koanf`。
	k.Load(structs.Provider(sampleStruct{
		Type:  "json",
		Empty: make(map[string]string),
		Parent1: parentStruct{
			Name: "parent1",
			ID:   1234,
			Child1: childStruct{
				Name:  "child1",
				Type:  "json",
				Empty: make(map[string]string),
				Grandchild1: grandchildStruct{
					Ids: []int{1, 2, 3},
					On:  true,
				},
			},
		},
	}, "koanf"), nil)

	fmt.Printf("名字是 = `%s`\n", k.String("parent1.child1.name"))
}

合并行为

默认行为

当你这样创建Koanf时的默认行为是:koanf.New(delim),最新加载的配置将与之前的配置合并。

例如: first.yml

key: [1,2,3]

second.yml

key: 'string'

当加载second.yml时,它将覆盖first.yml的类型。

如果不希望这种行为,你可以进行"严格"合并。在相同的场景下,Load将返回一个错误。

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("加载配置时出错: %v", err)
	}

	jsonPath := "mock/mock.json"
	if err := k.Load(file.Provider(jsonPath), json.Parser()); err != nil {
		log.Fatalf("加载配置时出错: %v", err)
	}
}

注意: 当合并不同的扩展名时,每个解析器可能会以不同方式处理其类型, 这意味着即使你加载相同的类型,使用StrictMerge: true也可能会失败。

例如:合并JSON和YAML很可能会失败,因为JSON将整数视为float64,而YAML将它们视为整数。

合并顺序和键的大小写敏感性

  • koanf中的配置键是大小写敏感的。例如,app.server.portAPP.SERVER.port不是相同的。
  • koanf不对从各种提供程序加载配置的顺序施加任何限制。每次连续的Load()Merge()都会将新配置合并到现有配置中。也就是说,可以先加载环境变量,然后在其上加载文件,然后在其上加载命令行变量,或任何这样的顺序。

自定义提供程序和解析器

提供程序返回一个嵌套的map[string]interface{}配置,可以直接使用koanf.Load()加载到koanf中,或者它可以返回原始字节,可以使用解析器进行解析(同样使用koanf.Load()加载)。编写提供程序和解析器很简单。请查看providersparsers目录中的内置实现。

自定义合并策略

默认情况下,使用Load()合并两个配置源时,koanf会递归合并嵌套映射(map[string]interface{})的键, 而静态值(切片、字符串等)会被覆盖。可以通过提供带有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("加载配置时出错: %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("加载配置时出错: %v", err)
	}
}

API

查看https://pkg.go.dev/github.com/knadh/koanf/v2#section-documentation 获取所有可用方法的完整API文档

内置提供程序

使用go get -u github.com/knadh/koanf/providers/$provider安装

包名提供者描述
filefile.Provider(filepath string)读取文件并返回待解析的原始字节。
fsfs.Provider(f fs.FS, filepath string)(实验性) 从fs.FS读取文件并返回待解析的原始字节。此提供者需要go v1.16或更高版本。
basicflagbasicflag.Provider(f *flag.FlagSet, delim string)接受标准库的flag.FlagSet
posflagposflag.Provider(f *pflag.FlagSet, delim string)接受spf13/pflag.FlagSet(支持多种类型的高级POSIX兼容标志),并基于分隔符提供嵌套的配置映射。
envenv.Provider(prefix, delim string, f func(s string) string)接受一个可选的前缀用于过滤环境变量,一个可选的函数用于转换环境变量(接受并返回字符串),并基于分隔符返回嵌套的配置映射。
confmapconfmap.Provider(mp map[string]interface{}, delim string)接受预先制作的map[string]interface{}配置映射。如果提供了分隔符,则假定键是扁平化的,因此使用分隔符进行解扁平化。
structsstructs.Provider(s interface{}, tag string)接受一个结构体和结构体标签。
s3s3.Provider(s3.S3Config{})接受一个s3配置结构体。
rawbytesrawbytes.Provider(b []byte)接受一个原始[]byte切片,用koanf.Parser进行解析
vault/v2vault.Provider(vault.Config{})Hashicorp Vault提供者
appconfig/v2vault.AppConfig(appconfig.Config{})AWS AppConfig提供者
etcd/v2etcd.Provider(etcd.Config{})CNCF etcd提供者
consul/v2consul.Provider(consul.Config{})Hashicorp Consul提供者
parameterstore/v2parameterstore.Provider(parameterstore.Config{})AWS Systems Manager Parameter Store提供者

内置解析器

通过 go get -u github.com/knadh/koanf/parsers/$parser 安装

包名解析器描述
jsonjson.Parser()将JSON字节解析为嵌套映射
yamlyaml.Parser()将YAML字节解析为嵌套映射
tomltoml.Parser()将TOML字节解析为嵌套映射
toml/v2toml.Parser()将TOML字节解析为嵌套映射(使用go-toml v2)
dotenvdotenv.Parser()将DotEnv字节解析为扁平映射
hclhcl.Parser(flattenSlices bool)将Hashicorp HCL字节解析为嵌套映射。建议将flattenSlices设置为true。了解更多
nestedtextnestedtext.Parser()将NestedText字节解析为扁平映射
hjsonhjson.Parser()将HJSON字节解析为嵌套映射

第三方提供者

包名提供者描述
github.com/defensestation/koanf/providers/secretsmanagervault.SecretsMananger(secretsmanager.Config{}, f func(s string) string)AWS Secrets Manager提供者,从存储中接受映射或字符串作为值
github.com/defensestation/koanf/providers/parameterstorevault.ParameterStore(parameterstore.Config{}, f func(s string) string)AWS ParameterStore提供者,一个可选的函数用于转换环境变量(接受并返回字符串)

viper的替代方案

koanf是流行的spf13/viper轻量级替代方案。它是为了解决使用viper时遇到的一些根本性问题而编写的。

  • viper通过强制将键转为小写违反了JSON、YAML、TOML、HCL语言规范。
  • 显著增加了构建大小
  • 将配置解析与文件扩展名紧密耦合。
  • 语义和抽象不佳。命令行、环境变量、文件等以及各种解析器都硬编码在核心中。没有可扩展的基本元素。
  • 将大量第三方依赖引入核心包。例如,即使不使用YAML或标志,由于耦合,这些依赖仍会被引入。
  • 强加任意的排序约定(例如:标志 -> 环境变量 -> 配置等)
  • Get()返回切片和映射的引用。外部进行的修改会改变配置映射中的底层值。
  • 做一些非惯用的事情,如在扁平映射上放弃O(1)
  • Viper将包含空映射的键(例如:my_key: {})视为未设置(即:IsSet("my_key") == false)。
  • 存在大量未解决的问题
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号