【golang】13、viper 配置库 | 配置文件读写 | 使用方式 | 源码逻辑分析


文章目录

  • 一、使用方式
    • 1.1 特性
    • 1.2 优势
    • 1.3 设置
      • 1.3.1 默认值
      • 1.3.2 配置文件
      • 1.3.3 写配置文件
      • 1.3.4 监听配置文件变化
      • 1.3.5 从 io.Reader 读配置
      • 1.3.6 Setting Overrides
      • 1.3.7 使用 Alias
      • 1.3.8 环境变量
      • 1.3.9 命令行 Flags
        • 1.3.8.1 Flag 接口
      • 1.3.9 配置中心
        • 1.3.9.1 未加密
        • 1.3.9.2 加密
        • 1.3.9.3 监听变化
    • 1.4 读取
      • 1.4.1 嵌套式获取
      • 1.4.2 数组下标访问
      • 1.4.3 显式的 delimited key path
      • 1.4.4 解析子树
  • 二、源码
    • 2.1 定义 Error
    • 2.2 Viper struct 定义
    • 2.3 构造:New 和 函数式 Options
    • 2.4 Reset
    • 2.5 配置中心
      • 2.5.1 接口定义
      • 2.5.2 添加 RemoteProvider
    • 2.6 监听配置变化
    • 2.7 文件设置
    • 2.8 环境变量
      • 2.8.1 配置
      • 2.8.2 绑定
      • 2.8.3 环境变量覆盖配置文件
    • 2.9 搜索 key
    • 2.10 Get 获取一个 key 的值
    • 2.11 Unmarshal
    • 2.12 BindPFlag
    • 2.13 关键逻辑 find()
    • 2.14 alias bieming
    • 2.15 读文件
    • 2.16 单测

viper 是由 spf13 作者写的,这个作者很强大,很多顶级库,

而整个代码也只有 5400 行,目的很纯粹,一直在更新,所以很适合读源码

一、使用方式

1.1 特性

viper 支持任意配置文件格式,有如下特性:

  • 默认配置
  • JSON, TOML, YAML, HCL, envfile
  • live watching 配置文件变化
  • 监听配置中心(etcd、consul)
  • 读取命令行参数
  • 读取 buffer
  • setting explicit values(设置显式值)

1.2 优势

开发应用服务时,不希望关注异构的配置文件格式,而希望更专注业务逻辑。viper 支持如下特性:

  • find, load, unmarshal 配置文件
  • 支持 set default values
  • 支持覆盖配置项(override values)
  • 提供 alias system,可以方便地 rename parameters without breaking existing code
  • 当用户提供了与默认设置相同的命令行或配置文件时,可以很容易地区分它们。

viper 优先级从高到低如下:

  • 显式调用 Set()
  • flag
  • env
  • config
  • key/value store
  • default

注意:viper 不区分大小写,社区对此是有争论的

1.3 设置

1.3.1 默认值

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

1.3.2 配置文件

目前,一个 viper 对象,只能读一个配置文件。但可以配置多个搜索路径。Viper 不默认任何配置搜索路径,将默认决定留给应用程序。示例如下:

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

可通过如下方式处理指定的错误:

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.3.3 写配置文件

有如下四个方法:

  • WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists.
  • SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists.
  • WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists.
  • SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists.
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")

1.3.4 监听配置文件变化

监听变化,可以使进程无需重启

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

1.3.5 从 io.Reader 读配置

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

1.3.6 Setting Overrides

可能来自命令行参数,或应用逻辑

viper.Set("Verbose", true)
viper.Set("LogFile", logFile)
viper.Set("host.port", 5899) // set subset

1.3.7 使用 Alias

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

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

1.3.8 环境变量

有如下五种方法:

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

viper 处理环境变量时,不区分大小写。

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

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

// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
// If more arguments are provided, they will represent the env variable names that
// should bind to this key and will be taken in the specified order.
// EnvPrefix will be used when set when env name is not provided.
func BindEnv(input ...string) error { return v.BindEnv(input...) }

func (v *Viper) BindEnv(input ...string) error {
	if len(input) == 0 {
		return fmt.Errorf("missing key to bind to")
	}

	key := strings.ToLower(input[0])

	if len(input) == 1 {
		v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))
	} else {
		v.env[key] = append(v.env[key], input[1:]...)
	}

	return nil
}

在使用ENV变量时需要注意的一件重要事情是,每次访问时都会读取值。在调用BindEnv时,Viper不固定该值。

AutomaticEnv是一个强大的助手,特别是与SetEnvPrefix结合使用时。当调用时,Viper将在任何时候发出viper.Get请求时检查环境变量。它将适用以下规则。它将检查一个环境变量,该变量的名称与升级后的键相匹配,如果设置了,则使用EnvPrefix作为前缀。

SetEnvKeyReplacer允许您在一定程度上使用strings.Replacer对象重写环境密钥。如果您希望在Get()调用中使用-或其他名称,但又希望您的环境变量使用_分隔符,则此功能非常有用。在viper_test.go中可以找到使用它的示例。

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

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

示例如下:

SetEnvPrefix("spf") // will be uppercased automatically
BindEnv("id")

os.Setenv("SPF_ID", "13") // typically done outside of the app

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

1.3.9 命令行 Flags

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

serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))

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

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

PFLAG包可以通过导入这些标志来处理为FLAG包定义的标志。这是通过调用由名为AddGoFlagSet()的PFLAG包提供的便利函数来实现的。

package main

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

func main() {

	// using standard library "flag" package
	flag.Int("flagname", 1234, "help message for flagname")

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

	i := viper.GetInt("flagname") // retrieve value from viper

	// ...
}
1.3.8.1 Flag 接口

Viper provides two Go interfaces to bind other flag systems if you don’t use Pflags.

FlagValue represents a single flag. This is a very simple example on how to implement this interface:

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

Once your flag implements this interface, you can simply tell Viper to bind it:

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

