当前位置:   article > 正文

(2)go web开发之 使用viper读取配置文件_golang viper 读取命令行参数

golang viper 读取命令行参数

介绍

  • 什么是 viper?
    • viper 是一个完整的配置解决方案,包括12-Factor apps。它被设计为在应用程序中工作,并且可以处理所有类型的配置需求和格式。它支持:
      • 设置默认值
      • 时时监控和重新读取配置文件
      • 从环境变量中读取
      • 从远程配置系统(etcd或Consul)读取并监听配置变化
      • 从命令行读取配置
      • 显示配置值
    • 可查看官方介绍:https://github.com/spf13/viper, 包括下载安装。上述介绍来自这里开篇
      在这里插入图片描述
  • 为什么使用 viper?
    • 也有官方有介绍优势:
      在这里插入图片描述
    • 能为你做这些:
      • 查找,加载,反序列化上述格式的配置文件。
      • 提供一种机制为你的不同配置选项设置默认值
      • 提供一种机制来通过命令行参数覆盖指定选项值
      • 提供别名系统,以便在不破坏现有代码的情况下,轻松重命名参数
      • 当用户提供了与默认值相同的命令行参数或配置文件时,可以很容易分辨它们之间的区别。
    • viper 按照下面的优先级,每以项都优先于它下面的一项:
      • (1)显示调用 Set设置值
      • (2)命令行参数flag
      • (3)环境变量
      • (4)配置文件
      • (5)key/value存储
      • (6)默认值
    • 另外,目前viper配置的key 是不区分大小写的。

使用

  • 下面的介绍,都是按照上面链接中,官方文档的顺序进行的。细节和小问题,可以直接对照着找到官方文档的讲解。

