下面由golang教程栏目给大家介绍一个轻便好用的golang配置库viper,希望对需要的朋友有所帮助!
正文
viper 的功能
viper 支持以下功能:
1. 支持yaml、json、 toml、hcl 等格式的配置
2. 可以从文件、io、环境变量、command line中提取配置
3. 支持自动转换的类型解析
4. 可以远程从etcd中读取配置
示例代码
定义一个类型:
type config struct { v *viper.viper;}
用于测试的yaml配置文件 config.yaml
timestamp: 2018-07-16 10:23:19author: wzppasswd: helloinformation: name: harry age: 37 alise: - lion - nk - kaqs image: /path/header.rpg public: falsefavorite: sport: - swimming - football music: - zui xuan min zu feng luckynumber: 99
读取yaml配置文件
func loadconfigfromyaml (c *config) error { c.v = viper.new(); //设置配置文件的名字 c.v.setconfigname(config) //添加配置文件所在的路径,注意在linux环境下%gopath要替换为$gopath c.v.addconfigpath(%gopath/src/) c.v.addconfigpath(./) //设置配置文件类型 c.v.setconfigtype(yaml); if err := c.v.readinconfig(); err != nil{ return err; } log.printf(age: %s, name: %s \n, c.v.get(information.age), c.v.get(information.name)); return nil;}
注意:如果不用addconfigpath去指定路径,它会在程序执行的目录去寻找config.yaml
从io中读取配置
//由io读取配置func readconfigformio(c *config) error { c.v = viper.new() if f, err := os.open(config.yaml); err != nil{ log.printf(filure: %s, err.error()); return err; }else { conflength, _ :=f.seek(0,2); //注意,通常写c++的习惯害怕读取字符串的时候越界,都会多留出一个null在末尾,但是在这里不行,会报出如下错误: //while parsing config: yaml: control characters are not allowed //错误参考网址:https://stackoverflow.com/questions/33717799/go-yaml-control-characters-are-not-allowed-error configdata := make([]byte, conflength); f.seek(0, 0); f.read(configdata); log.printf(%s\n, string(configdata)) c.v.setconfigtype(yaml); if err := c.v.readconfig(bytes.newbuffer(configdata)); err != nil{ log.fatalf(err.error()); } } log.printf(age: %s, name: %s \n, c.v.get(information.age), c.v.get(information.name)); return nil;}
上面的代码是把配置文件中的数据导入io,然后再从io中读取
从环境变量中读取配置
//读取本地的环境变量func envconfigprefix(c *config) error { c.v = viper.new(); //bindenv($1,$2) // 如果只传入一个参数,则会提取指定的环境变量$1,如果设置了前缀,则会自动补全 前缀_$1 //如果传入两个参数则不会补全前缀,直接获取第二参数中传入的环境变量$2 os.setenv(log_level, info); if nil == c.v.get(log_level ) { log.printf(log_level is nil); }else { return errornotmacth; } //必须要绑定后才能获取 c.v.bindenv(log_level); log.printf(log_level is %s, os.getenv(log_level)); //会获取所有的环境变量,同时如果过设置了前缀则会自动补全前缀名 c.v.automaticenv(); //环境变量前缀大小写不区分 os.setenv(dev_addones,none); log.printf(dev_addones: %s, c.v.get(dev_addones)); //setenvprefix会设置一个环境变量的前缀名 c.v.setenvprefix(dev); os.setenv(dev_mode, true); //此时会自动补全前缀,实际去获取的是dev_dev_mode if nil == c.v.get(dev_mode){ log.printf(dev_mode is nil) ; }else { return errornotmacth; } //此时我们直接指定了loglevel所对应的环境变量,则不会去补全前缀 c.v.bindenv(loglevel, log_level); log.printf(log_level: %s, c.v.get(loglevel)) ; return nil}
setenvprefix 和 automaticenv、bindenv搭配使用很方便,比如说我们把当前程序的环境变量都设置为xx_ ,这样方便我们管理,也避免和其他环境变量冲突,而在读取的时候又很方便的就可以读取。
方便的替换符
func envcongireplacer(c *config, setperfix bool) error { c.v = viper.new(); c.v.automaticenv(); c.v.setenvkeyreplacer(strings.newreplacer(.,_)); os.setenv(api_version,v0.1.0); //replacer和prefix一起使用可能会冲突,比如我下面的例子 //因为会自动补全前缀最终由获取api_version变成api_api_version if setperfix{ c.v.setenvprefix(api);} if s := c.v.get(api.version); s==nil{ return errornoxexistkey }else { log.printf(%s, c.v.get(api.version)); } return nil;}
我们有时候需要去替换key中的某些字符,来转化为对应的环境变脸,比如说例子中将' . '替换为'_' ,由获取api.version变成了api_version,但是有一点需要注意的,setenvprefix和setenvkeyreplacer一起用的时候可能会混淆。
别名功能
//设置重载 和别名func setandaliases(c *config) error { c.v = viper.new(); c.v.set(name,wzp); c.v.registeralias(id,name); c.v.set(id,mr.wang); //我们可以发现当别名对应的值修改之后,原本的key也发生变化 log.printf(id %s, name %s,c.v.get(id),c.v.get(name) ); return nil;}
我们可以为key设置别名,当别名的值被重置后,原key对应的值也会发生变化。
序列化和反序列化
type favorite struct { sports []string; music []string; luckynumber int;}type information struct { name string; age int; alise []string; image string; public bool}type yamlconfig struct { timestamp string author string passwd string information information favorite favorite;}//将配置解析为struct对象func umshalstruct(c *config) error { loadconfigfromyaml(c); var cf yamlconfig if err := c.v.unmarshal(&cf); err != nil{ return err; } return nil;}func yamlstringsettings(c *config) string { c.v = viper.new(); c.v.set(name, wzp); c.v.set(age, 18); c.v.set(aliase,[]string{one,two,three}) cf := c.v.allsettings() bs, err := yaml.marshal(cf) if err != nil { log.fatalf(unable to marshal config to yaml: %v, err) } return string(bs)}func jsonstringsettings(c *config) string { c.v = viper.new(); c.v.set(name, wzp); c.v.set(age, 18); c.v.set(aliase,[]string{one,two,three}) cf := c.v.allsettings() bs, err := json.marshal(cf) if err != nil { log.fatalf(unable to marshal config to yaml: %v, err) } return string(bs)}
超级实惠的一个功能,直接把配置反序列化到一个结构体,爽歪歪有木有?也可以把设置直接序列化为我们想要的类型:yaml、json等等
从command line中读取配置
func main() { flag.string(mode,run,please input the mode: run or debug); pflag.int(port,1080,please input the listen port); pflag.string(ip,127.0.0.1,please input the bind ip); //获取标准包的flag pflag.commandline.addgoflagset(flag.commandline); pflag.parse(); //bindflag //在pflag.init key后面使用 viper.bindpflag(port, pflag.lookup(port)); log.printf(set port: %d, viper.getint(port)); viper.bindpflags(pflag.commandline); log.printf(set ip: %s, viper.getstring(ip));}
可以使用标准的flag也可以使用viper包中自带的pflag,作者建议使用pflag。
监听配置文件
//监听配置文件的修改和变动func watchconfig(c *config) error { if err := loadconfigfromyaml(c); err !=nil{ return err; } ctx, cancel := context.withcancel(context.background()); c.v.watchconfig() //监听回调函数 watch := func(e fsnotify.event) { log.printf(config file is changed: %s \n, e.string()) cancel(); } c.v.onconfigchange(watch); <-ctx.done(); return nil;}
重点来了啊,这个可以说是非常非常实用的一个功能,以往我们修改配置文件要么重启服务,要么搞一个api去修改,viper把这个功能帮我们实现了。只要配置文件被修改保存后,我们事先注册的watch函数就回被触发,只要我们在这里面添加更新操作就ok了。不过美中不足的是,它目前只监听配置文件。
拷贝子分支
func testsubconfig(t *testing.t) { c := config{}; loadconfigfromyaml(&c); sc := c.v.sub(information); sc.set(age, 80); scs,_:=yaml.marshal(sc.allsettings()) t.log(string(scs)); t.logf(age: %d, c.v.getint(information.age));}
拷贝一个子分支最大的用途就是我们可以复制一份配置,这样在修改拷贝的时候原配置不会被修改,如果修改的配置出现了问题,我们可以方便的回滚。
获取配置项的方法
//测试各种get类型func testgetvalues(t *testing.t) { c := &config{} if err := loadconfigfromyaml(c); err != nil{ t.fatalf(%s: %s,t.name(), err.error()); } if info := c.v.getstringmap(information); info != nil{ t.logf(%t, info); } if aliases := c.v.getstringslice(information.aliases); aliases != nil{ for _, a := range aliases{ t.logf(%s,a); } } timestamp := c.v.gettime(timestamp); t.logf(%s, timestamp.string()); if public := c.v.getbool(information.public); public{ t.logf(the information is public); } age := c.v.getint(information.age); t.logf(%s age is %d, c.v.getstring(information.name), age);}
如果我们直接用get获取的返回值都是interface{}类型,这样我们还要手动转化一下,可以直接指定类型去获取,方便快捷。
除了以上所说的功能外,viper还有从etcd提取配置以及自定义flage的功能,这些大家感兴趣可以自己去了解一下。
有趣的应用
虽然unmarshal struct已经足够好用了,但有作者还是想开发一下新的玩法,比如说这个配置文件和当前的新版本不是很匹配,当然实际生产中我们是要讲究向下兼容的。
var yamlconfig = yamlconfig{}; yctype := reflect.typeof(yamlconfig); for i := 0 ; i < yctype.numfield();i++{ name := yctype.field(i).name; element := reflect.valueof(yamlconfig).field(i).interface(); if err = config.unmarshalkey(name, element); err != nil{ logger.errorf(error reading configuration:, err); } }
如上代码所示,我们从最外围的结构体中找出子元素的名称和interface,然后分别解析,这样及时某一项缺失了我们也可以及时提醒用户,或者设置缺省配置,还有很多好玩的方法,大家可以互相参考哦。
更多golang相关技术文章,请访问go语言栏目!
以上就是方便好用的golang配置库(viper)的详细内容。
