GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

2023-12-16

本来有今天是继续接着上一章写微服务的。但是这几天有朋友说,再写Web框架的时候,遇到一个问题,就是很多的中间件(redis,微信,mysql,mq)的配置信息写的太杂了,很不好管理。希望我能写一篇有管理配置文件的。所以这篇就放到今天写吧。微服务就放到下一篇来说吧。今天介绍的主角:Viper

Viper概念

Viper 是适用于 Go 应用程序(包括 Twelve-Factor App )的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。

它支持以下特性:

  • 设置默认值
  • JSON TOML YAML HCL envfile Java properties 格式的配置文件读取配置信息
  • 实时监控和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统( etcd Consul )读取并监控配置变化
  • 从命令行参数读取配置
  • buffer (缓冲)读取配置
  • 显式配置值

在构建现代应用程序时,你无需担心配置文件格式。

Viper能够为你执行下列操作:

  1. 查找,加载,和反序列化 JSON TOML YAML HCL INI envfile Java properties 格式的配置文件。
  2. 提供一种机制,为不同配置选项设置默认值。
  3. 提供一种机制来通过命令行参数覆盖指定选项的值。
  4. 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  5. 当用户提供了,与默认值相同的命令行或者配置文件时,可以很容易分辨出他们之间的不通。

Viper会按照下面的优先级,每个项目的优先级都高于它下面的项目:

  • 显示调用 Set 设置值
  • 命令行参数(flag)
  • 环境变量
  • 配置文件
  • key/value存储
  • 默认值

目前Viper配置的键( Key )是大小写不敏感的。目前正在讨论是否将这一选项设为可选。

Viper 让需要重启服务器才能使配置生效的日子一去不复返!!!

这才是VIper最大的魅力。

Viper没有默认的基础配置,所以在使用的过程中我们初始化 Viper 实例的时候需要告诉 Viper 你的 配置路径 配置格式 配置名称 等等信息。

Viper 虽然支持多配置同时使用,但是一个 Viper 实例只能寻一个配置路径。

安装

go get github.com/spf13/viper

使用VIper

默认值

Viper 支持使用 viper.SetDefault(key, value) 为 key 设置默认值 value

Viper配置的键( Key )是大小写不敏感的


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

键不需要默认值,但如果没有通过配置文件、环境变量、远程配置或命令行标志(flag)设置键,则默认值非常有用。

package main
import (
	"fmt"
	"github.com/spf13/viper"
)
func main() {
	// 设置默认配置
	viper.SetDefault("username", "李四")
	viper.SetDefault("server", map[string]string{"ip": "127.0.0.1", "port": "8080"})
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("Username")) // key 不区分大小写
	fmt.Printf("server: %+v\n", viper.Get("server"))
}
username: jianghushinian
server: map[ip:127.0.0.1 port:8080]

使用->配置文件

读取配置文件:

  • Viper 需要最少知道在哪里查找配置文件的配置。

  • Viper 可以搜索多个路径,但目前单个 Viper 实例只支持单个配置文件。

  • Viper 不默认任何配置搜索路径,将默认决策留给应用程序。

viper.SetConfigFile("./config.yaml") // 指定配置文件路径
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("Fatal error config file: %s \n", err))
}

在加载配置文件出错时,可以像下面这样处理找不到配置文件的特定情况:

if err := viper.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {
        // 配置文件未找到错误;如果需要可以忽略
    } else {
        // 配置文件被找到,但产生了另外的错误
    }
}

// 配置文件找到并成功解析
  • 通过 viper.SetConfigFile() 指定配置文件,如果配置文件名中没有扩展名,则需要使用 viper.SetConfigType() 显式指定配置文件的格式。
  • 通过 viper.AddConfigPath() 指定配置文件的搜索路径中,可以通过多次调用,来设置多个配置文件搜索路径。
    • 然后通过 viper.SetConfigName() 指定不带扩展名的配置文件, Viper 会根据所添加的路径顺序查找配置文件,如果找到就停止查找。