FlagValueSet represents a group of flags. This is a very simple example on how to implement this interface:

type myFlagSet struct {
	flags []myFlag
}

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

Once your flag set implements this interface, you can simply tell Viper to bind it:

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

1.3.9 配置中心

viper 使用crypt从 K/V 存储检索配置,这意味着您可以存储加密的配置值,如果您拥有正确的GPG密钥环,则会自动解密它们。加密是可选的。

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

下文以 etcd 为例

1.3.9.1 未加密
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()
1.3.9.2 加密
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001", "/config/hugo.json", "/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()
1.3.9.3 监听变化
// 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)
	}
}()

1.4 读取

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

One important thing to recognize is that each Get function will return a zero value if it’s not found. To check if a given key exists, the IsSet() method has been provided.

viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
	fmt.Println("verbose enabled")
}

1.4.1 嵌套式获取

{
    "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 "127.0.0.1")

1.4.2 数组下标访问

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") // returns 6029

1.4.3 显式的 delimited key path

如果有显式的 delimited key path,它的优先级最高

{
    "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"

1.4.4 解析子树

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

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

cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
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)
func NewCache(v *Viper) *Cache {
	return &Cache{
		MaxItems: v.GetInt("max-items"),
		ItemSize: v.GetInt("item-size"),
	}
}

二、源码

viper.go 有 1500 行,.viper_test.go 有 2100 行,主要是这两个文件

viper.go 引用了如下库

"github.com/fsnotify/fsnotify"	 // 监听文件系统通知 9k 
"github.com/mitchellh/mapstructure" // 通过 struct tag, 从 map[string]interface{} 解析到 struct 7k
slog "github.com/sagikazarmark/slog-shim" // 日志 已被合入 go 1.21
"github.com/spf13/afero" // 文件系统抽象 5k
"github.com/spf13/cast" // 类型强转 3k
"github.com/spf13/pflag" // 命令行参数 2k

首先实例化一个 package 级别的对象,并在 package 的 init() 方法实例化它:

var v *Viper
func init() {
	v = New()
}

2.1 定义 Error

然后定义了 6 种 Error()

// 1. 因为要存序列化的 err, 所以定义为 struct
// ConfigMarshalError happens when failing to marshal the configuration.
type ConfigMarshalError struct {
	err error
}

// 2. 不支持的配置类型, 直接返回 配置类型 string 即可
// UnsupportedConfigError denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string

// 3. 不支持的远端配置类型, 当前只支持 etcd 和 consul
// UnsupportedRemoteProviderError denotes encountering an unsupported remote
// provider. Currently only etcd and Consul are supported.
type UnsupportedRemoteProviderError string

// 4. 从配置中心拉配置,的错误(配置中心内部的错误,或网络)
// RemoteConfigError denotes encountering an error while trying to
// pull the configuration from the remote provider.
type RemoteConfigError string

// 5. 文件不存在,最常见的错误,包含文件名和路径
// ConfigFileNotFoundError denotes failing to find configuration file.
type ConfigFileNotFoundError struct {
	name, locations string
}

// 6. 配置文件已经存在,因为 viper 支持回写配置文件时,是否覆盖的选项
// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
type ConfigFileAlreadyExistsError string

2.2 Viper struct 定义

定义顶层的 Viper struct:

// Viper is a prioritized configuration registry. It
// maintains a set of configuration sources, fetches
// values to populate those, and provides them according
// to the source's priority.
// The priority of the sources is the following:
// 1. overrides
// 2. flags
// 3. env. variables
// 4. config file
// 5. key/value store
// 6. defaults
//
// For example, if values from the following sources were loaded:
//
//	Defaults : {
//		"secret": "",
//		"user": "default",
//		"endpoint": "https://localhost"
//	}
//	Config : {
//		"user": "root"
//		"secret": "defaultsecret"
//	}
//	Env : {
//		"secret": "somesecretkey"
//	}
//
// The resulting config will have the following values:
//
//	{
//		"secret": "somesecretkey",
//		"user": "root",
//		"endpoint": "https://localhost"
//	}
//
// Note: Vipers are not safe for concurrent Get() and Set() operations.
type Viper struct {
	// 嵌套层级的分隔符,默认是 .
	// Delimiter that separates a list of keys
	// used to access a nested value in one go
	keyDelim string

	// 支持多个文件搜索路径
	// A set of paths to look for the config file in
	configPaths []string

	// 用作者自己的库,而不是 golang 官方的 os 包,读文件
	// The filesystem to read config from.
	fs afero.Fs

	// 支持多个配置中心搜索路径
	// A set of remote providers to search for the configuration
	remoteProviders []*defaultRemoteProvider

	// Name of file to look for inside the path
	configName        string // 文件名
	configFile        string // TODO
	configType        string // 文件格式,如 JSON
	configPermissions os.FileMode // 文件的 chmod 权限
	envPrefix         string // 环境变量前缀

	// ini 格式的特殊选项
	// Specific commands for ini parsing
	iniLoadOptions ini.LoadOptions

	automaticEnvApplied bool // 是否自动应用环境变量
	envKeyReplacer      StringReplacer // 环境变量单词的分隔符,默认是 _
	allowEmptyEnv       bool // 是否允许空环境变量

	parents        []string // TODO
	config         map[string]any // 最终的配置
	override       map[string]any // 覆盖的配置
	defaults       map[string]any // 默认的配置
	kvstore        map[string]any // 远端配置中心的配置
	pflags         map[string]FlagValue // 命令行参数
	env            map[string][]string // 环境变量
	aliases        map[string]string // 别名
	typeByDefValue bool // TODO

	onConfigChange func(fsnotify.Event) // 监听配置文件的回调函数

	logger *slog.Logger // 日志接口, 兼容老 slog 和 go 1.21 的 slog

	// TODO: should probably be protected with a mutex
	encoderRegistry *encoding.EncoderRegistry // 编码工厂
	decoderRegistry *encoding.DecoderRegistry // 解码工厂
}

2.3 构造:New 和 函数式 Options

实例化的方法如下:

// New returns an initialized Viper instance.
func New() *Viper {
	v := new(Viper)
	v.keyDelim = "."
	v.configName = "config"
	v.configPermissions = os.FileMode(0o644)
	v.fs = afero.NewOsFs()
	v.config = make(map[string]any)
	v.parents = []string{}
	v.override = make(map[string]any)
	v.defaults = make(map[string]any)
	v.kvstore = make(map[string]any)
	v.pflags = make(map[string]FlagValue)
	v.env = make(map[string][]string)
	v.aliases = make(map[string]string)
	v.typeByDefValue = false
	v.logger = slog.New(&discardHandler{})

	v.resetEncoding() // 向工厂注册 yaml,json,toml,hcl,ini,java properties,dotenv

	return v
}

函数式编程,配置 option:

// 这里两篇文章是很好的设计理念,通过函数式的 Option,便于扩展,便于用户使用
// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
// If you're unfamiliar with this style,
// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis.
type Option interface { // 定义一个接口
	apply(v *Viper) // 其有 apply 方法,apply 方法可以传入 *Viper 参数,并改变 *Viper 的行为
}

type optionFunc func(v *Viper) // 定义一个类型

func (fn optionFunc) apply(v *Viper) { // 这个类型实现了 Option 接口
	fn(v)
}

apply() 函数只被 NewWithOptions() 这一个地方调用了,其遍历并应用 opts:

// NewWithOptions creates a new Viper instance.
func NewWithOptions(opts ...Option) *Viper {
	v := New()

	for _, opt := range opts {
		opt.apply(v)
	}

	v.resetEncoding()

	return v
}

有 4 个地方调用了 Option interface,详细情况如下:

// 第一处,设置 logger
// WithLogger sets a custom logger.
func WithLogger(l *slog.Logger) Option {
	return optionFunc(func(v *Viper) {
		v.logger = l
	})
}

// 第二处调用,设置嵌套标识符,默认是 .
// KeyDelimiter sets the delimiter used for determining key parts.
// By default it's value is ".".
func KeyDelimiter(d string) Option {
	return optionFunc(func(v *Viper) {
		v.keyDelim = d
	})
}

// 第三处调用,设置嵌套标识符,默认是 .
// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.
func EnvKeyReplacer(r StringReplacer) Option {
	return optionFunc(func(v *Viper) {
		v.envKeyReplacer = r
	})
}

// 第四处调用,不太重要的 ini 类型
// IniLoadOptions sets the load options for ini parsing.
func IniLoadOptions(in ini.LoadOptions) Option {
	return optionFunc(func(v *Viper) {
		v.iniLoadOptions = in
	})
}

2.4 Reset

Reset函数仅用于测试,单测,或应用程序的业务逻辑测试

// In the public interface for the viper package so applications
// can use it in their testing as well.
func Reset() {
	v = New()
	SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
	SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"}
}

2.5 配置中心

2.5.1 接口定义

secretKeyring:表示密钥环的路径或位置。它指定了密钥环文件的路径或位置,用于对敏感配置数据进行加密或解密操作。密钥环通常用于保护敏感信息,如密码、API密钥等
type defaultRemoteProvider struct {
	provider      string // 配置中心后端的类型,可以是某个特定的服务提供商或框架,例如"consul"、"etcd"、"zookeeper"等。
	endpoint      string // 配置中心的地址或终端点。它指定了配置中心的网络地址或连接终端点,用于与配置中心进行通信。
	path          string // 配置中心中存储配置的路径。它指定了在配置中心中存储配置的位置或路径,用于定位特定的配置数据。
	secretKeyring string // 密钥环的路径或位置。它指定了密钥文件的路径或位置,用于对敏感配置数据进行加密或解密操作。
}

func (rp defaultRemoteProvider) Provider() string {
	return rp.provider
}

func (rp defaultRemoteProvider) Endpoint() string {
	return rp.endpoint
}

func (rp defaultRemoteProvider) Path() string {
	return rp.path
}

func (rp defaultRemoteProvider) SecretKeyring() string {
	return rp.secretKeyring
}

// 定义了接口,虽然目前只有一个实现,但如果长年累月后,大版本升级时就对老用户很友好了
// RemoteProvider stores the configuration necessary
// to connect to a remote key/value store.
// Optional secretKeyring to unencrypt encrypted values
// can be provided.
type RemoteProvider interface {
	Provider() string
	Endpoint() string
	Path() string
	SecretKeyring() string
}

2.5.2 添加 RemoteProvider

// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url.  etcd requires http://ip:port, consul requires ip:port, nats requires nats://ip:port
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
func AddRemoteProvider(provider, endpoint, path string) error {
	return v.AddRemoteProvider(provider, endpoint, path)
}

func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
	if !stringInSlice(provider, SupportedRemoteProviders) {
		return UnsupportedRemoteProviderError(provider)
	}
	if provider != "" && endpoint != "" {
		v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)

		rp := &defaultRemoteProvider{
			endpoint: endpoint,
			provider: provider,
			path:     path,
		}
		if !v.providerPathExists(rp) {
			v.remoteProviders = append(v.remoteProviders, rp)
		}
	}
	return nil
}

// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url.  etcd requires http://ip:port  consul requires ip:port
// secretkeyring is the filepath to your openpgp secret keyring.  e.g. /etc/secrets/myring.gpg
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
// Secure Remote Providers are implemented with github.com/bketelsen/crypt.
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
	return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
}

func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
	if !stringInSlice(provider, SupportedRemoteProviders) {
		return UnsupportedRemoteProviderError(provider)
	}
	if provider != "" && endpoint != "" {
		v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)

		rp := &defaultRemoteProvider{
			endpoint:      endpoint,
			provider:      provider,
			path:          path,
			secretKeyring: secretkeyring,
		}
		if !v.providerPathExists(rp) {
			v.remoteProviders = append(v.remoteProviders, rp)
		}
	}
	return nil
}

func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
	for _, y := range v.remoteProviders {
		if reflect.DeepEqual(y, p) {
			return true
		}
	}
	return false
}

2.6 监听配置变化

启动了两级 goroutine,WatchConfig() 是非阻塞的,内层 goroutine 会持续运行。PS:外层 goroutine 等待内层 goroutine 结束后,才结束自身。

// 包级方法
// OnConfigChange sets the event handler that is called when a config file changes.
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }

// 实例级方法,设置回调函数,内部通过第三方库 fsnotify 实现
// OnConfigChange sets the event handler that is called when a config file changes.
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
	v.onConfigChange = run
}

