【注意】最后更新于 June 14, 2019,文中内容可能已过时,请谨慎使用。
简介
写程序总是需要一定的配置或者参数的。参数的来源可以是:命令行输入、配置文件和环境变量。本文就已经了解到Go语言的几种配置或者参数方式加一说明。
初步估计分为:
原生参数获取方式
命令行获取参数
原生的命令行获取参数有两种方式:简单粗暴的和参数解析
简单粗暴的方式直接获取 os.Args
数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import (
"fmt"
"os"
)
func main(){
fmt.Println("命令行的参数有", len(os.Args))
// 遍历 os.Args 切片,就可以得到所有的命令行输入参数值
for i, v := range os.Args {
fmt.Printf("args[%v]=%v\n", i, v)
}
}
/*
go run get_cmd_data_s.go -u utst -p ptest
命令行的参数有 5
args[0]=/var/folders/rq/24z0mdzj6tj65w__1xppmzwn2w8lw2/T/go-build234466484/b001/exe/get_cmd_data_s
args[1]=-u
args[2]=utst
args[3]=-p
args[4]=ptest
*/
|
linux命令行参数解析方式。示例代码如下
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
|
package main
import (
"fmt"
"flag"
)
func main(){
// 定义几个变量,用于接收命令行的参数值
var user string
var password string
var host string
var port int
// &user 就是接收命令行中输入 -u 后面的参数值,其他同理
flag.StringVar(&user, "u", "root", "账号,默认为root")
flag.StringVar(&password, "p", "", "密码,默认为空")
flag.StringVar(&host, "h", "localhost", "主机名,默认为localhost")
flag.IntVar(&port, "P", 3306, "端口号,默认为3306")
// 解析命令行参数写入注册的flag里
flag.Parse()
// 输出结果
fmt.Printf("user:%v\npassword:%v\nhost:%v\nport:%v\n",
user, password, host, port)
}
/*
go run get_cmd_data_c.go
user:root
password:
host:localhost
port:3306
go run get_cmd_data_c.go -p "test"
user:root
password:test
host:localhost
port:3306
go run get_cmd_data_c.go -p test -P2006
flag provided but not defined: -P2006
Usage of /var/folders/rq/24z0mdzj6tj65w__1xppmzwn2w8lw2/T/go-build192990242/b001/exe/get_cmd_data_c:
-P int
端口号,默认为3306 (default 3306)
-h string
主机名,默认为localhost (default "localhost")
-p string
密码,默认为空
-u string
账号,默认为root (default "root")
exit status 2
go run get_cmd_data_c.go -p test -P 2006
user:root
password:test
host:localhost
port:2006
*/
|
命令行获取参数,以上两种方式基本满足大部分的需求了
三方工具
viper
官方网址:https://github.com/spf13/viper
安装命令:go get -u github.com/spf13/viper
什么是 Viper
Viper是Go应用程序的完整配置解决方案,包括12-Factor应用程序。它旨在在应用程序中工作,并可以处理所有类型的配置需求和格式。它支持:
- 设置默认值
- 从JSON,TOML,YAML,HCL和Java属性配置文件中读取
- 实时观看和重新读取配置文件(可选)
- 从环境变量中读取
- 从远程配置系统(etcd或Consul)读取,并观察变化
- 从命令行标志读取
- 从缓冲区读取
- 设置显式值
Viper可以被认为是所有应用程序配置需求的注册表。
为何选择Viper
构建现代应用程序时,您不必担心配置文件格式; 你想专注于构建真棒软件。Viper就是为此提供帮助的。
Viper为您做了以下事情:
- 以JSON,TOML,YAML,HCL或Java属性格式查找,加载和解组配置文件。
- 提供一种机制来为不同的配置选项设置默认值。
- 提供一种机制来为通过命令行标志指定的选项设置覆盖值。
- 提供别名系统,轻松重命名参数,而不会破坏现有代码。
- 可以很容易地区分用户提供命令行或配置文件与默认值相同的时间。
Viper使用以下优先顺序。每个项目优先于其下方的项目:
- explicit call to Set
- flag
- env
- config
- key/value store
- default
Viper设置默认值
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
|
package main
import(
"fmt"
"github.com/spf13/viper"
)
func main(){
viper.SetDefault("key_a_1", "a_1")
viper.SetDefault("key_a_2", "a_2")
viper.SetDefault("key_b", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("key_b.test","test")
config := viper.AllSettings()
fmt.Println(config)
fmt.Printf("key_a_1 value is %v\n", viper.Get("key_a_1"))
fmt.Printf("key_a_2 value is %v\n", viper.Get("key_a_2"))
fmt.Printf("key_b value is %v\n", viper.Get("key_b"))
fmt.Printf("key_b.tag value is %v\n", viper.Get("key_b.tag"))
fmt.Printf("key_b.test value is %v\n", viper.Get("key_b.test"))
}
//viper_default.go
/*
go run viper_default.go
map[key_a_1:a_1 key_a_2:a_2 key_b:map[test:test]]
key_a_1 value is a_1
key_a_2 value is a_2
key_b value is map[test:test]
key_b.tag value is <nil>
key_b.test value is test
*/
|
Viper读取配置文件
Viper需要最少的配置,因此它知道在哪里查找配置文件。Viper支持JSON,TOML,YAML,HCL和Java Properties文件。Viper可以搜索多个路径,但目前单个Viper实例仅支持单个配置文件。Viper不会默认使用任何配置搜索路径,而是将默认值决定应用于应用程序。
以下是如何使用Viper搜索和读取配置文件的示例。不需要任何特定路径,但应在预期配置文件的位置提供至少一个路径。
1
2
3
4
5
6
7
8
9
10
|
viper.SetConfigName("config") // name of config file (without extension)
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: %s \n", err))
}
|
监听并重新读取配置文件
Viper支持在运行时让应用程序实时读取配置文件。
需要重新启动服务器以使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何一个节拍。
只需告诉viper实例watchConfig即可。您可以选择为Viper提供每次发生更改时运行的功能。
确保在调用之前添加所有configPath WatchConfig()
1
2
3
4
|
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
|
从io.Reader读取配置
Viper预定义了许多配置源,例如文件,环境变量,标志和远程K / V存储,但您不受它们的约束。您还可以实现自己的必需配置源并将其提供给viper。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
viper.SetConfigType("yaml") // 或viper.SetConfigType(“YAML”)
//任何需要将此配置放入程序的方法
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")
|
Viper设置并覆盖配置值
1
2
3
4
|
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)
|
Viper注册和使用别名
别名允许多个键引用单个值
1
2
3
4
5
6
7
8
|
viper.RegisterAlias("loud", "Verbose")
viper.Set("verbose", true) // same result as next line
viper.Set("loud", true) // same result as prior line
viper.GetBool("loud") // true
viper.GetBool("verbose") // true
|
Viper使用环境变量
Viper完全支持环境变量。有四种方法可以帮助使用ENV:
1
2
3
4
5
6
|
AutomaticEnv()
BindEnv(string...) : error
SetEnvPrefix(string)
SetEnvKeyReplacer(string...) *strings.Replacer
|
BindEnv
需要一个或两个参数。第一个参数是键名,第二个是环境变量的名称。环境变量的名称区分大小写。如果未提供ENV
变量名,则Viper
将自动假设密钥名称与ENV
变量名称匹配,但ENV
变量为IN ALL CAPS
。当您明确提供ENV变量名称时,它不会自动添加前缀。
使用ENV
变量时要认识到的一件重要事情是每次访问时都会读取该值。Viper
在BindEnv
调用时不会修复该值。
AutomaticEnv
尤其是当与结合了强大的帮手 SetEnvPrefix
。调用时,Viper
将在任何viper.Get
请求发出时检查环境变量。它将适用以下规则。它将检查一个环境变量,其名称与大写的键匹配,并以EnvPrefix
前缀。
SetEnvKeyReplacer
允许您使用strings.Replacer
对象重写Env
键到一定程度。如果要-在Get()
调用中使用或使用某些内容 ,但希望环境变量使用_分隔符,则此选项非常有用。可以在中找到使用它的示例viper_test.go
ENV实例
1
2
3
4
5
6
|
SetEnvPrefix("spf") //将自动大写
BindEnv("id")
os.Setenv("SPF_ID", "13") // 通常在应用以外完成
id := Get("id") // 13
|
使用flag
Viper能够绑定到flag。
就像BindEnv,在调用绑定方法时,不会设置该值。这意味着您可以尽早绑定,甚至可以在init()函数中绑定 。
对于单个标志,该BindPFlag()方法提供此功能。
1
2
|
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
|
您还可以绑定一组现有的pflags
(pflag.FlagSet
)
1
2
3
4
5
6
7
|
pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname")
|
在Viper
中使用pflag
并不排除使用 标准库中使用标志包的其他包。pflag
包可以通过导入这些标志来处理为标志包定义的标志。这是通过调用名为AddGoFlagSet()
的pflag
包提供的便利函数来实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
...
}
|
flag接口
如果您不使用,Viper
提供两个Go
接口来绑定其他标志系统Pflags
FlagValue
代表一个标志。这是一个关于如何实现此接口的非常简单的示例:
1
2
3
4
5
6
|
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绑定它:
1
|
viper.BindFlagValue("my-flag-name", myFlag{})
|
远程key/value存储
要在Viper
中启用远程支持,请对viper/remote
包进行空白导入:
import _ "github.com/spf13/viper/remote"
Viper
将读取key/value
存储(如etcd
或Consul
)中的路径检索的配置字符串(如JSON
,TOML
,YAML
或HCL
)。这些值优先于默认值,但会被从磁盘,标志或环境变量检索的配置值覆盖。
Viper
使用crypt
从K / V存储中检索配置,这意味着您可以存储加密的配置值,并在拥有正确的gpg密钥环时自动解密。加密是可选的。
您可以将远程配置与本地配置结合使用,也可以独立使用。
crypt
有一个命令行帮助程序,您可以使用它来将配置放入K / V存储区。crypt
在http://127.0.0.1:4001
上默认为etcd
。
1
2
3
4
|
go get github.com/xordataexchange/crypt/bin/crypt
crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
|
确认您的值已设置:
1
2
3
|
crypt get -plaintext /config/hugo.json
|
远程key/value存储示例 - 未加密
1
2
3
4
5
|
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") //因为字节流中没有文件扩展名,支持的扩展名是“json”,“toml”,“yaml”,“yml”,“properties”,“props”,“prop”
err := viper.ReadRemoteConfig()
|
远程key/value存储示例 - 加密
1
2
3
4
5
|
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”
err := viper.ReadRemoteConfig()
|
监听etcd中的变化 - 未加密
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
|
//或者,您可以创建一个新的viper实例
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml")
// 第一次从远程配置中读取
err := runtime_viper.ReadRemoteConfig()
//解密配置
runtime_viper.Unmarshal(&runtime_conf)
// 打开一个goroutine来永远监听远程变化
go func(){
for {
time.Sleep(time.Second * 5) // 每次请求后延迟
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
//将新配置解组到我们的运行时配置结构中。您还可以使用通道
//实现信号以通知系统更改
runtime_viper.Unmarshal(&runtime_conf)
}
}()
|
Viper获取值
在Viper中,有几种方法可以根据值的类型获取值。存在以下功能和方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Get(key string) : interface{}
GetBool(key string) : bool
GetFloat64(key string) : float64
GetInt(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()方法检查给定密钥是否存在。
实例:
1
2
3
4
5
6
|
viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
}
|
参考
- Go:获取命令行参数
- github.com/spf13/viper go viper包介绍