package main
import (
	"errors"
	"flag"
	"fmt"
	"github.com/spf13/viper"
)
var (
	cfg = flag.String("c", "", "config file.")
)
func main() {
	flag.Parse()
	if *cfg != "" {
		viper.SetConfigFile(*cfg)   // 指定配置文件(路径 + 配置文件名)
		viper.SetConfigType("yaml") // 如果配置文件名中没有扩展名,则需要显式指定配置文件的格式
	} else {
		viper.AddConfigPath(".")             // 把当前目录加入到配置文件的搜索路径中
		viper.AddConfigPath("$HOME/.config") // 可以多次调用 AddConfigPath 来设置多个配置文件搜索路径
		viper.SetConfigName("cfg")           // 指定配置文件名(没有扩展名)
	}
	// 读取配置文件
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			fmt.Println(errors.New("config file not found"))
		} else {
			fmt.Println(errors.New("config file was found but another error was produced"))
		}
		return
	}
	fmt.Printf("using config file: %s\n", viper.ConfigFileUsed())
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

如果有多个名称为config的配置文件,viper怎么搜索呢?它会按照如下顺序搜索

  • config.json
  • config.toml
  • config.yaml
  • config.yml
  • config.properties (这种一般是java中的配置文件名)
  • config.props (这种一般是java中的配置文件名)

写入配置文件

从配置文件中读取配置文件是有用的,但是有时你想要存储在 运行时 所做的所有修改。

所以这个,就是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")
  • WriteConfig - 将当前的 viper 配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。
  • SafeWriteConfig - 将当前的 viper 配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
  • WriteConfigAs - 将当前的 viper 配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。
  • SafeWriteConfigAs - 将当前的 viper 配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。

监控并重新读取配置文件(运行时读取配置文件)

Viper 支持在应用程序运行过程中实时读取配置文件,即热加载配置。

只需要调用 viper.WatchConfig() 即可开启此功能。

viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
  // 配置文件发生变更之后会调用的回调函数
	fmt.Println("Config file changed:", e.Name)
})

viper 驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何消息。

package main
import (
	"fmt"
	"time"
	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)
func main() {
	viper.SetConfigFile("./config.yaml")
	viper.ReadInConfig()
	// 注册每次配置文件发生变更后都会调用的回调函数
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Printf("config file changed: %s\n", e.Name)
	})
	// 监控并重新读取配置文件,需要确保在调用前添加了所有的配置路径
	viper.WatchConfig()
	// 阻塞程序,这个过程中可以手动去修改配置文件内容,观察程序输出变化
	time.Sleep(time.Second * 10)
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

调用了 set 方法,用了 Viper 中的最高优先级,对原来的配置文件做覆盖。但是要让这个配置文件在运行中启动,就需要调用: viper.WatchConfig()

也就是说:在调用 viper.WatchConfig() 监控并重新读取配置文件之前,需要确保添加了所有的配置路径。

io.Reader 读取配置

Viper 支持从任何实现了 io.Reader 接口的配置源中读取配置

package main
import (
	"bytes"
	"fmt"
	"github.com/spf13/viper"
)
func main() {
	viper.SetConfigType("yaml") // 或者使用 viper.SetConfigType("YAML")
	var yamlExample = []byte(`
		username: jianghushinian
		password: 123456
		server:
		ip: 127.0.0.1
		port: 8080`
		)
	viper.ReadConfig(bytes.NewBuffer(yamlExample))
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

这里我们通过 bytes.NewBuffer() 构造了一个 bytes.Buffer 对象,它实现了 io.Reader 接口,所以可以直接传递给 viper.ReadConfig() 来从中读取配置。

注册和使用别名

viper.RegisterAlias("loud", "Verbose")  // 注册别名(此处loud和Verbose建立了别名)
viper.Set("verbose", true) // 结果与下一行相同
viper.Set("loud", true)   // 结果与前一行相同
viper.GetBool("loud") // true
viper.GetBool("verbose") // true

环境变量

Viper 还支持从环境变量读取配置,有 5 个方法可以帮助我们使用环境变量:

  • AutomaticEnv() :可以绑定全部环境变量(用法上类似 flag 包的 flag.Parse() )。调用后, Viper 会自动检测和加载所有环境变量。
  • BindEnv(string...) : error :绑定一个环境变量。需要一个或两个参数,第一个参数是配置项的键名,第二个参数是环境变量的名称。
    • 如果未提供第二个参数,则 Viper 将假定环境变量名为:环境变量前缀_键名,且为全大写形式。
      • 例如环境变量前缀为 ENV ,键名为 username ,则环境变量名为 ENV_USERNAME
    • 当显式提供第二个参数时,它不会自动添加前缀,也不会自动将其转换为大写。
      • 例如,使用 viper.BindEnv("username", "username") 绑定键名为 username 的环境变量,应该使用 viper.Get("username") 读取环境变量的值。

在使用环境变量时,需要注意,每次访问它的值时都会去环境变量中读取。当调用 BindEnv 时, Viper 不会固定它的值。

  • SetEnvPrefix(string) :可以告诉 Viper 在读取环境变量时使用的前缀。 BindEnv AutomaticEnv 都将使用此前缀。
    • 例如,使用 viper.SetEnvPrefix("ENV") 设置了前缀为 ENV ,并且使用 viper.BindEnv("username") 绑定了环境变量,在使用 viper.Get("username") 读取环境变量时,实际读取的 key ENV_USERNAME
  • SetEnvKeyReplacer(string...) *strings.Replacer :允许使用 strings.Replacer 对象在一定程度上重写环境变量的键名。
    • 例如,存在 SERVER_IP="127.0.0.1" 环境变量,使用 viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) 将键名中的 . - 替换成 _ ,则通过 viper.Get("server_ip") viper.Get("server.ip") viper.Get("server-ip") 三种方式都可以读取环境变量对应的值。
  • AllowEmptyEnv(bool) :当环境变量为空时(有键名而没有值的情况),默认会被认为是未设置的,并且程序将回退到下一个配置来源。要将空环境变量视为已设置,可以使用此方法。