// WatchConfig starts watching a config file for changes.
func WatchConfig() { v.WatchConfig() }

// 当且仅当调此方法时,才会启动协程监听变化
// WatchConfig starts watching a config file for changes.
func (v *Viper) WatchConfig() {
	initWG := sync.WaitGroup{}
	initWG.Add(1)
	go func() {
		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err))
			os.Exit(1)
		}
		defer watcher.Close()
		// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
		filename, err := v.getConfigFile()
		if err != nil {
			v.logger.Error(fmt.Sprintf("get config file: %s", err))
			initWG.Done()
			return
		}

		configFile := filepath.Clean(filename)
		configDir, _ := filepath.Split(configFile)
		realConfigFile, _ := filepath.EvalSymlinks(filename)

		eventsWG := sync.WaitGroup{}
		eventsWG.Add(1)
		go func() {
			for {
				select {
				case event, ok := <-watcher.Events:
					if !ok { // 'Events' channel is closed
						eventsWG.Done()
						return
					}
					currentConfigFile, _ := filepath.EvalSymlinks(filename)
					// we only care about the config file with the following cases:
					// 1 - if the config file was modified or created
					// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
					if (filepath.Clean(event.Name) == configFile &&
						(event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||
						(currentConfigFile != "" && currentConfigFile != realConfigFile) {
						realConfigFile = currentConfigFile
						err := v.ReadInConfig()
						if err != nil {
							v.logger.Error(fmt.Sprintf("read config file: %s", err))
						}
						if v.onConfigChange != nil {
							v.onConfigChange(event)
						}
					} else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
						eventsWG.Done()
						return
					}

				case err, ok := <-watcher.Errors:
					if ok { // 'Errors' channel is not closed
						v.logger.Error(fmt.Sprintf("watcher error: %s", err))
					}
					eventsWG.Done()
					return
				}
			}
		}()
		watcher.Add(configDir)
		initWG.Done()   // done initializing the watch in this go routine, so the parent routine can move on...
		eventsWG.Wait() // now, wait for event loop to end in this go-routine...
	}()
	initWG.Wait() // make sure that the go routine above fully ended before returning
}

2.7 文件设置

// 直接定义配置文件全路径
// SetConfigFile explicitly defines the path, name and extension of the config file.
// Viper will use this and not check any of the config paths.
func SetConfigFile(in string) { v.SetConfigFile(in) }

func (v *Viper) SetConfigFile(in string) {
	if in != "" {
		v.configFile = in
	}
}

// 查看真正使用的配置文件
// ConfigFileUsed returns the file used to populate the config registry.
func ConfigFileUsed() string            { return v.ConfigFileUsed() }
func (v *Viper) ConfigFileUsed() string { return v.configFile }

// 添加配置文件路径(支持多个)
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func AddConfigPath(in string) { v.AddConfigPath(in) }

func (v *Viper) AddConfigPath(in string) {
	if in != "" {
		absin := absPathify(v.logger, in) // 从这个函数的单测可看出,可以变为绝对路径,例如可把 ./sub 变为 sub

		v.logger.Info("adding path to search paths", "path", absin)
		if !stringInSlice(absin, v.configPaths) {
			v.configPaths = append(v.configPaths, absin)
		}
	}
}

2.8 环境变量

2.8.1 配置

// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }

func (v *Viper) SetEnvPrefix(in string) {
	if in != "" {
		v.envPrefix = in
	}
}

func GetEnvPrefix() string { return v.GetEnvPrefix() }

func (v *Viper) GetEnvPrefix() string {
	return v.envPrefix
}

// 默认用 _ 和 前缀 拼接 字符串
func (v *Viper) mergeWithEnvPrefix(in string) string {
	if v.envPrefix != "" {
		return strings.ToUpper(v.envPrefix + "_" + in)
	}

	return strings.ToUpper(in)
}

// 默认不允许空环境变量
// AllowEmptyEnv tells Viper to consider set,
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }

func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
	v.allowEmptyEnv = allowEmptyEnv
}

// TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (camel case to snake case for JSON keys perhaps)

// getEnv is a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys than the config object
// keys.
func (v *Viper) getEnv(key string) (string, bool) {
	if v.envKeyReplacer != nil {
		key = v.envKeyReplacer.Replace(key) // 字符串替换
	}

	val, ok := os.LookupEnv(key) // 判断环境变量是否存在

	return val, ok && (v.allowEmptyEnv || val != "")
}

2.8.2 绑定

// 环境变量不区分大小写,如果手动指定了,会转为全大写去找环境变量
// 可以指定多个,参数的第一项是要绑定到哪个值,后面的参数按顺序优先级读取

// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
// If more arguments are provided, they will represent the env variable names that
// should bind to this key and will be taken in the specified order.
// EnvPrefix will be used when set when env name is not provided.
func BindEnv(input ...string) error { return v.BindEnv(input...) }

// 使用示例如下
func TestAllKeysWithEnv(t *testing.T) {
	v := New()

	// bind and define environment variables (including a nested one)
	v.BindEnv("id")
	v.BindEnv("foo.bar")
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	t.Setenv("ID", "13")
	t.Setenv("FOO_BAR", "baz")

	assert.ElementsMatch(t, []string{"id", "foo.bar"}, v.AllKeys())
}

