Viper--方便好用的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:19"
Author: "WZP"
PassWd: "Hello"
Information:
Name: "Harry"
Age: "37"
Alise:
- "Lion"
- "NK"
- "KaQS"
Image: "/path/header.rpg"
Public: false
Favorite:
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,然后分别解析,这样及时某一项缺失了我们也可以及时提醒用户,或者设置缺省配置,还有很多好玩的方法,大家可以互相参考哦。
Viper--方便好用的Golang 配置库的更多相关文章
- golang常用库:配置文件解析库-viper使用
一.viper简介 viper 配置解析库,是由大神 Steve Francia 开发,他在google领导着 golang 的产品开发,他也是 gohugo.io 的创始人之一,命令行解析库 cob ...
- golang常用库:cli命令行/应用程序生成工具-cobra使用
golang常用库:cli命令行/应用程序生成工具-cobra使用 一.Cobra 介绍 我前面有一篇文章介绍了配置文件解析库 Viper 的使用,这篇介绍 Cobra 的使用,你猜的没错,这 2 个 ...
- 一种优雅的Golang的库插件注册加载机制
一种优雅的Golang的库插件注册加载机制 你好,我是轩脉刃. 最近看到一个内部项目的插件加载机制,非常赞.当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种 ...
- 混合使用TFVC和GIT配置库的优化方案
如果要选出最近几年在软件工程领域最热的技术,那毋庸置疑就是git了.作为分布式源代码管理(DVCS)的代表,git以其超快的操作,便捷的分支合并模型和P2P模式的代码分享模式让软件开发团队的很多复杂协 ...
- Emacs golang 配置
在配置前需要下载用到的包: godoc godef gocode oracle 在下载包之前需要设置好环境变量: # Golang export GOROOT=$HOME/go export GOPA ...
- eclipse下使用maven配置库托管jar包
1.项目是通过maven配置库托管jar包 首先要保证maven配置库中有相应的jar包才能通过这个方法来添加jar包.maven的有点就是把要用到的jar包统一放在一个配置库中,在某个项目需要用到这 ...
- golang 标准库间依赖的可视化展示
简介 国庆看完 << Go 语言圣经 >>,总想做点什么,来加深下印象.以可视化的方式展示 golang 标准库之间的依赖,可能是一个比较好的切入点.做之前,简单搜了下相关的内 ...
- pip 打包项目配置库
打包项目中配置库(filename为文件名,可修改) pip freeze > filename.txt 安装配置文件中所有的库包 pip install -r filename.txt 如提示 ...
- [开源] gnet: 一个轻量级且高性能的 Golang 网络库
Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...
随机推荐
- EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况
使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...
- 【Dubbo源码阅读系列】服务暴露之本地暴露
在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...
- 基于oracle数据库存储过程的创建及调用
1.PLSQL编程 1.1概念和目的 PL/SQL(Procedure Language/SQL) PLSQL是Oracle对sql语言的过程化扩展 指在SQL命令语言中增加了过程处理语句(如分支.循 ...
- execute immediate
首先在这里发发牢骚,指责下那些刻板的书写方式,不考虑读者理不理解,感觉就是给专业人员用来复习用的一样,没有前戏,直接就高潮,实在受不了!没基础或基础差的完全不知道发生了什么,一脸懵逼的看着,一星差评! ...
- C++较精确的测试代码运行时间
#include <chrono> using namespace chrono; int main() { auto t0 = system_clock::now(); //测试代码 a ...
- C++ primer第三章作业
3.1节 练习3.1: 使用恰当的using声明重做1.4.1节(第11页)和2.6.2节(第67页)的练习 #ifdef 1 #include <iostream> using std: ...
- 基于 HTML5 Canvas 的 3D 渲染引擎构建机架式服务器
前言 今天找到了 HT 的官网里的 Demo 网站( http://www.hightopo.com/demos/index.html ),看的我眼花缭乱,目不暇接. 而且 HT 的用户手册,将例子和 ...
- Redis 4.0.X版本reshard出现错误的解决办法
原文链接:https://my.oschina.net/juluking/blog/1606222 原作者的版本是Redis 4.0.6,我的版本是4.0.8,所以猜测是否所有4.0.x版本都有此问题 ...
- Delphi判断某个类是否实现了某个接口
通过TObject.GetInterface可以获得对象的实例实现某个接口,前提条件是必须实例化对象后才能运行GetInterface 下面的方法可获取类是否实现了某个接口,并返回接口的偏移: fun ...
- string 模块
string 说明:string 模块保留了很多有用的常量和类,用来处理 string 和 unicode 对象. 作用:包含处理文本的常量和类. capwords( ) capwords( ) 的作 ...