Viper是一套完整的Go应用程序配置解决方案

Viper


Go configuration with fangs!

安装

shell 复制代码
go get github.com/spf13/viper

注意: Viper 使用 Go Modules 来管理依赖项。

什么是 Viper?

Viper 是一套完整的 Go 应用程序配置解决方案,包括 12-Factor 应用程序。它旨在在应用程序中运行,能够处理各种类型的配置需求和格式。它支持:

  • 设置默认值
  • 读取 JSON、TOML、YAML、HCL、envfile 和 Java 属性配置文件
  • 实时监控和重读配置文件(可选)
  • 读取环境变量
  • 读取远程配置系统(etcd 或 Consul)并监控更改
  • 读取命令行标志
  • 读取缓冲区
  • 设置显式值

Viper 可以被视为所有应用程序配置需求的注册表。

为什么选择 Viper?

构建现代应用程序时,您无需担心配置文件格式;您只想专注于构建出色的软件。Viper 可以帮助您实现这一点。

Viper 可以为您完成以下工作:

  1. 查找、加载和解组 JSON、TOML、YAML、HCL、INI、envfile 或 Java 属性格式的配置文件。
  2. 提供一种机制,用于为不同的配置选项设置默认值。
  3. 提供一种机制,用于为通过命令行标志指定的选项设置覆盖值。
  4. 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  5. 轻松区分用户提供的命令行或配置文件是否与默认值相同。

Viper 使用以下优先级顺序。每一项的优先级都高于其下方的一项:

  • 显式调用 Set
  • 标志
  • 环境变量
  • 配置
  • 键/值存储
  • 默认值

重要提示: Viper 配置键不区分大小写。目前正在讨论将其设为可选。

将值放入 Viper

设置默认值

一个好的配置系统应该支持默认值。默认值对于键来说不是必需的,但如果键尚未通过配置文件、环境变量、远程配置或标志设置,默认值会很有用。

示例:

go 复制代码
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})

读取配置文件

Viper 只需极少的配置,就能知道在哪里查找配置文件。Viper 支持 JSON、TOML、YAML、HCL、INI、envfile 和 Java 属性文件。Viper 可以搜索多个路径,但目前单个 Viper 实例仅支持一个配置文件。Viper 不默认任何配置搜索路径,默认路径由应用程序自行决定。

以下是如何使用 Viper 搜索和读取配置文件的示例。无需指定任何特定路径,但至少应提供一个需要配置文件的路径。

go 复制代码
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath("/etc/appname/")   // path to look for the config file in
viper.AddConfigPath("$HOME/.appname")  // call multiple times to add many search paths
viper.AddConfigPath(".")               // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
	panic(fmt.Errorf("fatal error config file: %w", err))
}

您可以像这样处理未找到配置文件的特殊情况:

go 复制代码
if err := viper.ReadInConfig(); err != nil {
	if _, ok := err.(viper.ConfigFileNotFoundError); ok {
		// Config file not found; ignore error if desired
	} else {
		// Config file was found but another error was produced
	}
}

// Config file found and successfully parsed

注意 [自 1.6 起]: 您也可以创建一个没有扩展名的文件,并通过编程指定其格式。对于那些位于用户主目录中且没有扩展名(例如 .bashrc)的配置文件,

写入配置文件

读取配置文件很有用,但有时您希望存储运行时所做的所有修改。为此,您可以使用一系列命令,每个命令都有各自的用途:

  • WriteConfig - 将当前 viper 配置写入预定义路径(如果存在)。如果没有预定义路径,则会出错。如果存在,则会覆盖当前配置文件。
  • SafeWriteConfig - 将当前 viper 配置写入预定义路径。如果没有预定义路径,则会出错。如果存在,则不会覆盖当前配置文件。
  • WriteConfigAs - 将当前 viper 配置写入给定文件路径。如果存在,则会覆盖给定文件。
  • SafeWriteConfigAs - 将当前 viper 配置写入给定文件路径。如果指定文件存在,则不会覆盖该文件。

根据经验,所有标记为 safe 的文件都不会覆盖任何文件,如果文件不存在,则会创建该文件,而默认行为是创建或截断。

一个小示例部分:

go 复制代码
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written
viper.SafeWriteConfigAs("/path/to/my/.other_config")

监视和重新读取配置文件