// 比如之前配置文件「没有」 foo 配置项,然后通过 BindEnv() 绑定环境变量,并通过 os.SetEnv() 写入环境变量,最终可以通过 viper.Get("foo") 获取配置项。
// 因为其内部会将 foo 的环境变量绑定到 foo 的配置项上,即使之前并不存在这个配置项

2.8.3 环境变量覆盖配置文件

设置 v.SetEnvKeyReplacer(strings.NewReplacer(“.”, “_”))

然后配置项层级之间默认以 . 作为分隔符

环境变量会将 . 转为 _ 做分隔符

找到环境变量后,会覆盖

2.9 搜索 key

// 递归方法
// searchMap recursively searches for a value for path in source map.
// Returns nil if not found.
// Note: This assumes that the path entries and map keys are lower cased.
func (v *Viper) searchMap(source map[string]any, path []string) any {
	if len(path) == 0 {
		return source
	}

	next, ok := source[path[0]]
	if ok {
		// Fast path
		if len(path) == 1 {
			return next
		}

		// Nested case
		switch next := next.(type) {
		case map[any]any:
			return v.searchMap(cast.ToStringMap(next), path[1:])
		case map[string]any:
			// Type assertion is safe here since it is only reached
			// if the type of `next` is the same as the type being asserted
			return v.searchMap(next, path[1:])
		default:
			// got a value but nested key expected, return "nil" for not found
			return nil
		}
	}
	return nil
}

// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
//
// While searchMap() considers each path element as a single map key or slice index, this
// function searches for, and prioritizes, merged path elements.
// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar"
// is also defined, this latter value is returned for path ["foo", "bar"].
//
// This should be useful only at config level (other maps may not contain dots
// in their keys).
//
// Note: This assumes that the path entries and map keys are lower cased.
func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any {
	if len(path) == 0 {
		return source
	}

	// search for path prefixes, starting from the longest one
	for i := len(path); i > 0; i-- {
		prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))

		var val any
		switch sourceIndexable := source.(type) {
		case []any:
			val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)
		case map[string]any:
			val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path)
		}
		if val != nil {
			return val
		}
	}

	// not found
	return nil
}

// searchSliceWithPathPrefixes searches for a value for path in sourceSlice
//
// This function is part of the searchIndexableWithPathPrefixes recurring search and
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
func (v *Viper) searchSliceWithPathPrefixes(
	sourceSlice []any,
	prefixKey string,
	pathIndex int,
	path []string,
) any {
	// if the prefixKey is not a number or it is out of bounds of the slice
	index, err := strconv.Atoi(prefixKey)
	if err != nil || len(sourceSlice) <= index {
		return nil
	}

	next := sourceSlice[index]

	// Fast path
	if pathIndex == len(path) {
		return next
	}

	switch n := next.(type) {
	case map[any]any:
		return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
	case map[string]any, []any:
		return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
	default:
		// got a value but nested key expected, do nothing and look for next prefix
	}

	// not found
	return nil
}

// searchMapWithPathPrefixes searches for a value for path in sourceMap
//
// This function is part of the searchIndexableWithPathPrefixes recurring search and
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
func (v *Viper) searchMapWithPathPrefixes(
	sourceMap map[string]any,
	prefixKey string,
	pathIndex int,
	path []string,
) any {
	next, ok := sourceMap[prefixKey]
	if !ok {
		return nil
	}

	// Fast path
	if pathIndex == len(path) {
		return next
	}

	// Nested case
	switch n := next.(type) {
	case map[any]any:
		return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
	case map[string]any, []any:
		return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
	default:
		// got a value but nested key expected, do nothing and look for next prefix
	}

	// not found
	return nil
}

// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere
// on its path in the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
//
//	"foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string {
	var parentVal any
	for i := 1; i < len(path); i++ {
		parentVal = v.searchMap(m, path[0:i])
		if parentVal == nil {
			// not found, no need to add more path elements
			return ""
		}
		switch parentVal.(type) {
		case map[any]any:
			continue
		case map[string]any:
			continue
		default:
			// parentVal is a regular value which shadows "path"
			return strings.Join(path[0:i], v.keyDelim)
		}
	}
	return ""
}

// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere
// in a sub-path of the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
//
//	"foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
	// unify input map
	var m map[string]interface{}
	switch miv := mi.(type) {
	case map[string]string:
		m = castMapStringToMapInterface(miv)
	case map[string]FlagValue:
		m = castMapFlagToMapInterface(miv)
	default:
		return ""
	}

	// scan paths
	var parentKey string
	for i := 1; i < len(path); i++ {
		parentKey = strings.Join(path[0:i], v.keyDelim)
		if _, ok := m[parentKey]; ok {
			return parentKey
		}
	}
	return ""
}

// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere
// in the environment, when automatic env is on.
// e.g., if "foo.bar" has a value in the environment, it “shadows”
//
//	"foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
	var parentKey string
	for i := 1; i < len(path); i++ {
		parentKey = strings.Join(path[0:i], v.keyDelim)
		if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {
			return parentKey
		}
	}
	return ""
}

2.10 Get 获取一个 key 的值

// Get can retrieve any value given the key to use.
// Get is case-insensitive for a key.
// Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func Get(key string) any { return v.Get(key) }

