go语言超好用配置库viper

带尖牙的Go配置!

许多Go项目都使用Viper构建,包括:

  • Hugo

  • EMC RexRay

  • Imgur的Incus

  • Nanobox
    /
    Nanopack

  • Docker Notary

  • BloomApi

  • doctl

  • Clairctl

  • Mercure

  • Meshery

  • Bearer

  • Coder

  • Vitess

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
go get github.com/spf13/viper
```

**注意**
:Viper使用
Go模块
来管理依赖项。
## 什么是Viper?

Viper是Go应用程序(包括
12要素应用程序
)的完整配置解决方案。它旨在在应用程序中工作,可以处理所有类型的配置需求和格式。它支持:
- 设置默认值

- 从JSON、TOML、YAML、HCL、envfile和Java属性配置文件读取

- 实时监视和重新读取配置文件(可选)

- 从环境变量读取

- 从远程配置系统(etcd或Consul)读取并监视更改

- 从命令行标志读取

- 从缓冲区读取

- 设置显式值

Viper可以被视为满足应用程序所有配置需求的注册表。
## 为什么选择Viper?

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

Viper为您做以下事情:
1. 查找、加载和解组JSON、TOML、YAML、HCL、INI、envfile或Java属性格式的配置文件。

1. 提供一种为不同配置选项设置默认值的机制。

1. 提供一种为通过命令行标志指定的选项设置覆盖值的机制。

1. 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。

1. 轻松区分用户提供的命令行或配置文件与默认值相同的情况。

Viper使用以下优先级顺序:
- 对Set
的显式调用

- 标志

- 环境变量

- 配置

- 键/值存储

- 默认值

**重要**
:Viper配置键不区分大小写。目前正在讨论是否使其可选。
## 向Viper中放入值
### 设置默认值

一个好的配置系统会支持默认值。键不需要默认值,但在键未通过配置文件、环境变量、远程配置或标志设置的情况下很有用。

示例:

viper.SetDefault(“ContentDir”, “content”)viper.SetDefault(“LayoutDir”, “layouts”)viper.SetDefault(“Taxonomies”, map[string]string{“tag”: “tags”, “category”: “categories”})

1
2
3
4
5
### 读取配置文件  

Viper需要最少的配置,以便知道在哪里查找配置文件。Viper支持JSON、TOML、YAML、HCL、INI、envfile和Java属性文件。Viper可以搜索多个路径,但目前单个Viper实例仅支持单个配置文件。Viper不会默认使用任何配置搜索路径,将默认路径的决策留给应用程序。

以下是如何使用Viper搜索和读取配置文件的示例。具体路径都不是必需的,但至少应该提供一个预期存在配置文件的路径。

viper.SetConfigName(“config”) // 配置文件的名称(不带扩展名)viper.SetConfigType(“yaml”) // 如果配置文件的名称中没有扩展名,则此为必需项viper.AddConfigPath(“/etc/appname/”)   // 查找配置文件的路径viper.AddConfigPath(“$HOME/.appname”)  // 多次调用以添加多个搜索路径viper.AddConfigPath(“.”)               // 可选地在工作目录中查找配置err := viper.ReadInConfig() // 查找并读取配置文件if err!= nil { // 处理读取配置文件时的错误 panic(fmt.Errorf(“致命错误配置文件: %w”, err))}

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

if err := viper.ReadInConfig(); err!= nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok {  // 配置文件未找到;如果需要,忽略错误 } else {  // 配置文件已找到但产生了其他错误 }// 配置文件已找到并成功解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  
**注意 [自1.6版本起]**:您也可以有一个不带扩展名的文件并以编程方式指定格式。对于那些位于用户主目录中且没有扩展名(如.bashrc
)的配置文件。
### 写入配置文件

从配置文件读取很有用,但有时您希望存储运行时所做的所有修改。为此,有一系列命令可用,每个命令都有其用途:
- WriteConfig - 将当前Viper配置写入预定义路径(如果存在)。如果没有预定义路径则出错。如果存在,将覆盖当前配置文件。

- SafeWriteConfig - 将当前Viper配置写入预定义路径。如果没有预定义路径则出错。如果存在,不会覆盖当前配置文件。

- WriteConfigAs - 将当前Viper配置写入给定的文件路径。如果存在,将覆盖给定文件。

- SafeWriteConfigAs - 将当前Viper配置写入给定的文件路径。如果存在,不会覆盖给定文件。

一般来说,所有标记为“安全”的操作都不会覆盖任何文件,而只是在不存在时创建,而默认行为是创建或截断。

一个小示例部分:

viper.WriteConfig() // 将当前配置写入由’viper.AddConfigPath()'和’viper.SetConfigName’设置的预定义路径viper.SafeWriteConfig()viper.WriteConfigAs(“/path/to/my/.config”)viper.SafeWriteConfigAs(“/path/to/my/.config”) // 由于已经写入,将出错viper.SafeWriteConfigAs(“/path/to/my/.other_config”)

1
2
3
4
5
6
### 监视和重新读取配置文件  

Viper支持让应用程序在运行时实时读取配置文件的功能。不再需要重启服务器以使配置生效,使用Viper的应用程序可以在运行时读取配置文件的更新,并且不会错过任何操作。只需告诉Viper实例监视配置即可。

可选地,您可以提供一个函数,让Viper在每次发生更改时运行。**确保在调用WatchConfig()之前添加所有配置路径**

viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println(“配置文件已更改:”, e.Name)})viper.WatchConfig()

1
2
3
### 从io.Reader读取配置  

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

viper.SetConfigType(“yaml”) // 或viper.SetConfigType(“YAML”)// 任何将此配置引入程序的方法。var yamlExample = []byte(Hacker: truename: stevehobbies:- skateboarding- snowboarding- goclothing:  jacket: leather  trousers: denimage: 35eyes : brownbeard: true)viper.ReadConfig(bytes.NewBuffer(yamlExample))viper.Get(“name”) // 这将是"steve"

1
2
3
### 设置覆盖值  

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

viper.Set(“Verbose”, true)viper.Set(“LogFile”, LogFile)viper.Set(“host.port”, 5899)   // 设置子集

1
2
3
### 注册和使用别名  

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

viper.RegisterAlias(“loud”, “Verbose”)viper.Set(“verbose”, true) // 与下一行结果相同viper.Set(“loud”, true)   // 与上一行结果相同viper.GetBool(“loud”) // trueviper.GetBool(“verbose”) // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
### 使用环境变量  

Viper完全支持环境变量。这使得12要素应用程序开箱即用。有五种方法可帮助处理环境变量:
- AutomaticEnv()

- BindEnv(string...) : error

- SetEnvPrefix(string)

- SetEnvKeyReplacer(string...) *strings.Replacer

- AllowEmptyEnv(bool)

**在处理环境变量时,重要的是要认识到Viper将环境变量视为区分大小写的。**

Viper提供了一种机制来尝试确保环境变量是唯一的。通过使用SetEnvPrefix
,您可以告诉Viper在从环境变量读取时使用前缀。BindEnv
和AutomaticEnv
都会使用此前缀。

BindEnv
接受一个或多个参数。第一个参数是键名,其余的是要绑定到此键的环境变量名。如果提供了多个,它们将按照指定的顺序具有优先级。环境变量的名称是区分大小写的。如果未提供环境变量名,那么Viper将自动假定环境变量匹配以下格式:前缀 + "_" + 键名(全部大写)。当您显式提供环境变量名(第二个参数)时,它**不会**
自动添加前缀。例如,如果第二个参数是"id",Viper将查找环境变量"ID"

在处理环境变量时,要认识到的一件重要事情是,每次访问值时都会读取该值。当调用BindEnv
时,Viper不会固定该值。

AutomaticEnv
是一个强大的助手,特别是与SetEnvPrefix
结合使用时。调用时,Viper将在每次进行viper.Get
请求时检查环境变量。它将应用以下规则。它将检查名称与键大写并带有EnvPrefix
(如果设置)前缀的环境变量。

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

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

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

SetEnvPrefix(“spf”) // 将自动大写BindEnv(“id”)os.Setenv(“SPF_ID”, “13”) // 通常在应用程序外部完成id := Get(“id”) // 13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### 使用标志  

Viper能够绑定到标志。具体来说,Viper支持在
Cobra
库中使用的Pflags


与BindEnv
一样,绑定方法调用时不会设置值,而是在访问时设置。这意味着您可以尽早绑定,甚至在init()
函数中。

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

示例:

serverCmd.Flags().Int(“port”, 1138, “运行应用程序服务器的端口”)viper.BindPFlag(“port”, serverCmd.Flags().Lookup(“port”))

1
2
3
4
5
6
  
您还可以绑定现有的一组pflags
(pflag.FlagSet
):

示例:

pflag.Int(“flagname”, 1234, “flagname的帮助消息”)pflag.Parse()viper.BindPFlags(pflag.CommandLine)i := viper.GetInt(“flagname”) // 从Viper而不是pflag中检索值

1
2
3
4
5
6
7
8
9
10
11
12
  
Viper中使用
pflag
并不排除使用标准库中使用
flag
包的其他包。pflag
包可以通过导入这些标志来处理为flag
包定义的标志。这是通过调用pflag
包提供的名为AddGoFlagSet()
的便利函数来完成的。

示例:

package mainimport (“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中检索值//…}

1
2
3
4
5
6
7
#### 标志接口  

如果您不使用Pflags
,Viper提供了两个Go接口来绑定其他标志系统。

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” }

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

viper.BindFlagValue(“my-flag-name”, myFlag{})

1
2
3
  
FlagValueSet
表示一组标志。这是一个实现此接口的非常简单的示例:

type myFlagSet struct { flags []myFlag}func (f myFlagSet) VisitAll(fn func(FlagValue)) { for _, flag := range flags {  fn(flag) }}

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

fSet := myFlagSet{ flags: []myFlag{myFlag{}, myFlag{}},}viper.BindFlagValues(“my-flags”, fSet)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
### 远程键值存储支持  

要在 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。

$ go get github.com/sagikazarmark/crypt/bin/crypt$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json

1
2
  
确认已设置值:

$ crypt get -plaintext /config/hugo.json

1
2
3
4
5
  
有关如何设置加密值或如何使用 Consul 的示例,请参阅 crypt
 文档。
### 远程键值存储示例 - 未加密
#### etcd

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()

1
#### etcd3  

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()

1
2
3
4
#### Consul  

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

{    “port”: 8080,    “hostname”: “myhostname.com”}

1
2
3
4
```
viper.AddRemoteProvider("consul""localhost:8500""MY_CONSUL_KEY")viper.SetConfigType("json") // 需要显式将其设置为 jsonerr := viper.ReadRemoteConfig()fmt.Println(viper.Get("port")) // 8080fmt.Println(viper.Get("hostname")) // myhostname.com
```
#### Firestore

viper.AddRemoteProvider(“firestore”, “google-cloud-project-id”, “collection/document”)viper.SetConfigType(“json”) // 配置格式:“json”、“toml”、“yaml”、"yml"err := viper.ReadRemoteConfig()

1
2
3
4
  
当然,你也可以使用 SecureRemoteProvider

#### NATS

viper.AddRemoteProvider(“nats”, “nats://127.0.0.1:4222”, “myapp.config”)viper.SetConfigType(“json”)err := viper.ReadRemoteConfig()

1
### 远程键值存储示例 - 加密  

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()

1
### 监控 etcd 中的更改 - 未加密  

// 或者,你可以创建一个新的 viper 实例。var runtime_viper = viper.New()runtime_viper.AddRemoteProvider(“etcd”, “http://127.0.0.1:4001”, “/config/hugo.yml”)runtime_viper.SetConfigType(“yaml”) // 因为字节流中没有文件扩展名,支持的扩展名有 “json”、“toml”、“yaml”、“yml”、“properties”、“props”、“prop”、“env”、“dotenv”// 首次从远程配置读取。err := runtime_viper.ReadRemoteConfig()// 反序列化配置runtime_viper.Unmarshal(&runtime_conf)// 打开一个 goroutine 以永远监控远程更改gofunc(){for {  time.Sleep(time.Second * 5) // 每次请求后延迟// 当前,仅在 etcd 支持下进行了测试  err := runtime_viper.WatchRemoteConfig()if err!= nil {   log.Errorf(“无法读取远程配置:%v”, err)   continue  }// 将新配置反序列化到我们的运行时配置结构体中。你也可以使用通道// 来实现一个信号,以通知系统更改  runtime_viper.Unmarshal(&runtime_conf) }}()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
### 从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()
方法。如果值已设置但无法解析为请求的类型,也将返回零值。

示例:

viper.GetString(“logfile”) // 不区分大小写的设置和获取if viper.GetBool(“verbose”) { fmt.Println(“verbose enabled”)}

1
2
3
### 访问嵌套键  

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

{    “host”: {        “address”: “localhost”,        “port”: 5799    },    “datastore”: {        “metric”: {            “host”: “127.0.0.1”,            “port”: 3099        },        “warehouse”: {            “host”: “198.0.0.1”,            “port”: 2112        }    }}

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

GetString(“datastore.metric.host”) // (返回"127.0.0.1")

1
2
3
4
5
6
7
8
9
10
11
12
  
这遵循上述建立的优先级规则;对路径的搜索将通过剩余的配置注册表级联,直到找到为止。

例如,对于此配置文件,datastore.metric.host
和datastore.metric.port
都已定义(并且可能被覆盖)。如果此外datastore.metric.protocol
在默认值中定义,Viper也会找到它。但是,如果datastore.metric
被标志、环境变量、Set()
方法等更高优先级的配置覆盖为直接值,那么datastore.metric
的所有子键都将变为未定义,它们被更高优先级的配置级别“遮蔽”。

Viper可以通过在路径中使用数字来访问数组索引。例如:

{    “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”) // 返回6029

1
2
  
最后,如果存在与分隔键路径匹配的键,其值将被返回。例如:

{    “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”) // 返回"0.0.0.0"

1
2
3
4
5
### 提取子树  

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

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

cache:  cache1:    max-items: 100    item-size: 64  cache2:    max-items: 200    item-size: 80

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

因此,我们改为将表示配置子集的Viper实例传递给构造函数:

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)

1
2
3
4
5
6
7
8
9
10
  
**注意**
:始终检查Sub
的返回值。如果找不到键,它将返回nil


在内部,NewCache
函数可以直接访问max-items
和item-size
键:

func NewCache(v *Viper) *Cache { return &Cache{  MaxItems: v.GetInt(“max-items”),  ItemSize: v.GetInt(“item-size”), }}

1
2
3
4
5
6
7
8
9
10
11
12
  
生成的代码易于测试,因为它与主配置结构解耦,并且更易于重用(出于同样的原因)。
### 解组

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

有两种方法可以做到这一点:
- Unmarshal(rawVal any) : error

- UnmarshalKey(key string, rawVal any) : error

示例:

type config struct { Port int Name string PathMap string mapstructure:"path_map"}var C configerr := viper.Unmarshal(&C)if err != nil { t.Fatalf(“unable to decode into struct, %v”, err)}

1
2
  
如果要解组键本身包含点(默认键分隔符)的配置,则必须更改分隔符:

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 configv.Unmarshal(&C)

1
2
  
Viper还支持解组到嵌入结构体中:

/Example config:module:    enabled: true    token: 89h3f98hbwf987h3f98wenf89ehf/type config struct { Module struct {  Enabled bool  moduleConfig mapstructure:",squash" }}// moduleConfig could be in a module specific packagetype moduleConfig struct { Token string}var C configerr := viper.Unmarshal(&C)if err != nil { t.Fatalf(“unable to decode into struct, %v”, err)}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  
Viper在底层使用
github.com/go-viper/mapstructure
进行解组值,默认使用mapstructure
标签。
### 解码自定义格式

Viper一个经常被请求的功能是添加更多的值格式和解码器。例如,将字符(点、逗号、分号等)分隔的字符串解析为切片。这在Viper中已经可以使用mapstructure
解码钩子实现。在
这篇博客文章
中阅读更多详细信息。
### 编组为字符串

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

import ( yaml “gopkg.in/yaml.v2”// …)func yamlStringSettings() string { c := viper.AllSettings() bs, err := yaml.Marshal©if err != nil {  log.Fatalf(“unable to marshal config to YAML: %v”, err) }returnstring(bs)}

1
2
3
4
5
6
7
8
9
10
## Viper还是Vipers?  

Viper开箱即用带有一个全局实例(单例)。虽然它使配置设置变得容易,但通常不鼓励使用它,因为它使测试更加困难并可能导致意外行为。最佳实践是初始化一个Viper实例,并在必要时传递它。全局实例将来可能会被弃用。有关更多详细信息,请参阅
#1855

### 使用多个Viper实例

您还可以在应用程序中创建许多不同的Viper实例。每个实例都将有其自己独特的配置和值集。每个实例可以从不同的配置文件、键值存储等读取。Viper包支持的所有函数都作为Viper实例的方法进行镜像。

示例:

x := viper.New()y := viper.New()x.SetDefault(“ContentDir”, “content”)y.SetDefault(“ContentDir”, “foobar”)//…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  
在使用多个Viper实例时,由用户来跟踪不同的Viper实例。
## 问答
### 为什么它被称为“Viper”?

答:Viper旨在成为
Cobra

伙伴
。虽然两者可以完全独立运行,但它们一起构成了强大的组合,可以处理您应用程序基础的许多需求。
### 为什么它被称为“Cobra”?

对于
指挥官
来说,有更好的名字吗?
### Viper支持区分大小写的键吗?

**简而言之**
:不支持。

Viper合并来自各种源的配置,其中许多源要么不区分大小写,要么使用与其他源不同的大小写(例如环境变量)。为了在使用多个源时提供最佳体验,已决定使所有键不区分大小写。已经有几次尝试实现区分大小写,但不幸的是,这并不那么简单。我们可能会在
Viper v2
中尝试实现它,但尽管最初有呼声,但似乎对此的需求并没有那么大。您可以通过填写此反馈表为区分大小写投票:https://forms.gle/R6faU74qPRPAzchZ9。
### 并发读写Viper安全吗?

不安全,您需要自己同步对Viper的访问(例如使用sync
包)。并发读写可能会导致恐慌。
## 故障排除

请参阅
TROUBLESHOOTING.md

## 开发

**为了获得最佳开发体验,建议安装Nix和direnv。**
或者,在您的计算机上安装
Go
,然后运行make deps
来安装其余的依赖项。

运行测试套件:

make test

1
2
  
运行代码检查工具:

make lint # 传递-j选项以并行运行它们

1
2
  
一些代码检查违规可以自动修复:

make fmt

## 许可证  
  
该项目根据  
MIT许可证  
授权。  
  
  

![江达小记](/images/wechatmpscan.png)