spf13/viper

GitHub: spf13/viper

Go 语言中最流行的完整配置解决方案,统一管理配置文件、环境变量、命令行参数和远程配置源。

Stars: 30122 | Forks: 2099

![viper logo](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/288a0f0aa6142100.png) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration) [![run on repl.it](https://repl.it/badge/github/sagikazarmark/Viper-example)](https://repl.it/@sagikazarmark/Viper-example#main.go) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spf13/viper/ci.yaml?branch=master&style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/viper?style=flat-square)](https://goreportcard.com/report/github.com/spf13/viper) ![Go Version](https://img.shields.io/badge/go%20version-%3E=1.23-61CFDD.svg?style=flat-square) [![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/spf13/viper)](https://pkg.go.dev/mod/github.com/spf13/viper) **带有獠牙的 Go 配置方案!** 许多 Go 项目都使用 Viper 构建,包括: * [Hugo](http://gohugo.io) * [EMC RexRay](http://rexray.readthedocs.org/en/stable/) * [Imgur’s Incus](https://github.com/Imgur/incus) * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) * [Docker Notary](https://github.com/docker/Notary) * [BloomApi](https://www.bloomapi.com/) * [doctl](https://github.com/digitalocean/doctl) * [Clairctl](https://github.com/jgsqware/clairctl) * [Mercure](https://mercure.rocks) * [Meshery](https://github.com/meshery/meshery) * [Bearer](https://github.com/bearer/bearer) * [Coder](https://github.com/coder/coder) * [Vitess](https://vitess.io/) ## 安装 ``` go get github.com/spf13/viper ``` ## 为什么使用 Viper? Viper 是一个完整的 Go 应用程序配置解决方案,包括 [12-Factor apps](https://12factor.net/#the_twelve_factors)。它被设计为可以在任何应用程序中工作,并能处理所有类型的配置需求和格式。它支持: * 设置默认值 * 设置显式值 * 读取配置文件 * 动态发现跨多个位置的配置文件 * 从环境变量读取 * 从远程系统读取(例如 Etcd 或 Consul) * 从命令行标志读取 * 从缓冲区读取 * 实时监控和更新配置 * 为配置键设置别名以便于重构 Viper 可以被视为满足您应用程序所有配置需求的注册表。 ## 将值存入 Viper Viper 可以从多个配置源读取数据,并将它们合并为一组配置键值对。 Viper 使用以下优先级顺序进行合并: * 显式调用 `Set` * flags * 环境变量 * 配置文件 * 外部键/值存储 * 默认值 ### 读取配置文件 Viper 加载配置文件只需最少的配置。Viper 目前支持: * JSON * TOML * YAML * INI * envfile * Java Propeties 单个 Viper 实例仅支持单个配置文件,但可以搜索多个路径以查找该文件。 以下是如何使用 Viper 搜索和读取配置文件的示例。应至少提供一个预期存在配置文件的路径。 ``` // Name of the config file without an extension (Viper will intuit the type // from an extension on the actual file) viper.SetConfigName("config") // Add search paths to find the file viper.AddConfigPath("/etc/appname/") viper.AddConfigPath("$HOME/.appname") viper.AddConfigPath(".") // Find and read the config file err := viper.ReadInConfig() // Handle errors if err != nil { panic(fmt.Errorf("fatal error config file: %w", err)) } ``` 您可以处理找不到配置文件的特定情况。 ``` var fileLookupError viper.FileLookupError if err := viper.ReadInConfig(); err != nil { if errors.As(err, &fileLookupError) { // Indicates an explicitly set config file is not found (such as with // using `viper.SetConfigFile`) or that no config file was found in // any search path (such as when using `viper.AddConfigPath`) } else { // Config file was found but another error was produced } } // Config file found and successfully parsed ``` ### 写入配置文件 有时您可能希望存储运行时所做的所有配置修改。 ``` // Writes current config to the path set by `AddConfigPath` and `SetConfigName` viper.WriteConfig() viper.SafeWriteConfig() // Like the above, but will error if the config file exists // Writes current config to a specific place viper.WriteConfigAs("/path/to/my/.config") // Will error since it has already been written viper.SafeWriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.other_config") ``` 根据经验,带有 `Safe` 前缀的方法不会覆盖任何现有文件,而其他方法则会。 ### 监控和重新读取配置文件 需要重启服务器才能使配置生效的日子一去不复返了——由 Viper 驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何节拍。 也可以提供一个函数,供 Viper 在每次发生更改时运行。 ``` // All config paths must be defined prior to calling `WatchConfig()` viper.AddConfigPath("$HOME/.appname") viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) }) viper.WatchConfig() ``` ### 从 `io.Reader` 读取配置 Viper 预定义了许多配置源,但您也可以实现自己所需的配置源。 ``` viper.SetConfigType("yaml") var yamlExample = []byte(` hacker: true hobbies: - skateboarding - snowboarding - go name: steve `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) viper.Get("name") // "steve" ``` ### 设置默认值 一个好的配置系统将支持默认值,如果某个键没有通过其他方式设置,则会使用默认值。 示例: ``` viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) ``` ### 设置覆盖值 Viper 允许显式设置配置,例如来自您自己的应用程序逻辑。 ``` viper.Set("verbose", true) viper.Set("host.port", 5899) // Set an embedded key ``` ### 注册和使用别名 别名允许通过多个键引用单个值 ``` viper.RegisterAlias("loud", "Verbose") viper.Set("verbose", true) // Same result as next line viper.Set("loud", true) // Same result as prior line viper.GetBool("loud") // true viper.GetBool("verbose") // true ``` ### 使用环境变量 Viper 完全支持环境变量。 ``` // Tells Viper to use this prefix when reading environment variables viper.SetEnvPrefix("spf") // Viper will look for "SPF_ID", automatically uppercasing the prefix and key viper.BindEnv("id") // Alternatively, we can search for any environment variable prefixed and load // them in viper.AutomaticEnv() os.Setenv("SPF_ID", "13") id := viper.Get("id") // 13 ``` * 默认情况下,空环境变量被视为未设置,并将回退到下一个配置源,除非使用了 `AllowEmptyEnv`。 * Viper 不会“缓存”环境变量——每次访问时都会读取该值。 * `SetEnvKeyReplacer` 和 `EnvKeyReplacer` 允许您重写环境变量键,这对于将 SCREAMING_SNAKE_CASE 环境变量与来自其他来源的 kebab-cased 配置值合并非常有用。 ### 使用 Flags Viper 能够绑定到 flags。具体来说,Viper 支持 [Cobra](https://github.com/spf13/cobra) 库中使用的 [pflag](https://github.com/spf13/pflag/)。 与环境变量一样,值不是在调用绑定方法时设置的,而是在访问时设置的。 对于单个 flags,`BindPFlag` 方法提供了此功能。 ``` serverCmd.Flags().Int("port", 1138, "Port to run Application server on") viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) ``` 您还可以绑定一组现有的 pflags。 ``` pflag.Int("flagname", 1234, "help message for flagname") pflag.Parse() viper.BindPFlags(pflag.CommandLine) i := viper.GetInt("flagname") // Retrieve values from viper instead of pflag ``` 标准库 [flag](https://golang.org/pkg/flag/) 包不被直接支持,但可以通过 pflag 进行解析。 ``` package main import ( "flag" "github.com/spf13/pflag" ) func main() { // Using standard library "flag" package flag.Int("flagname", 1234, "help message for flagname") // Pass standard library flags to pflag pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() // Viper takes over viper.BindPFlags(pflag.CommandLine) } ``` 可以通过实现 `FlagValue` 和 `FlagValueSet` 接口来完全避免使用 pflag。 ``` // Implementing FlagValue 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.BindFlagValue("my-flag-name", myFlag{}) // Implementing FlagValueSet type myFlagSet struct { flags []myFlag } func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags { fn(flag) } } fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}}, } viper.BindFlagValues("my-flags", fSet) ``` ### 远程 Key/Value 存储支持 要在 Viper 中启用远程支持,请对 `viper/remote` 包进行空白导入。 ``` import _ "github.com/spf13/viper/remote" ``` Viper 支持以下远程键/值存储。下面提供了每种存储的示例。 * Etcd 和 Etcd3 * Consul * Firestore * NATS Viper 将读取从键/值存储中的路径检索到的配置字符串。 Viper 支持用 `;` 分隔的多个主机。例如:`http://127.0.0.1:4001;http://127.0.0.1:4002`。 #### 加密 Viper 使用 [crypt](https://github.com/sagikazarmark/crypt) 从键/值存储中检索配置,这意味着您可以加密存储配置值,如果您拥有正确的 GPG 密钥环,它们将自动解密。加密是可选的。 Crypt 有一个命令行助手,您可以使用它将配置放入您的键/值存储中。 ``` $ go get github.com/sagikazarmark/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json $ crypt get -plaintext /config/hugo.json ``` 有关如何设置加密值或如何使用 Consul 的示例,请参阅 Crypt 文档。 ### 远程 Key/Value 存储示例(未加密) #### etcd ``` viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` #### etcd3 ``` viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` #### Consul 给定一个 Consul 键 `MY_CONSUL_KEY`,其值为: ``` { "port": 8080, "hostname": "myhostname.com" } ``` ``` viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") viper.SetConfigType("json") // Need to explicitly set this to json err := viper.ReadRemoteConfig() fmt.Println(viper.Get("port")) // 8080 ``` #### Firestore ``` viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" err := viper.ReadRemoteConfig() ``` 当然,您也可以使用 `SecureRemoteProvider`。 #### NATS ``` viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config") viper.SetConfigType("json") err := viper.ReadRemoteConfig() ``` ### 远程 Key/Value 存储示例(加密) ``` viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` ### 监控 Key/Value 存储更改 ``` // 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 runtime_viper.Unmarshal(&runtime_conf) } }() ``` ## 从 Viper 获取值 从 Viper 检索配置值的最简单方法是使用 `Get*` 函数。`Get` 将返回一个 any 类型,但可以使用 `Get` 函数检索特定类型。 请注意,如果找不到键,每个 `Get*` 函数将返回零值。要检查键是否存在,请使用 `IsSet` 方法。 嵌套键使用 `.` 作为分隔符,数字用于数组索引。给定以下配置: ``` { "datastore": { "metric": { "host": "127.0.0.1", "ports": [ 5799, 6029 ] } } } ``` ``` GetString("datastore.metric.host") // "127.0.0.1" GetInt("host.ports.1") // 6029 ``` 如果存在与分隔键路径匹配的键,则返回其值。 ``` { "datastore.metric.host": "0.0.0.0", "datastore": { "metric": { "host": "127.0.0.1" } } } ``` ``` GetString("datastore.metric.host") // "0.0.0.0" ``` ### 配置子集 提取配置的子集通常很有用(例如,在开发应接受特定配置部分的可重用模块时)。 ``` cache: cache1: item-size: 64 max-items: 100 cache2: item-size: 80 max-items: 200 ``` ``` func NewCache(v *Viper) *Cache { return &Cache{ ItemSize: v.GetInt("item-size"), MaxItems: v.GetInt("max-items"), } } 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) ``` ### 反序列化 您还可以选择使用 `Unmarshal*` 方法将配置反序列化为结构体、map 等。 ``` 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) } ``` 如果您想反序列化键本身包含 `.`(默认键分隔符)的配置,您可以更改分隔符。 ``` 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 还支持反序列化为嵌入结构体。 ``` /* Example config: module: enabled: true token: 89h3f98hbwf987h3f98wenf89ehf */ type config struct { Module struct { Enabled bool moduleConfig `mapstructure:",squash"` } } 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](https://github.com/go-viper/mapstructure) 来反序列化值,默认情况下使用 `mapstructure` 标签。 ### 编组为字符串 您可能需要将 Viper 中保存的所有设置编组为字符串。您可以将您喜欢的格式的编组器与 `AllSettings` 返回的配置一起使用。 ``` 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 中通过 mapstructure 解码钩子实现了。 在[此博客文章](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/)中阅读更多信息。 ## 常见问题解答 ### 为什么叫“Viper”? Viper 被设计为 [Cobra](https://github.com/spf13/cobra) 的[伙伴](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))。虽然两者都可以完全独立运行,但它们一起构成了一对强大的组合,可以处理您大部分的应用程序基础需求。 ### 我发现了一个错误或想要一个功能,我应该提交 issue 还是 PR? 是的,但有两点需要注意。 1. Viper 项目目前优先考虑向后兼容性和稳定性,而不是功能。 2. 功能可能会推迟到 Viper 2 形成之后。 ### 可以使用多个 Viper 实例吗? **tl;dr:** 可以。 每个实例都有自己独特的配置,并且可以从不同的配置源读取。Viper 包支持的所有函数都作为方法镜像在 Viper 实例上。 ``` x := viper.New() y := viper.New() x.SetDefault("ContentDir", "content") y.SetDefault("ContentDir", "foobar") ``` ### Viper 应该是全局单例还是传递使用? 最佳实践是初始化一个 Viper 实例并在必要时传递它。 Viper 开箱即带有一个全局实例(单例)。虽然它使设置配置变得容易,但通常不鼓励使用它,因为它会使测试变得更难并可能导致意外行为。 全局实例将来可能会被弃用。有关更多详细信息,请参阅 [#1855](https://github.com/spf13/viper/issues/1855)。 ### Viper 支持区分大小写的键吗? **tl;dr:** 不支持。 Viper 合并来自各种来源的配置,其中许多要么不区分大小写,要么使用与其他来源不同的大小写(例如,env vars)。为了在使用多个来源时提供最佳体验,所有键都不区分大小写。 曾有过几次尝试实现区分大小写,但不幸的是这并非易事。我们可能会尝试在 [Viper v2](https://github.com/spf13/viper/issues/772) 中实现它,但尽管最初有噪音,似乎并没有那么多人要求它。 您可以通过填写此反馈表为区分大小写投票:https://forms.gle/R6faU74qPRPAzchZ9。 ### 并发读写 Viper 实例安全吗? 不安全,您需要自己同步对 Viper 的访问(例如通过使用 `sync` 包)。并发读写会导致 panic。 ## 故障排除 请参阅 [TROUBLESHOOTING.md](TROUBLESHOOTING.md)。 ## 开发 **为了获得最佳的开发者体验,建议安装 [Nix](https://nixos.org/download.html) 和 [direnv](https://direnv.net/docs/installation.html)。** _或者,在您的计算机上安装 [Go](https://go.dev/dl/),然后运行 `make deps` 以安装其余的依赖项。_ 运行测试套件: ``` make test ``` 运行 linters: ``` make lint # pass -j option to run them in parallel ``` 一些 linter 违规行为可以自动修复: ``` make fmt ``` ## 许可证 该项目根据 [MIT License](LICENSE) 授权。
标签:12-Factor应用, EVTX分析, Go语言, JSON解析, spf13, TOML支持, Viper, YAML解析, 后端开发, 实时重载, 应用程序配置, 开发库, 恶意代码分析, 日志审计, 环境变量, 程序破解, 软件供应链组件, 配置文件, 配置解决方案