func (v *Viper) Get(key string) any {
	lcaseKey := strings.ToLower(key)
	val := v.find(lcaseKey, true)
	if val == nil {
		return nil
	}

	if v.typeByDefValue {
		// TODO(bep) this branch isn't covered by a single test.
		valType := val
		path := strings.Split(lcaseKey, v.keyDelim)
		defVal := v.searchMap(v.defaults, path)
		if defVal != nil {
			valType = defVal
		}

		switch valType.(type) {
		case bool:
			return cast.ToBool(val)
		case string:
			return cast.ToString(val)
		case int32, int16, int8, int:
			return cast.ToInt(val)
		case uint:
			return cast.ToUint(val)
		case uint32:
			return cast.ToUint32(val)
		case uint64:
			return cast.ToUint64(val)
		case int64:
			return cast.ToInt64(val)
		case float64, float32:
			return cast.ToFloat64(val)
		case time.Time:
			return cast.ToTime(val)
		case time.Duration:
			return cast.ToDuration(val)
		case []string:
			return cast.ToStringSlice(val)
		case []int:
			return cast.ToIntSlice(val)
		case []time.Duration:
			return cast.ToDurationSlice(val)
		}
	}

	return val
}

2.11 Unmarshal

和官方的 Unmarshal 方法不一样,不支持 json tag,使用时需注意。因为最佳实践建议通过 GetXXX() 方法,而不是 Unmarshal 到 struct,所以推荐看官方的 Unmarshal,可跳过此方法。

// UnmarshalKey takes a single key and unmarshals it into a Struct.
func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {

// Unmarshal unmarshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {

func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {

// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
// of time.Duration values & string slices.
func defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {

// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.
func decode(input any, config *mapstructure.DecoderConfig) error {

// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
	return v.UnmarshalExact(rawVal, opts...)
}

2.12 BindPFlag

// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
//	serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
//	Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }

// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) }

// BindFlagValue binds a specific key to a FlagValue.
func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }

2.13 关键逻辑 find()

按优先级,赋值

// Given a key, find the value.
//
// Viper will check to see if an alias exists first.
// Viper will then check in the following order:
// flag, env, config file, key/value store.
// Lastly, if no value was found and flagDefault is true, and if the key
// corresponds to a flag, the flag's default value is returned.
//
// Note: this assumes a lower-cased key given.
func (v *Viper) find(lcaseKey string, flagDefault bool) any {

有两个地方调用了,所以 find() 的输入是全小写字母

func (v *Viper) Get(key string) any {
	lcaseKey := strings.ToLower(key) // 转小写
	val := v.find(lcaseKey, true)
	if val == nil {return nil}
	return val
}

func (v *Viper) IsSet(key string) bool {
	lcaseKey := strings.ToLower(key) // 转小写
	val := v.find(lcaseKey, false)
	return val != nil
}

下面分析详细逻辑:

// Given a key, find the value.
//
// Viper will check to see if an alias exists first.
// Viper will then check in the following order:
// flag, env, config file, key/value store.
// Lastly, if no value was found and flagDefault is true, and if the key
// corresponds to a flag, the flag's default value is returned.
//
// Note: this assumes a lower-cased key given.

// Get 的参数是 A.B.C.D.E, 会转为全小写 a.b.c.d.e
// 输入的 lcaseKey 是 a.b.c.d.e
func (v *Viper) find(lcaseKey string, flagDefault bool) any {
	var (
		val    any
		exists bool
		path   = strings.Split(lcaseKey, v.keyDelim) // 按默认的分隔符 . 分隔, 即 []{"a", "b", "c", "d", "e"}
		nested = len(path) > 1 // 是否是嵌套层级,true
	)

	// 优先级1. alias
	// compute the path through the nested maps to the nested value
	if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" { // 默认 v.alias 是空,因为没有别名
		return nil
	}

	// if the requested key is an alias, then return the proper key
	lcaseKey = v.realKey(lcaseKey) // 通过 alias 找到真正的 key, 通过递归做深度优先搜索
	path = strings.Split(lcaseKey, v.keyDelim) // 按真正的 lcaseKey 重新计算 path
	nested = len(path) > 1 // 同上

	// 优先级2. 找 override 的, 应该是通过 SetXXX() 方法赋值的
	// Set() override first
	val = v.searchMap(v.override, path)
	if val != nil {
		return val
	}
	if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {
		return nil
	}

	// 优先级3. pflags
	// PFlag override next
	flag, exists := v.pflags[lcaseKey]
	if exists && flag.HasChanged() {
		switch flag.ValueType() {
		case "int", "int8", "int16", "int32", "int64":
			return cast.ToInt(flag.ValueString())
		case "bool":
			return cast.ToBool(flag.ValueString())
		case "stringSlice", "stringArray":
			s := strings.TrimPrefix(flag.ValueString(), "[")
			s = strings.TrimSuffix(s, "]")
			res, _ := readAsCSV(s)
			return res
		case "intSlice":
			s := strings.TrimPrefix(flag.ValueString(), "[")
			s = strings.TrimSuffix(s, "]")
			res, _ := readAsCSV(s)
			return cast.ToIntSlice(res)
		case "durationSlice":
			s := strings.TrimPrefix(flag.ValueString(), "[")
			s = strings.TrimSuffix(s, "]")
			slice := strings.Split(s, ",")
			return cast.ToDurationSlice(slice)
		case "stringToString":
			return stringToStringConv(flag.ValueString())
		case "stringToInt":
			return stringToIntConv(flag.ValueString())
		default:
			return flag.ValueString()
		}
	}
	if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {
		return nil
	}
	
	// 优先级4. 环境变量
	// Env override next
	if v.automaticEnvApplied { // 只有设置了环境变量开关, 才会生效
		envKey := strings.Join(append(v.parents, lcaseKey), ".")
		// even if it hasn't been registered, if automaticEnv is used,
		// check any Get request
		if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok { // 如果设置了 Env 前缀的话
			return val
		}
		if nested && v.isPathShadowedInAutoEnv(path) != "" {
			return nil
		}
	}
	envkeys, exists := v.env[lcaseKey]
	if exists {
		for _, envkey := range envkeys {
			if val, ok := v.getEnv(envkey); ok { // 如果找到了环境变量,直接返回
				return val
			}
		}
	}
	if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {
		return nil
	}

	// 优先级5. 在配置文件路径中找
	// Config file next
	val = v.searchIndexableWithPathPrefixes(v.config, path)
	if val != nil {
		return val
	}
	if nested && v.isPathShadowedInDeepMap(path, v.config) != "" {
		return nil
	}

	// 优先级6. 在配置中心找
	// K/V store next
	val = v.searchMap(v.kvstore, path)
	if val != nil {
		return val
	}
	if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" {
		return nil
	}

	// 优先级7. 默认值
	// Default next
	val = v.searchMap(v.defaults, path)
	if val != nil {
		return val
	}
	if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" {
		return nil
	}

	// 优先级8. flag 的默认值
	if flagDefault {
		// last chance: if no value is found and a flag does exist for the key,
		// get the flag's default value even if the flag's value has not been set.
		if flag, exists := v.pflags[lcaseKey]; exists {
			switch flag.ValueType() {
			case "int", "int8", "int16", "int32", "int64":
				return cast.ToInt(flag.ValueString())
			case "bool":
				return cast.ToBool(flag.ValueString())
			case "stringSlice", "stringArray":
				s := strings.TrimPrefix(flag.ValueString(), "[")
				s = strings.TrimSuffix(s, "]")
				res, _ := readAsCSV(s) // 逗号分隔的字符串是通过 readAsCSV()封装的
				return res
			case "intSlice":
				s := strings.TrimPrefix(flag.ValueString(), "[")
				s = strings.TrimSuffix(s, "]")
				res, _ := readAsCSV(s)
				return cast.ToIntSlice(res)
			case "stringToString":
				return stringToStringConv(flag.ValueString())
			case "stringToInt":
				return stringToIntConv(flag.ValueString())
			case "durationSlice":
				s := strings.TrimPrefix(flag.ValueString(), "[")
				s = strings.TrimSuffix(s, "]")
				slice := strings.Split(s, ",")
				return cast.ToDurationSlice(slice)
			default:
				return flag.ValueString()
			}
		}
		// last item, no need to check shadowing
	}

	// 没找到
	return nil
}