Viper 支持让您的应用程序在运行时实时读取配置文件。无需重启服务器即可使配置生效的日子已经一去不复返了,由 Viper 驱动的应用程序可以在运行时读取配置文件的更新,并且不会错过任何细节。

只需将 Viper 实例设置为 watchConfig 即可。您也可以选择提供一个函数,让 Viper 在每次发生更改时运行该函数。

请确保在调用 WatchConfig() 之前添加所有 configPath

go 复制代码
viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()

从 io.Reader 读取配置

Viper 预定义了许多配置源,例如文件、环境变量、标志和远程键值存储,但您不必受这些配置源的约束。您也可以实现自己所需的配置源并将其提供给 Viper。

go 复制代码
viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")

// any approach to require this configuration into your program.
var yamlExample = []byte(`
Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
  jacket: leather
  trousers: denim
age: 35
eyes : brown
beard: true
`)

viper.ReadConfig(bytes.NewBuffer(yamlExample))

viper.Get("name") // this would be "steve"

设置覆盖

这些可以来自命令行标志,也可以来自您自己的应用程序逻辑。

go 复制代码
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
viper.Set("host.port", 5899) // 设置子集

注册和使用别名

别名允许多个键引用单个值

go 复制代码
viper.RegisterAlias("loud", "Verbose")

viper.Set("verbose", true) // 结果与下一行相同
viper.Set("loud", true) // 结果与上一行相同

viper.GetBool("loud") // true
viper.GetBool("verbose") // true

使用环境变量

Viper 完全支持环境变量。这使得 12 因素应用程序开箱即用。有五种方法可以帮助使用 ENV:

  • AutomaticEnv()
  • BindEnv(string...) : error
  • SetEnvPrefix(string)
  • SetEnvKeyReplacer(string...) *strings.Replacer
  • AllowEmptyEnv(bool)

使用环境变量时,务必注意 Viper会区分大小写。

Viper 提供了一种机制来尝试确保环境变量的唯一性。使用 SetEnvPrefix 可以告诉 Viper 在读取环境变量时使用前缀。BindEnvAutomaticEnv 都会使用此前缀。

BindEnv 接受一个或多个参数。第一个参数是键名,其余参数是要绑定到此键的环境变量名称。如果提供了多个参数,则这些参数将按指定的顺序优先处理。环境变量的名称区分大小写。如果未提供环境变量名称,
Viper 会自动假定环境变量符合以下格式:前缀 + "_" + 键名(全部大写)。当您明确提供环境变量名称(第二个参数)时,它不会自动添加前缀。例如,如果第二个参数是“id”,
Viper 将查找环境变量“ID”。

使用环境变量时需要注意的一点是,每次访问时都会读取该变量的值。Viper 在调用 BindEnv 时不会修复该值。

AutomaticEnv 是一个强大的辅助函数,尤其与SetEnvPrefix 结合使用时。调用后,Viper 会在每次发出 viper.Get 请求时检查环境变量。它会应用以下规则。它会检查环境变量的名称是否与键匹配,键名大写,并且如果设置了 EnvPrefix,则以 EnvPrefix 为前缀。

SetEnvKeyReplacer 允许您使用 strings.Replacer 对象在一定程度上重写环境变量。如果您希望在 Get() 调用中使用 - 或其他字符,但希望环境变量使用 _ 分隔符,这将非常有用。可以在 viper_test.go 中找到使用示例。

或者,您可以将 EnvKeyReplacerNewWithOptions 工厂函数一起使用。与 SetEnvKeyReplacer 不同,它接受 StringReplacer 接口,允许您编写自定义字符串替换逻辑。

默认情况下,空环境变量被视为未设置,并将回退到下一个配置源。要将空环境变量视为已设置,请使用AllowEmptyEnv 方法。

Env 示例

go 复制代码
SetEnvPrefix("spf") // 将自动大写
BindEnv("id")

os.Setenv("SPF_ID", "13") // 通常在应用外部执行

id := Get("id") // 13

使用标志

Viper 能够绑定标志。具体来说,Viper 支持 Pflags,就像 Cobra 库中使用的一样。

BindEnv 类似,该值不是在调用绑定方法时设置的,而是在访问绑定方法时设置的。这意味着您可以随时绑定,即使在init() 函数中也是如此。

对于单个标志,BindPFlag() 方法提供了此功能。

示例:

go 复制代码
serverCmd.Flags().Int("port", 1138, "运行应用服务器的端口")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))

您还可以绑定一组现有的 pflag (pflag.FlagSet):

示例:

go 复制代码
pflag.Int("flagname", 1234, "flagname 的帮助消息")

pflag.Parse()
viper.BindPFlags(pflag.CommandLine)

i := viper.GetInt("flagname") // 从 viper 而不是 pflag 获取值

在 Viper 中使用 pflag 并不妨碍使用其他使用 pflag 的包flag来自标准库的包。pflag 包可以通过导入 flag 包中定义的标志来处理这些标志。这可以通过调用 pflag 包提供的便捷函数 AddGoFlagSet() 来实现。

示例:

go 复制代码
package main

import (
"flag"
"github.com/spf13/pflag"
)

func main() {

// 使用标准库“flag”包
flag.Int("flagname", 1234, "flagname 的帮助信息")

pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)

i := viper.GetInt("flagname") // 从 viper 获取值

// ...
}

标志接口

如果您不使用 Pflags,Viper 提供了两个 Go 接口来绑定其他标志系统。FlagValue 表示单个标志。这是一个非常简单的示例,演示如何实现此接口:

go 复制代码
type myFlag struct {}
func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() string { return "string" }

一旦你的标志实现了此接口,你只需告诉 Viper 绑定它即可:

go 复制代码
viper.BindFlagValue("my-flag-name", myFlag{})

FlagValueSet 表示一组标志。这是一个非常简单的示例,演示如何实现此接口:

go 复制代码
type myFlagSet struct {
flags []myFlag
}

func (f myFlagSet) VisitAll(fn func(FlagValue)) {
for _, flag := range flags {
fn(flag)
}
}

一旦您的标志集实现了此接口,您就可以简单地告诉 Viper 绑定它:

go 复制代码
fSet := myFlagSet{
flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)

远程键/值存储支持