package main
import (
	"fmt"
	"strings"
	"github.com/spf13/viper"
)
func main() {
	viper.SetEnvPrefix("env") // 设置读取环境变量前缀,会自动转为大写 ENV
	viper.AllowEmptyEnv(true) // 将空环境变量视为已设置
	viper.AutomaticEnv()      // 可以绑定全部环境变量
	viper.BindEnv("username") // 也可以单独绑定某一个环境变量
	viper.BindEnv("password")
	// 将键名中的 . 或 - 替换成 _
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
	// 读取配置
	fmt.Printf("username: %v\n", viper.Get("username"))
	fmt.Printf("password: %v\n", viper.Get("password"))
	fmt.Printf("server.ip: %v\n", viper.Get("server.ip"))
	// 读取全部配置,只能获取到通过 BindEnv 绑定的环境变量,无法获取到通过 AutomaticEnv 绑定的环境变量
	fmt.Println(viper.AllSettings())
}

命令行参数(flag)

安装pflag

go get github.com/spf13/pflag

Viper 支持 pflag 包(它们其实都在 spf13 仓库下),能够绑定命令行标志,从而读取命令行参数。

BindEnv 类似,在调用绑定方法时,不会设置值,而是在每次访问时设置。

这意味着我们可以随时绑定它,例如可以在 init() 函数中。

  • BindPFlag :对于单个标志,可以调用此方法进行绑定。
  • BindPFlags :可以绑定一组现有的标志集 pflag.FlagSet
package main
import (
	"fmt"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)
var (
	username = pflag.StringP("username", "u", "", "help message for username")
	password = pflag.StringP("password", "p", "", "help message for password")
)
func main() {
	pflag.Parse()
	viper.BindPFlag("username", pflag.Lookup("username")) // 绑定单个标志
	viper.BindPFlags(pflag.CommandLine)                   // 绑定标志集
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
	fmt.Printf("password: %s\n", viper.Get("password"))
}

因为 pflag 能够兼容标准库的 flag 包,所以我们也可以变相的让 Viper 支持 flag

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

package main
import (
	"flag"
	"fmt"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)
func main() {
	flag.String("username", "", "help message for username")
	pflag.CommandLine.AddGoFlagSet(flag.CommandLine) // 将 flag 命令行参数注册到 pflag
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
}

使用 Flags

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

如果你不使用 Pflag Viper 提供了两个Go接口来绑定其他 flag 系统。

FlagValue 表示单个 flag

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

flag 实现了这个接口,Viper绑定。

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

FlagValueSet 代表一组 flags

type myFlagSet struct {
	flags []myFlag
}

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

flag set 实现了这个接口,就可以很方便地告诉 Viper 绑定它

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

key/value 存储(远程 Key/Value 存储支持)(微服务)

Viper 中启用远程支持,需要在代码中匿名导入 viper/remote 这个包。

import _ "github.com/spf13/viper/remote"

Viper 支持 etcd、Consul 等远程 key/value 存储,这里以 Consul 为例进行讲解。

而具体什么是Consul 这个会在微服务中体现,也就是说,这个处于微服务使用。

首先需要准备 Consul 环境:文章

Consul Key/Value 存储中设置一个 Key 保存包含所需配置的 JSON 值。