2.14 alias bieming

// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) }

func (v *Viper) RegisterAlias(alias, key string) {
	v.registerAlias(alias, strings.ToLower(key))
}

func (v *Viper) registerAlias(alias, key string) {
	alias = strings.ToLower(alias)
	if alias != key && alias != v.realKey(key) {
		_, exists := v.aliases[alias]

		if !exists {
			// if we alias something that exists in one of the maps to another
			// name, we'll never be able to get that value using the original
			// name, so move the config value to the new realkey.
			if val, ok := v.config[alias]; ok {
				delete(v.config, alias)
				v.config[key] = val
			}
			if val, ok := v.kvstore[alias]; ok {
				delete(v.kvstore, alias)
				v.kvstore[key] = val
			}
			if val, ok := v.defaults[alias]; ok {
				delete(v.defaults, alias)
				v.defaults[key] = val
			}
			if val, ok := v.override[alias]; ok {
				delete(v.override, alias)
				v.override[key] = val
			}
			v.aliases[alias] = key
		}
	} else {
		v.logger.Warn("creating circular reference alias", "alias", alias, "key", key, "real_key", v.realKey(key))
	}
}

func (v *Viper) realKey(key string) string {
	newkey, exists := v.aliases[key]
	if exists {
		v.logger.Debug("key is an alias", "alias", key, "to", newkey)

		return v.realKey(newkey)
	}
	return key
}

2.15 读文件

// ReadInConfig will discover and load the configuration file from disk
// and key/value stores, searching in one of the defined paths.
func ReadInConfig() error { return v.ReadInConfig() }

// MergeInConfig merges a new configuration with an existing config.
func MergeInConfig() error { return v.MergeInConfig() }

// MergeConfig merges a new configuration with an existing config.
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }

// MergeConfigMap merges the configuration from the map given with an existing config.
// Note that the map given may be modified.
func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(cfg) }

// WriteConfig writes the current configuration to a file.
func WriteConfig() error { return v.WriteConfig() }

2.16 单测

看 viper_test,通过单测学使用方法,确实非常好用,比代码注释更易懂

其余的就不再看了,后续花时间看更多更新的库,和经典的服务,然后上手模仿

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/361701.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NoSQL数据库简介

NoSQL数据库简介 Brief Introduction to NoSQL Databases By JacksonML 1. 什么是SQL&#xff1f; 在了解NoSQL之前&#xff0c;先简要介绍一下SQL。 SQL是 Structured Query Language&#xff08;结构化查询语言&#xff09;的缩写。 SQL在关系型数据中广泛使用&#xf…

Linux中vim编辑器的使用

vim常见使用方法 vim介绍命令模式下常用命令用法底行模式下常见命令用法注释代码删除 vim细节vim的配置问题 vim介绍 vim是一款多模式的编辑器 所谓多模式就是有几种模式供我们选择使用 创建一个文件叫test.c&#xff0c;用vim打开就是 vim test.c这样就可以打开test.c进行编…

运维SRE-04 磁盘管理体系

磁盘管理体系详解 磁盘管理系统概述 目标 熟练掌握常用磁盘配置(容量,转速,个数)熟练说出来或写出来: raid级别熟练掌握磁盘基础使用:拿到一块硬盘到可以向硬盘写入数据分区,格式化,挂载熟练掌握: 磁盘空间不足 no space left on device 故障,原因,排查,解决. 磁盘基础内容 …

17.Golang channel的基本定义及使用

目录 概述实践无缓冲 channel代码结果 缓冲 channel代码结果 channel的关闭特点代码结果range代码结果 select channel代码结果 结束 概述 此篇文章介绍 channel 的用法 无缓冲 channel缓冲 channelchannel的关闭特点range channelselect channel 每一种&#xff0c;配上完整…

深入浅出AI落地应用分析:AI个人助手Monica