1. 设置默认值: Establishing Defaults

  • 文档介绍完毕的下方,就是演示设置默认值了:
    • 使用: func SetDefault(key string, value interface{})
      import "github.com/spf13/viper"
      
      func main() {
      	// 设置当前的主文件目录, main.go 所在的位置
      	viper.SetDefault("main", "D:\\gofiles\\go-learning-notes\\go-learning\\zap日志库")
      	// 因为是个接口类型的values,所以还可以这样设置
      	viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

2. 读取配置文件:Reading Config Files

  • 官方介绍:

    • 2 Viper需要最少的配置,所以它知道在哪里查找配置文件。Viper支持JSON、TOML、YAML、HCL、INI、envfile和Java Properties文件。Viper可以搜索多个路径
    • 注意这里:SetConfigType("yaml") : 这是针对从远程获取文件的时候,用来判断文件的类型。 当本地运行时 ,这一句是不生效的。 只会执行 SetConfigName(“config”) 查找这个文件名的配置。
      • 如果既有 config.yaml 还有 config.json 此时读取就会造成混乱!
      • 解决方法是使用: SetConfigFile("config.json") ,明确指定是哪个文件。
  • 展示:

    // 配置文件名, 不加扩展
    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 \n", err))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 在当前路径下创建查找,会依次从配置的路径查找
      在这里插入图片描述

    • 可以按照这种写法,处理特定的找不到配置文件的情况

      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
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

3. 写配置文件:Writing Config Files

  • 官方介绍场景作用:从配置文件中读取是有用的,但有时您希望存储在运行时所做的所有修改。为此,有一堆命令可用,每个命令都有自己的目的。

  • 命令介绍

    • WriteConfig -将当前的viper配置写到预定义的路径(如果存在的话)。如果没有预定义的路径,将出现错误。将覆盖当前配置文件,如果它存在。
    • SafeWriteConfigAs——将当前的viper配置写到给定的文件路径。将不会覆盖给定的文件,如果它存在。
    • WriteConfigAs -将当前的viper配置写到给定的文件路径。将覆盖给定的文件,如果它存在。
    • SafeWriteConfig -写入当前的毒蛇配置到预定义的路径。如果没有预定义的路径,将出现错误。不会覆盖当前配置文件,如果它存在。
  • 展示:

    // 将当前的配置写入 'viper.AddConfigPath()' and 'viper.SetConfigName' 设置的文件
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

4. 监视和重读配置文件: Watching and re-reading config files

  • viper 支持在运行时实时读取配置文件的功能。不需要重启才能生效配置文件了!

    • 读取配置文件后执行viper.WatchConfig()就可以了, 还可以添加回调函数, 记录更改的文件等等。
    viper.OnConfigChange(func(e fsnotify.Event) {
    	fmt.Println("Config file changed:", e.Name)
    })
    viper.WatchConfig()
    
    • 1
    • 2
    • 3
    • 4
  • 实际例子展示:前面的 viper的设置,就是前文的一些设置

    r := gin.Default()
    	r.GET("/version", func(c *gin.Context){
    		c.JSON(http.StatusOK, gin.H{
    			"name": "haungdonglin",
    			"age": 18,
    			// 读取配置里的 version
    			"version": viper.Get("version").(string),
    		})
    	})
    	// 获取配置文件中的端口号
    	r.Run(":" + viper.Get("port").(string))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 设置的配置文件:
    在这里插入图片描述

  • 运行程序,成功启动 8080端口。输入:http://127.0.0.1:8080/version,得到结果:

    • {“age”:18,“name”:“haungdonglin”,“version”:“1.17.2”}
  • 还下面不停止服务器: 继续修改配置文件, 会提示我们修改的信息,回调函数。并且再次访问,会得到新的version值。

    • {“age”:18,“name”:“haungdonglin”,“version”:“1.17.8”}
      在这里插入图片描述

5. 从io.Reader 中读取配置:Reading Config from io.Reader

  • 官方介绍:除了这些提供给你的之外: such as files, environment variables, flags, and remote K/V store。 你还可以自定定义 所需要得配制源,然后提供给 viper。

    viper.SetConfigType("yaml") // or viper.SetConfigType("YAML")
    
    	// any approach to require this configuration into your program.
    	// 将配置转换为 字节类型的slice,再读取
    	var yamlExample = []byte(`
    	Hacker: true
    	name: steve
    	hobbies:
    	- skateboarding
    	- snowboarding
    	- go
    	clothing:
    	  jacket: leather
    	  trousers: denim
    	age: 35
    	eyes : brown
    	beard: true
    	`)
    
    	viper.ReadConfig(bytes.NewBuffer(yamlExample))
    
    	viper.Get("name") // this would be "steve"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

6. 覆盖设置:

  • 前面最开始就讲过, 显示调用Set优先级最高,可用于覆盖配置。
  • 官方介绍是说: 这些可以来自命令行标志,也可以来自您自己的应用程序逻辑。(自己通过逻辑判断是否 设置,更改)
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
  • 1
  • 2

7. 注册和使用别名: Registering and Using Aliases

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

    // 这里建立别名
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

8. 使用环境变量:Working with Environment Variables

  • 可以直接查看文档介绍: https://github.com/spf13/viper#working-with-environment-variables

    • AutomaticEnv():
      BindEnv(string…) : error:
      SetEnvPrefix(string): 使用前缀
      SetEnvKeyReplacer(string…) *strings.Replacer
      AllowEmptyEnv(bool)
  • 实例

    // 自动转换为大写
    SetEnvPrefix("spf") // will be uppercased automatically
    // 绑定键名
    BindEnv("id")
    // 默认的就为这个SPF_ID
    os.Setenv("SPF_ID", "13") // typically done outside of the app
    
    id := Get("id") // 13
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

9 。使用flags(命令行参数): Working with Flags

  • 没咋用过,可以看官方文档

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 还要远程读取等内容,就不介绍了,没使用过。文档都要介绍和 例子。

11. 从viper 中获取值:Getting Values From 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 方法在找不到值的时候,都会返回 0 值,为了检测键是否存在,提供了 IsSet()方法
    • AllSettings() 获取所有得值。

12. 访问嵌套的键: Accessing nested keys

  • 下面例子展示的: 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
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 使用 点 . 分隔符,进行嵌套访问:GetString("datastore.metric.host") // (returns "127.0.0.1")

    • 还有下面的情况:

      {
          "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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

13. 提取子树: Extracting a sub-tree

  • 在开发可重用模块时,提取配置的子集并将其传递给模块通常很有用。这样,可以使用不同的配置多次实例化模块。
    • 例如
cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
cache1Config := viper.Sub("cache.cache1")
// cache1Config 就代表 cache1

并且还可以定义一个缓存函数,基于 这个,便于进行缓存。
cache1 := NewCache(cache1Config)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

14. 反序列化: Unmarshaling

  • 您还可以选择将所有或特定值解组到结构、映射等。
  • 有两个函数可以选择:
    • 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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 当解析的key 含有默认的分隔符 . 的时候,需要修改分隔符

      // 修改默认的分隔符
      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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 还支持写入嵌套的结构体,要注意 viper 的tag

      • 这样就可以读出来上面的配置
      type MysqlConfig struct {
      		Host string `mapstructure:"port"`
      		DbName string `mapstructure:"dbname"`
      		Port int `mapstructure:"port"`
      	}
      
      	type Config struct {
      		Port int `mapstructure:"port"`
      		Version string `mapstructure:"version"`
      		MysqlConfig `mapstructure:"mysql"`
      	}
      
      	var c Config
      	_ = viper.Unmarshal(&c)
      	fmt.Printf("c: %#v\n", c)
      	
      	结果:c: main.Config{Port:8080, Version:"1.17.10", 	MysqlConfig:main.MysqlConfig{Host:"13306"
      , DbName:"sql_demo", Port:13306}}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
  • 后买还有序列化为字符串, 使用多个 viper 实例(New)。就不细讲了,可以看文档。

viper读取配置文件

  • 有两种方式:
    • 第一种就是 上面的 viper.Get,等等方法。直接读取配置文件
    • 第二种就是使用 结构体来存储配置信息。 定义结构体类型的变量。 然后进行 viper.Unmarshal反序列化读取数据到结构体中, 避免了大量的 Get等操作。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/237097
推荐阅读
相关标签
  

闽ICP备14008679号