package main
import (
	"fmt"
	"github.com/spf13/viper"
	_ "github.com/spf13/viper/remote" // 必须导入,才能加载远程 key/value 配置
)
func main() {
	viper.AddRemoteProvider("consul", "localhost:8500", "user/config") // 连接远程 consul 服务
	viper.SetConfigType("YAML")                                        // 显式设置文件格式文 YAML
	viper.ReadRemoteConfig()
	// 读取配置值
	fmt.Printf("username: %s\n", viper.Get("username"))
	fmt.Printf("server.ip: %s\n", viper.Get("server.ip"))
}

官方文档中有还提供这几点:etcd,Consul,Firestore:

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

Consul

创建一个 keyMY_CONSUL_KEY 将下面的值存入 Consul key/value 存储

{
    "port": 8080,
    "hostname": "liwenzhou.com"
}

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")) // liwenzhou.com

Firestore

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

这里其实还涉及到一个监控加密的问题,这里建议看 官方文档

从Viper获取值

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

Get(key string) : interface{}
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]interface{}
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]interface{}

每一个 Get 方法在找不到值的时候都会返回零值。为了检查给定的键是否存在,提供了 IsSet() 方法。

访问嵌套的键

加载下面的JSON文件

{
    "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") // (返回 "127.0.0.1")

搜索路径将遍历其余配置注册表,直到找到为止。

译注:因为Viper支持从多种配置来源,例如磁盘上的 配置文件 > 命令行标志位 > 环境变量 > 远程Key/Value存储 > 默认值 ,我们在查找一个配置的时候如果在当前配置源中没找到,就会继续从后续的配置源查找,直到找到为止。

如果 datastore.metric 被直接赋值覆盖(被 flag 环境变量 set()方法 等等…),那么 datastore.metric 的所有子键都将变为 未定义 状态,它们被 高优先级配置级别 “遮蔽”(shadowed)了。

提取子树

app:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
subv := viper.Sub("app.cache1")

假设我们现在有这么一个函数:

func NewCache(cfg *Viper) *Cache {...}

它基于subv格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存。

cfg1 := viper.Sub("app.cache1")
cache1 := NewCache(cfg1)

cfg2 := viper.Sub("app.cache2")
cache2 := NewCache(cfg2)

反序列化

这个也是其中最重要的特点。

可以选择将所有或特定的值解析到结构体、map等:

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error
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]interface{}{
    "ingress": map[string]interface{}{
        "annotations": map[string]interface{}{
            "traefik.frontend.rule.type":                 "PathPrefix",
            "traefik.ingress.kubernetes.io/ssl-redirect": "true",
        },
    },
})

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

var C config

v.Unmarshal(&C)

序列化成字符串

可能需要将 viper 中保存的所有设置序列化到一个字符串中,而不是将它们写入到一个文件中。

你可以将自己喜欢的格式的序列化器与 AllSettings() 返回的配置一起使用。

import (
    yaml "gopkg.in/yaml.v2"
    // ...
)

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实例

可以在应用程序中创建许多不同的 viper实例

  • 每个都有自己独特的一组配置和值。

  • 每个人都可以从不同的配置文件, key value 存储区等读取数据。

  • 每个都可以从不同的配置文件、键值存储等中读取。

viper包支持的所有功能都被镜像为viper实例的方法。

x := viper.New()
y := viper.New()

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

//...

当使用多个viper实例时,由用户来管理不同的viper实例。

使用Viper示例

现在有一个 ./conf/config.yaml 配置文件

port: 8123
version: "v1.2.3"

直接使用viper管理配置

gin框架 搭建的 web项目 中使用 viper ,使用 viper 加载配置文件中的信息,并在代码中直接使用 viper.GetXXX() 方法获取对应的配置值。

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

func main() {
	viper.SetConfigFile("./conf/config.yaml") // 指定配置文件路径
	err := viper.ReadInConfig()        // 读取配置信息
	if err != nil {                    // 读取配置信息失败
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	// 监控配置文件变化
	viper.WatchConfig()

	r := gin.Default()
	// 访问/version的返回值会随配置文件的变化而变化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, viper.GetString("version"))
	})

	if err := r.Run(
		fmt.Sprintf(":%d", viper.GetInt("port"))); err != nil {
		panic(err)
	}
}

使用结构体变量保存配置信息

可以在项目中定义与配置文件对应的结构体, viper 加载完配置信息后使用结构体变量保存配置信息。