前言:铺天盖地的大模型以及所谓的应用到目前为止实际还是很少有像Monica这样贴合个人工作习惯的产品落地,比如像Chatgpt等这样的产品,绝大多数人不会专门买🪜翻墙出去用,而且大多数场景下素人或小白都不知道该怎么用,但是Monica这款产品就很好的以浏览器的插件的形式始终…

29 python快速上手

Python操作MySQL和实战 1. 事务1.1 MySQL客户端1.2 Python代码 2. 锁2.1 排它锁2.2 共享锁 3. 数据库连接池4. SQL工具类4.1 单例和方法4.2 上下文管理 5.其他总结 目标&#xff1a;掌握事务和锁以及Python操作MySQL的各种开发必备知识。 概要&#xff1a; 事务锁数据库连接池…

【Oracle云】使用 boto3 访问 OCI 对象存储 (AWS S3协议兼容)

在现代云计算环境中&#xff0c;S3&#xff08;Simple Storage Service&#xff09;协议已经成为云对象存储的事实标准。它提供了简单、可扩展、高度耐用的存储解决方案&#xff0c;得到了广泛应用。Oracle Cloud Infrastructure&#xff08;OCI&#xff09;秉承着开放性和灵活…

摄像头监控系统/视频监控云平台EasyCVR接入单兵设备后如何配置移动规矩

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体控可实现视频监控直播、视频轮播、视频录像…

Java编程练习之类的封装

1.把一个Student类封装起来&#xff0c;模拟一个转校生转入新学校后为其制作学生信息的过程。运行结果如下&#xff1a; package zhtestdemo; import java.util.Scanner; import java.text.DecimalFormat; public class demo { //创建类&#xff0c;类名叫demo; private Stud…

CentOS7中安装ElasticSearch

文章目录 检测是否安装了Elasticsearch安装JDK下载java配置 下载Elasticsearch解压安装Elasticsearch修改配置文件启动Elasticsearch常见问题 ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasti…

【Ubuntu 18.04 安装截图软件 flameshot 】

安装命令&#xff1a; sudo apt-get install flameshot 使用命令&#xff1a; flameshot gui 创建快捷键&#xff1a;设备->键盘->>输入名字和快捷键 截完图后保存CtrlS&#xff0c;复制到剪贴板 CtrlC ​​​​​​

Altium Designer的学习

PCB设计流程 1.新建空白工程&#xff1a; 创建一个新的工程 新建四个文件&#xff0c;并且保存&#xff1a; 每次打开文件时&#xff0c;打开以.PrjPcb结尾的文件 2.元件符号的创建&#xff1a; 在绘制图形的时候设置成10mil,为了在原理图中显得不那么大。 在绘制引脚的时候设…

【软考问题】-- 6 - 知识精讲 - 项目成本管理

一、基本问题 项目成本管理&#xff08;预算内完成&#xff09; 1&#xff1a;成本管理计划 定义&#xff1a;是项目管理计划的组成部分&#xff0c; 描述将如何规划、 安排和控制项目成本。在计划中一般要规定 计量单位精确度&#xff1a;例如 995.59 元取整为 1000元准确度&…

【Qt】—— Qt Creator界⾯认识

目录 &#xff08;一&#xff09;左边栏 &#xff08;二&#xff09;代码编辑区 &#xff08;三&#xff09;UI设计界⾯ &#xff08;四&#xff09;构建区 &#xff08;一&#xff09;左边栏 在编辑模式下&#xff0c;左边竖排的两个窗⼝叫做"边栏"。 ①是项⽬…

【TCP】三次握手(建立连接)

前言 在网络通信的世界里&#xff0c;可靠传输协议&#xff08;TCP&#xff09;扮演着重要的角色&#xff0c;它保证了数据包能够按顺序、完整地从发送端传送到接收端。TCP协议中有一个至关重要的机制——三次握手。这一过程确保了两个TCP设备在开始数据传输之前建立起一个稳定…

数字孪生产品评测:五款数字孪生产品的优劣对比

作为数据可视化领域的资深用户&#xff0c;我深知数字孪生产品在当今工业4.0时代的重要地位。本文将为大家介绍五款市面上的数字孪生产品&#xff0c;帮助大家了解各产品之间的优缺点&#xff0c;选择适合自己的产品。 一、山海鲸可视化 山海鲸可视化是一款强大而灵活的免费数…

C++(map和set)

目录 前言 正文 1.预备知识 1.1序列式容器 1.2关联式容器 1.3键值对 2.set 2.1概念 ​编辑 2.2set的使用 2.3set总结 2.4multiset 3.map 3.1概念 3.2、map的使用 3.3map中operator[] 3.4map总结 3.5multimap 前言 s et 和 map 是 STL 中的容器之一&a…

matlab自定义函数实现图像小波变换

matlab中提供了小波变换函数lwt和ilwt&#xff0c;可以方便地实现提升小波变换。 我们按照小波变换的定义&#xff0c;粗糙地实现一个针对图像的小波变换&#xff0c;如下&#xff1a; % 使用方法&#xff1a; img imread(lena256.bmp); % 假设lena.png是灰度图像 subplot(2…

单细胞转录组数据分析的10大软件/流程

单细胞数据分析现在已经有上千个软件工具可供使用了&#xff0c;这为用户带来便利的同时也造成了选择困难。就像时间一样&#xff0c;一个表&#xff0c;没问题&#xff0c;但如果有两个表&#xff0c;时间还不一样&#xff0c;该信谁的呢&#xff1f; 正好我们前面一篇文章介绍…

Windows10更新失败 错误 0x80070643、KB5034441的解决方法之二

Windows10更新失败 错误 0x80070643、KB5034441 在知乎Windows10更新失败 错误 0x80070643、KB5034441的原因分析和几个解决方法 - 知乎 参考文章进行操作&#xff0c;更详细信息自己看上面链接。 我电脑的硬盘是mbr格式&#xff0c;而且没有划分恢复分区。 Microsoft Windo…