要在 Viper 中启用远程支持,请导入 viper/remote 包:
`import _ "github.com/spf13/viper/remote"

Viper 将从键值存储(例如 etcd 或 Consul)中的路径中检索到配置字符串(JSON、TOML、YAML、HCL 或 envfile)。这些值优先于默认值,但会被从磁盘、标志或环境变量中检索到的配置值覆盖。

Viper 支持多个主机。要使用,请传递以“;”分隔的端点列表。例如,“http://127.0.0.1:4001;http://127.0.0.1:4002”。

Viper 使用 crypt 从键值存储中检索
配置,这意味着您可以将配置值加密存储,并在拥有正确的 gpg 密钥环的情况下自动解密。加密是可选的。

您可以将远程配置与本地配置结合使用,也可以单独使用。

crypt 有一个命令行助手,您可以使用它将配置放入键/值存储中。crypt 默认为 http://127.0.0.1:4001 上的 etcd。

bash 复制代码
$ go get github.com/sagikazarmark/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json

确认您的值已设置:

bash 复制代码
$ crypt get -plaintext /config/hugo.json

有关如何设置加密值或如何使用 Consul 的示例,请参阅 crypt 文档。

远程键值存储示例 - 未加密

etcd

go 复制代码
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 由于字节流中没有文件扩展名,因此支持的扩展名包括 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

etcd3

go 复制代码
viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 由于字节流中没有文件扩展名,因此支持的扩展名包括 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

Consul

您需要将一个键设置为 Consul 键值存储的键,该键的 JSON 值包含您所需的配置。例如,创建一个 Consul 键/值存储键 MY_CONSUL_KEY,其值为:

json 复制代码
{
"port": 8080,
"hostname": "myhostname.com"
}
go 复制代码
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要明确将其设置为 json
err := viper.ReadRemoteConfig()

fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // myhostname.com

Firestore

go 复制代码
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

当然,你也可以使用 SecureRemoteProvider

NATS

go 复制代码
viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config")
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()

远程键值存储示例 - 加密

go 复制代码
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // 因为字节流中没有文件扩展名,所以支持的扩展名是"json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

监视 etcd 中的更改 - 未加密

go 复制代码
// alternatively, you can create a new viper instance.
var runtime_viper = viper.New()

runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"

// read from remote config the first time.
err := runtime_viper.ReadRemoteConfig()

// unmarshal config
runtime_viper.Unmarshal(&runtime_conf)

// open a goroutine to watch remote changes forever
go func(){
	for {
		time.Sleep(time.Second * 5) // delay after each request

		// currently, only tested with etcd support
		err := runtime_viper.WatchRemoteConfig()
		if err != nil {
			log.Errorf("unable to read remote config: %v", err)
			continue
		}

		// unmarshal new config into our runtime config struct. you can also use channel
		// to implement a signal to notify the system of the changes
		runtime_viper.Unmarshal(&runtime_conf)
	}
}()

从 Viper 获取值

在 Viper 中,根据值的类型,有几种获取值的方法。存在以下函数和方法:

  • Get(key string) : any
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]any
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]any

需要注意的是,如果未找到 Get 函数,则返回零值。为了检查给定的键是否存在,我们提供了 IsSet() 方法。

如果该值已设置,但解析失败,也会返回零值。

示例:

go 复制代码
viper.GetString("logfile") // 不区分大小写 设置和获取
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
}

访问嵌套键

访问器方法也接受指向深层嵌套键的格式化路径。例如,如果加载了以下 JSON 文件:

json 复制代码
{
"host": {
"address": "localhost",
"port": 5799
},
"datastore": {
"metric": {
"host": "127.0.0.1",
"port": 3099
},
"warehouse": {
"host": "198.0.0.1",
"port": 2112
}
}
}

Viper 可以通过传递以 . 分隔的键路径来访问嵌套字段:

go 复制代码
GetString("datastore.metric.host") // (返回 "127.0.0.1")

这遵循上面建立的优先规则;对路径的搜索将遍历剩余的配置注册表,直到找到为止。

例如,给定此配置文件,datastore.metric.hostdatastore.metric.port 都已定义(并且可能被覆盖)。如果默认配置中还定义了datastore.metric.protocol,Viper 也会找到它。

但是,如果 datastore.metric 被立即值覆盖(通过标志、环境变量、Set() 方法等),则 datastore.metric 的所有子键都将变为未定义,它们会被更高优先级的配置级别“覆盖”。

Viper 可以通过路径中的数字访问数组索引。例如:

jsonc 复制代码
{
    "host": {
        "address": "localhost",
        "ports": [
            5799,
            6029
        ]
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetInt("host.ports.1") // returns 6029

最后,如果存在与分隔键路径匹配的键,则返回其值。例如

jsonc 复制代码
{
    "datastore.metric.host": "0.0.0.0",
    "host": {
        "address": "localhost",
        "port": 5799
    },
    "datastore": {
        "metric": {
            "host": "127.0.0.1",
            "port": 3099
        },
        "warehouse": {
            "host": "198.0.0.1",
            "port": 2112
        }
    }
}

GetString("datastore.metric.host") // returns "0.0.0.0"

提取子树

在开发可复用模块时,提取配置的子集并将其传递给模块通常很有用。这样,模块就可以使用不同的配置多次实例化。

例如,一个应用可能会出于不同的目的使用多个不同的缓存存储:

yaml 复制代码
cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80

我们可以将缓存名称传递给模块(例如 NewCache("cache1")), 但这需要奇怪的连接来访问配置键,并且与全局配置的分离性较差。

因此,我们不要这样做,而是将 Viper 实例传递给代表配置子集的构造函数:

go 复制代码
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // Sub returns nil if the key cannot be found
	panic("cache configuration not found")
}

cache1 := NewCache(cache1Config)

注意:务必检查 Sub 的返回值。如果找不到键,则返回 nil

在内部,NewCache 函数可以直接处理 max-itemsitem-size 键:

go 复制代码
func NewCache(v *Viper) *Cache {
	return &Cache{
		MaxItems: v.GetInt("max-items"),
		ItemSize: v.GetInt("item-size"),
	}
}

生成的代码很容易测试,因为它与主配置结构分离,并且更容易重用(出于同样的原因)。

解组

您还可以选择将所有值或特定值解组到结构体、映射等中。

有两种方法可以实现这一点:

  • Unmarshal(rawVal any) : error
  • UnmarshalKey(key string, rawVal any) : error

Example:

go 复制代码
type config struct {
	Port int
	Name string
	PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
	t.Fatalf("unable to decode into struct, %v", err)
}

如果您想要解组配置,其中键本身包含点(默认键分隔符), 则必须更改分隔符:

go 复制代码
v := viper.NewWithOptions(viper.KeyDelimiter("::"))

v.SetDefault("chart::values", map[string]any{
	"ingress": map[string]any{
		"annotations": map[string]any{
			"traefik.frontend.rule.type":                 "PathPrefix",
			"traefik.ingress.kubernetes.io/ssl-redirect": "true",
		},
	},
})

type config struct {
	Chart struct{
		Values map[string]any
	}
}

var C config

v.Unmarshal(&C)

Viper 还支持解组为嵌入式结构:

go 复制代码
/*
Example config:

module:
    enabled: true
    token: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
	Module struct {
		Enabled bool

		moduleConfig `mapstructure:",squash"`
	}
}

// moduleConfig could be in a module specific package
type moduleConfig struct {
	Token string
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
	t.Fatalf("unable to decode into struct, %v", err)
}

Viper 在底层使用 github.com/go-viper/mapstructure 来解组值,默认情况下使用 mapstructure 标签。

解码自定义格式

Viper 的一个常见功能是添加更多值格式和解码器。 例如,将字符(点、逗号、分号等)分隔的字符串解析为片段。

这已在 Viper 中使用 mapstructure 解码钩子实现。

请参阅此博客文章 了解更多详细信息。

编组为字符串

您可能需要将 viper 中保存的所有设置编组为字符串,而不是将它们写入文件。
您可以将您喜欢的格式的编组器与 AllSettings() 返回的配置一起使用。

go 复制代码
import (
	yaml "go.yaml.in/yaml/v3"
	// ...
)

func yamlStringSettings() string {
	c := viper.AllSettings()
	bs, err := yaml.Marshal(c)
	if err != nil {
		log.Fatalf("unable to marshal config to YAML: %v", err)
	}
	return string(bs)
}

Viper 还是 Vipers?

Viper 自带一个全局实例(单例),开箱即用。虽然它简化了配置设置,但通常不建议使用它,因为它会增加测试难度,并可能导致意外行为。最佳实践是初始化一个 Viper 实例,并在必要时将其传递。全局实例_可能_在未来被弃用。更多详情,请参阅 #1855

使用多个 Viper

您还可以在应用程序中创建多个不同的 Viper。每个 Viper 都有自己独特的配置和值集。每个 Viper 都可以从不同的配置文件、键值存储等读取数据。viper 包支持的所有函数都以方法的形式映射到 Viper 上。

示例:

go 复制代码
x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

使用多个 viper 时,用户需要跟踪不同的 viper。

问答

为什么叫“Viper”?

答:Viper 的设计目标是成为 Cobra伴侣。虽然两者可以完全独立运行,
但它们结合在一起,可以组成一个强大的组合,满足您大部分的应用程序基础需求。

为什么叫“Cobra”?

有没有更好的 commander 名称?

Viper 支持区分大小写的键吗?

tl;dr: 不支持。

Viper 会合并来自不同来源的配置,其中许多来源要么不区分大小写,要么使用与其他来源不同的大小写(例如环境变量)。为了在使用多个来源时提供最佳体验,我们决定将所有键都设置为不区分大小写。

我们曾多次尝试实现区分大小写,但遗憾的是,这并非易事。我们可能会尝试在 Viper v2 中实现它,但尽管最初有一些争议,但似乎并没有太多人提出这样的要求。

您可以填写此反馈表来投票支持区分大小写:https://forms.gle/R6faU74qPRPAzchZ9

并发读写 viper 是否安全?

不安全,您需要自行同步对 viper 的访问(例如,使用 sync 包)。并发读写可能会导致 panic。

故障排除

请参阅 TROUBLESHOOTING.md

开发

为了获得最佳的开发体验,建议安装 Nixdirenv

或者,在您的计算机上安装 Go,然后运行 ​​make deps 安装其余依赖项。

运行测试套件:

shell 复制代码
make test

运行 linters:

shell 复制代码
make lint # 传递 -j 选项以并行运行它们

一些 linters 违规行为可以自动修复:

shell 复制代码
make fmt

许可证

该项目采用 MIT 许可证 许可。

关于项目

Viper 是一套完整的 Go 应用程序配置解决方案,包括 12-Factor 应用程序。它专为应用程序内部使用而设计,能够处理各种类型的配置需求和格式。
MIT
Golang
29,396
2078
252
2014-04-02
2025-10-09

增长趋势 - stars