package main

import (
	"fmt"
	"net/http"

	"github.com/fsnotify/fsnotify"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

type Config struct {
	Port    int    `mapstructure:"port"`
	Version string `mapstructure:"version"`
}

var Conf = new(Config)

func main() {
	viper.SetConfigFile("./conf/config.yaml") // 指定配置文件路径
	err := viper.ReadInConfig()               // 读取配置信息
	if err != nil {                           // 读取配置信息失败
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
	// 将读取的配置信息保存至全局变量Conf
	if err := viper.Unmarshal(Conf); err != nil {
		panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
	}
	// 监控配置文件变化
	viper.WatchConfig()
	// 注意!!!配置文件发生变化后要同步到全局变量Conf
	viper.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("夭寿啦~配置文件被人修改啦...")
		if err := viper.Unmarshal(Conf); err != nil {
			panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
		}
	})

	r := gin.Default()
	// 访问/version的返回值会随配置文件的变化而变化
	r.GET("/version", func(c *gin.Context) {
		c.String(http.StatusOK, Conf.Version)
	})

	if err := r.Run(fmt.Sprintf(":%d", Conf.Port)); err != nil {
		panic(err)
	}
}

这里献上 官方文档

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GoLong的学习之路,进阶,Viper(yaml等配置文件的管理) 的相关文章

  • 【UE】制作地月全息投影

    效果 步骤 1 在必应国际版上搜索 purlin noise 下载如下所示图片 再搜索 Earth Map 下载如下所示图片 再搜索 Moon 360 下载如下所示图片 这三张图片的资源链接如下 链接 https pan baidu com
  • python在车载电子测试方面的应用笔记【1】

    文章目录 在DataFrame中某列插入数据 并根据另一列查找是否存在某个字符串完全一样 在另一列插入对应数据的功能 删除DataFrame某列数据长度大于6的数据 使用 PyInstaller 打包成一个独立的 exe 文件 通过检索空格
  • 通过kubeadm方式安装k8s

    虚拟机最少是 2 core master内存最小3G node内存最小2G 要求的Docker版本是18 03 如果不是安装的docker ce 版本是过旧的 可以选择删除后重新安装 也可以重新创建一个虚拟机执行以下命令 简单方法 使用ma
  • Docker build 无法解析域名

    报错 Docker build 无法解析域名 报错 ERROR 2 12 RUN curl o etc yum repos d CentOS Base repo https mirrors aliyun com repo Centos 7
  • 安装 运行 gemmini 和chipyard

    安装gemmini 和chipyard过程 安装版本 chipyard 版本是1 8 1 gemmini版本0 7 0 tip 如果在base里安装conda lock觉得缓慢 可以新建新的环境时就指定安装conda lock conda

随机推荐

  • GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

    这章是接上一章 使用 RPC包 序列化中没有详细去讲 因为这一块需要看的和学习的地方很多 并且这一块是RPC中可以说是最重要的一块 也是性能的重要影响因子 今天这篇主要会讲其使用方式 文章目录 Protocol Buffers V3 背景以
  • GoLong的学习之路,进阶,微服务之使用,RPC包(包括源码分析)

    今天这篇是接上上篇RPC原理之后这篇是讲如何使用go本身自带的标准库RPC 这篇篇幅会比较短 重点在于上一章对的补充 文章目录 RPC包的概念 使用RPC包 服务器代码分析 如何实现的 总结 Server还提供了两个注册服务的方法
  • 车载以太网笔记

    文章目录 以太网协议分层 协议 中间设备 子网掩码 物理层 测试 内容比较杂 后续会整理 以太网协议分层 协议 中间设备
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)

    WebAppDbTest 项目准备 项目准备 1 net cli 创建项目 2 nuget 包引用和项目结构 2 1 项目添加相关 nuget 包 2 2 WebAppDbTest 项目结构 3 项目代码说明
  • ETL.NET 助力海量数据轻松处理

    ETL NET 助力海量数据轻松处理 什么是 ETL EtlT About ETL About EtlT 谈谈 ETL 作用 ETL 对企业的作用 ETL 对个人职业发展的作用 ETL NET 介绍
  • 一篇能让你彻底搞明白前复权、后复权、不复权的好文

    当我们打开炒股软件看K线走势图时 点击鼠标右键会看到有前复权 后复权 不复权三种模式形态 有的股票三种模式下形态差别很大 选错模式 甚至有的股票用不复权处在底部 用前复权处在顶部 股票位置的高低都分不清 又怎么能选好股票呢 今天用最容易理解
  • 让同事高血压的8个Bug操作集锦

    来源 juejin cn post 7222676935147651132 前言 案例一 案例二 案例三 案例四 案例五 案例六 案例七 案例八 前言 本文的选题来源于一周前我的一次崩溃 当时各种压
  • 机器翻译:跨越语言边界的智能大使

    导言 机器翻译作为人工智能领域的瑰宝 正在以前所未有的速度和精度 为全球沟通拓展新的可能性 本文将深入研究机器翻译的技术原理 应用场景以及对语言交流未来的影响 1 简介 机器翻译是一项致力于通过计算机自动将一种语言的文本翻译成另一种语言的技
  • 【UE 材质】任务目标点效果

    效果 步骤 1 新建一个工程 创建一个Basic关卡 2 新建一个材质 这里命名为 M GoalPoint 打开 M GoalPoint 设置混合模式为 半透明 勾选 双面 在材质图表中添加如下节点 此时预览效果如下 继续添加如下节点 此时
  • 外贸行业怎么跟进客户?

    外贸行业怎么跟进客户 建立一个完善的跟进系统需要人性化关怀和高效的技术支持 人性化关怀 是指以客户为中心 关注客户的需求和情感 建立真诚 个性化的关系 高效的技术支持 则是指利用技术和工具来提高跟进效率和服务质量 也就是我们接下来要重点介绍
  • 期末备考 |《现代控制理论》期末备考资料包来啦!

    写在前面 不知不觉又到了学期的末尾 不知道各位计算机er的 专业课复习得怎么样了呢 为了帮助大家更好地 备战期末 从今天开始 岛主将持续为大家更新 计算机期末备考资料 为同学们的绩点护航 今天岛主为大家带来的是 现代控制理论 期末备考资料包
  • java 生成 UUID

    java 生成 UUID 1 生成 UUID import cn hutool core util IdUtil public class IdUtilTest public static void main String args 获取随
  • 大语言模型:开启自然语言处理新纪元

    导言 大语言模型 如GPT 3 Generative Pre trained Transformer 3 标志着自然语言处理领域取得的一项重大突破 本文将深入研究大语言模型的基本原理 应用领域以及对未来的影响 1 简介 大语言模型是基于深度
  • 数据库基础知识

    一 数据库 1 数据库分类 关系型数据库 RDMS最常用 以数据表为核心 非关系型数据库 不存在表的概念 2 常见数据库 Oracle 适用于项目结构庞大的数据库 例如 银行 电信等 MariaDB 与MySQL同源 起源于同一个团队 SQ
  • 波奇学Linux:父子进程和进程状态

    vim编辑器 编写一个程序模拟进程 在vim中查看sleep函数 底行模式输入 写个Makefile自动运行 波奇学Linux yum和vim CSDN博客 运行程序 PID和PPID 查看进程目录信息 实际有过滤出来有两个 一个进程本身一
  • 期末备考 |《数学物理方法》期末备考资料包来啦!

    写在前面 不知不觉又到了学期的末尾 不知道各位计算机er的 专业课复习得怎么样了呢 为了帮助大家更好地 备战期末 从今天开始 岛主将持续为大家更新 计算机期末备考资料 为同学们的绩点护航 今天岛主为大家带来的是 数学物理方法 期末备考资料包
  • Solidity之旅(十)OOP-抽象合约

    抽象合约 abstractcontract 前文在讲合约继承的基类构造函数的参数时 有提到抽象合约 也就是说 如果派生合约未能给其继承的基合约指定构造函数参数时 那么 该派生合约必须声明为抽象合约 abstractcontract 我们知道
  • Graylog 中日志级别及其对应的数字

    在 Graylog 中 日志级别 level 通常使用数字表示 数字越低表示日志级别越高 以下是常见的日志级别及其对应的数字表示 DEBUG 调试 对应数字 7 INFO 信息 对应数字 6 NOTICE 通知 对应数字 5 WARN 警告
  • ERP、SAP、MES 三者之间的区别是什么?

    ERP SAP MES之间有什么区别 SAP 思爱普 是ERP系统与企业管理解决方案 提供商 而ERP和MES是两个用途不一样的 管理系统 也就是说 SAP是一家厂商 提供包含ERP在内的管理系统 SAP搞清楚了 那么 ERP和MES 呢
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微