当前位置: 首页 > news >正文

深圳营销型网站推广优化大师使用心得

深圳营销型网站推广,优化大师使用心得,中国菲律宾引渡,宿迁网站建设介绍公司作者:程序员CKeen 博客:http://ckeen.cn​​​​​​​​​​​​​​​​​​​​​ 长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好😄 目录 1. viper的介绍 …

作者:程序员CKeen
博客:http://ckeen.cn​​​​​​​​​​​​​​​​​​​​​

长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好😄

目录

1. viper的介绍

2. viper的使用

2.1  Viper对象的创建

2.2 预设默认配置。 

2.3 从命令行工具的选项参数Flags读取

2.4 从环境变量读取

2.5 从配置文件读取

2.6 从远程key/value存储读取

2.7 监听配置变化

2.8 写入配置到文件

3. 源码分析--配置读取的顺序

4. 参考资料​​​​​​​


1. viper的介绍

viper是go一个强大的流行的配置解决方案的库。viper是spf13的另外一个重量级库。有大量项目都使用该库,比如hugo, docker等。 它基本上可以处理所有类型的配置需求和格式, viper支持功能

  • 设置默认配置
  • 支持各种配置文件,如JSON,TOML, YAML, HCL, envfile和Java属性配置文件
  • 支持监听文件变化以及重新读取配置
  • 支持从环境变量读取配置
  • 支持从远程配置系统(etcd或Consul)读取配置,并能监听远程配置修改
  • 支持从命令行标志Flag读取配置,比如搭配cobra使用
  • 支持读取缓冲区数据

Viper主要为我们做以下工作:

  • 查找、加载和解组JSON、TOML、YAML、HCL、INI、envfile或Java属性格式的配置文件。
  • 提供一种机制来为不同的配置选项设置默认值。
  • 提供一种机制来为通过命令行标志指定的选项设置覆盖值。
  • 提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
  • 当用户提供了与默认值相同的命令行或配置文件时,很容易区分它们。

viepr的安装很简单,直接再工程中使用go get命令安装即可

$ go get github.com/spf13/viper

2. viper的使用

2.1  Viper对象的创建

Viper的是viper库的主要实现对象, viper提供了下面的方法可以获取Viper实例:

func GetViper() *Viper
func New() *Viper
func NewWithOptions(opts ...Option) *Viper
func Sub(key string) *Viper
  • 使用viper.GetViper()获取的为全局的Viper实例对象,默认使用viper包使用也是该全局Viper实例。查看viper的源码,可以看到viper默认提供了一个全局的Viper实例:
var v *Viperfunc init() {v = New()
}// New returns an initialized Viper instance.
func New() *Viper {v := new(Viper)v.keyDelim = "."v.configName = "config"v.configPermissions = os.FileMode(0o644)v.fs = afero.NewOsFs()v.config = make(map[string]interface{})v.override = make(map[string]interface{})v.defaults = make(map[string]interface{})v.kvstore = make(map[string]interface{})v.pflags = make(map[string]FlagValue)v.env = make(map[string][]string)v.aliases = make(map[string]string)v.typeByDefValue = falsev.logger = jwwLogger{}v.resetEncoding()return v
}
  • New和NewWithOptions为我们提供了创建实例的方法。New是直接使用默认配置项进行创建,而NewWithOptions可以传入一些配置项。
func New1() *viper.Viper {return viper.New()
}func New2() *viper.Viper {return viper.NewWithOptions()
}
  • Sub为我们读取子配置项提供了一个新的实例Viper,使用子配置项的Viper实例,我们可以限定他只读取该子配置项的配置。
v := viper.Sub("db")
url := v.Get("url")
log.Printf("mysql url:%s\n", url)

2.2 预设默认配置。 

viper.SetDefault("ContentDir", "content")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})viper.SetDefault("redis.port", 6379)
viper.SetDefault("mysql.url", "root:root@tcp(127.0.0.1:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local")

 我们可以将一些项目初始化的默认的配置项,直接使用默认配置。

2.3 从命令行工具的选项参数Flags读取

viper主要提供了以下四个方法,可以绑定命令行参数的输入的选项值:

func (v *Viper) BindFlagValue(key string, flag FlagValue) error
func (v *Viper) BindFlagValues(flags FlagValueSet) (err error)
func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error
func (v *Viper) BindPFlags(flags *pflag.FlagSet) error

这里我们主要结合之前讲的cobra库中的pflag来讲解一下viper对Flags选项参数的绑定。
在cobra中,我们主要通过cobra.Command来组织不同的命令和子命令,这里我们我通过在root根命令来做测试。代码如下:

func init(){rootCmd.Flags().String("author", "YOUR NAME", "Author name for copyright attribution")rootCmd.Flags().String("email", "YOUR EMAIL", "Author email for contact")// 绑定多个key-value值viper.BindPFlags(rootCmd.Flags())// 单个绑定不同的keyviper.BindPFlag("author", rootCmd.Flags().Lookup("author"))viper.BindPFlag("email", rootCmd.Flags().Lookup("email"))rootCmd.AddCommand(version.VersionCmd)
}

在cobra的命令的run回调方法中,我们通过viper的来获取输入的选项值

func run(){fmt.Println("go root cmd run")fmt.Println(viper.GetString("author"))fmt.Println(viper.GetString("email"))
}

启动应用,传入参数测试一下:

go run main.go --author ckeen --email ck@gmail.com

查看一下打印结果,可以看到从viper成功获取到以flag传入的参数值:

➜  cli git:(master) ✗ go run main.go --author keen --email ck@gmail.com
go root cmd run
ckeen
ck@gmail.com

2.4 从环境变量读取

viper支持绑定环境变量,以对环境变量进行操作。对环境变量的操作主要有以下的函数:

func (v *Viper) AutomaticEnv()			// 开启绑定环境变量
func (v *Viper) BindEnv(input ...string) error		// 绑定系统中某个环境变量
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer)
func (v *Viper) SetEnvPrefix(in string)

那我们是怎么对环境变量进行操作的呢? 下面我们说一下主要的操作方法:

  1. 使用AutomaticEnv()开启绑定环境变量,没开启的时候不会从环境变量获取,开启后可以获取环境变量的值。如果不想开启所有环境变量值,可以使用BindEnv(input …string)方法绑定单个环境变量的绑定, 那么只有该绑定的环境变量的key才能获取到值
  2. 绑定环境变量后,可以使用正常的Get方法来获取变量值,示例代码如下:
    func testEnv(){v := New1()os.Setenv("CK_HOME","123")os.Setenv("CK_NAME","ckeen")v.AutomaticEnv()//v.BindEnv("SHELL")v.AllowEmptyEnv(true)log.Printf("os env:%+v\n", os.Environ())log.Printf("env: %+v\n", v.Get("HOME"))log.Printf("env: %+v\n", v.Get("SHELL"))v.SetEnvPrefix("CK")log.Printf("ck-home: %+v\n", v.Get("HOME"))log.Printf("ck-email: %+v\n", v.Get("NAME"))}
  3. 还可以通过SetEnvPrefix()方法设置环境变量前缀, 前缀和Key之间用下划线分割

2.5 从配置文件读取

我们重点讲一下操作实例的方法: 

先看下我们的配置文件app.yml文件:

app:name: viper-testmode: devdb:mysql:url: "root:root@tcp(127.0.0.1:3306)/stock?charset=utf8mb4&parseTime=True&loc=Local"redis:host: 127.0.0.1port:  6067db: 0passwd: 123456

然后我们具体来看下使用viper库实际的操作方法: 

  • 初始化配置
    func InitConfig() (*viper.Viper, error) {v := viper.New()v.AddConfigPath(".")					// 添加配置文件搜索路径,点号为当前目录v.AddConfigPath("./configs")		// 添加多个搜索目录v.SetConfigType("yaml")				// 如果配置文件没有后缀,可以不用配置v.SetConfigName("app.yml")			// 文件名,没有后缀// v.SetConfigFile("configs/app.yml")// 读取配置文件if err := v.ReadInConfig(); err == nil {log.Printf("use config file -> %s\n", v.ConfigFileUsed())} else {return nil,err}return v, nil
    }
    

    首先这里我们添加一个配置文件搜索路径,点号表示当前路径,搜索路径可以添加多个然后设置了配置文件类型,这里我们设置文件类型为yaml,

    接着我们设置了配置文件名称,这个文件可以从配置的搜索路径从查找。

    最后我们通过提供的ReadInConfig()函数读取配置文件

  • 读取配置文件
    // 通过.号来区分不同层级,来获取配置值
    log.Printf("app.mode=%s\n", v.Get("app.mode"))
    log.Printf("db.mysql.url=%s\n", v.Get("db.mysql.url"))
    log.Printf("db.redis.host=%s\n", v.GetString("db.redis.host"))
    log.Printf("db.redis.port=%d\n", v.GetInt("db.redis.port"))// 使用Sub获取子配置,然后获取配置值
    v2 := v.Sub("db")
    log.Printf("db.mysql.url:%s\n", v2.Sub("mysql").GetString("url"))
    log.Printf("db.redis.host:%s\n", v2.Sub("redis").GetString("host"))
    log.Printf("db.redis.port:%s\n", v2.Sub("redis").GetInt("port"))

    这里我们就可以使用viper提供的不同的方法来读取配置值,多层级的的配置项我们使用点号进行区分。除了上面这些方法,viper还提供了如下获取类型获取配置项值:

     注: 其中重要的一个函数IsSet可以用来判断某个key是否被设置

 viper库提供了不同的类型的函数,来方便我们读取不同数据类型的配置。

2.6 从远程key/value存储读取

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

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

Viper将读取从Key/Value存储系统中的检索到的配置字符串(如JSONTOMLYAML格式)。viper目前支持Consul/Etcd/firestore三种Key/Value的存储系统。下面我来演示从etcd读取配置:

  • 首先我们安装crypt的工具
    go get github.com/bketelsen/crypt/bin/crypt
  • 使用crypt的命令,将app.yml的文件添加到detcd
    crypt set --endpoint=http://127.0.0.1:2379 -plaintext /config/app.yml /Users/ckeen/Documents/code/gosource/go-awesome/go-samples/viper/configs/app.yml
  • 添加viper的操作远程资源的配置
    _ "github.com/spf13/viper/remote"
  • 实现从远程读取配置
    func InitConfigFromRemote() (*viper.Viper,error) {v := viper.New()// 远程配置v.AddRemoteProvider("etcd","http://127.0.0.1:2379","config/app.yml")//v.SetConfigType("json")v.SetConfigFile("app.yml")v.SetConfigType("yml")if err := v.ReadRemoteConfig(); err == nil {log.Printf("use config file -> %s\n", v.ConfigFileUsed())} else {return nil, err}return v, nil
    }func main(){v, err := InitConfigFromRemote()if err != nil {log.Printf("read remote error:%+v\n")}log.Printf("remote read app.mode=%+v\n", v.GetString("app.mode"))log.Printf("remote read db.mysql.url=%+v\n", v.GetString("db.mysql.url"))}
  • 测试打印结果

2.7 监听配置变化

viper提供如下两种监听配置的函数,一个是本地的监听和一个远程监听的:

func (v *Viper) WatchConfig()
func (v *Viper) WatchRemoteConfig() error
func (v *Viper) WatchRemoteConfigOnChannel() error

我们主要看一下监听本地文件变更的示例

v, err := InitConfig()
if err != nil {
log.Fatalf("viper读取失败, error:%+v\n",err)
}// 监听到文件变化后的回调
v.OnConfigChange(func(e fsnotify.Event) {fmt.Println("Config file changed:", e.Name)fmt.Println(v.Get("db.redis.passwd"))
})v.WatchConfig()// 阻塞进程退出
time.Sleep(time.Duration(1000000) * time.Second)

我们使用前面的InitConfig()方法来初始化本地文件读取配置,然后设定了监听函数,最后使用WatchConfig()开启本地文件监听。

当我们修改本地配置configs/app.yml的db.redis.passwd的值,然后保存后,我们可以看到控制台有打印最新修改后的值,不要我们重新去获取。

2.8 写入配置到文件

viper提供了如下四个写入配置文件发方法

func (v *Viper) SafeWriteConfig() error
func (v *Viper) SafeWriteConfigAs(filename string) error
func (v *Viper) WriteConfig() error
func (v *Viper) WriteConfigAs(filename string) error

使用SafeWriteConfig()和WriteConfig()时,可以先设定SetConfigFile()设定配置文件的路径。配置写入示例:
 

v := New1()
v.SetConfigFile("./hello.yml")log.Printf("config path:%+v\n", v.ConfigFileUsed())v.SetDefault("author","CKeen")
v.SetDefault("email", "ck@gmail.com")v.Set("hello", "foo")v.Set("slice", []string {"slice1","slice2","slice3"})v.SetDefault("test.web", "https://ckeen.cn")v.WriteConfig()//v.WriteConfigAs("./hello.yml")

如果使用SafeWriteConfigAs()或者WriteConfigAs()方法,则直接传入配置文件路径即可。

3. 源码分析--配置读取的顺序

通过上面的示例我们知道,viper读取配置主要通过一系列Get方法来实现,我们从Get方法跳转到源码可以发现, 主要获取的配置值的为find方法, 方法实现如下:

func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {var (val    interface{}exists boolpath   = strings.Split(lcaseKey, v.keyDelim)nested = len(path) > 1)// compute the path through the nested maps to the nested valueif nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {return nil}// if the requested key is an alias, then return the proper keylcaseKey = v.realKey(lcaseKey)path = strings.Split(lcaseKey, v.keyDelim)nested = len(path) > 1// Set() override firstval = v.searchMap(v.override, path)if val != nil {return val}if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {return nil}// PFlag override nextflag, exists := v.pflags[lcaseKey]if exists && flag.HasChanged() {switch flag.ValueType() {case "int", "int8", "int16", "int32", "int64":return cast.ToInt(flag.ValueString())case "bool":return cast.ToBool(flag.ValueString())case "stringSlice", "stringArray":s := strings.TrimPrefix(flag.ValueString(), "[")s = strings.TrimSuffix(s, "]")res, _ := readAsCSV(s)return rescase "intSlice":s := strings.TrimPrefix(flag.ValueString(), "[")s = strings.TrimSuffix(s, "]")res, _ := readAsCSV(s)return cast.ToIntSlice(res)case "stringToString":return stringToStringConv(flag.ValueString())default:return flag.ValueString()}}if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {return nil}// Env override nextif v.automaticEnvApplied {// even if it hasn't been registered, if automaticEnv is used,// check any Get requestif val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {return val}if nested && v.isPathShadowedInAutoEnv(path) != "" {return nil}}envkeys, exists := v.env[lcaseKey]if exists {for _, envkey := range envkeys {if val, ok := v.getEnv(envkey); ok {return val}}}if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {return nil}// Config file nextval = v.searchIndexableWithPathPrefixes(v.config, path)if val != nil {return val}if nested && v.isPathShadowedInDeepMap(path, v.config) != "" {return nil}// K/V store nextval = v.searchMap(v.kvstore, path)if val != nil {return val}if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" {return nil}// Default nextval = v.searchMap(v.defaults, path)if val != nil {return val}if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" {return nil}if flagDefault {// last chance: if no value is found and a flag does exist for the key,// get the flag's default value even if the flag's value has not been set.if flag, exists := v.pflags[lcaseKey]; exists {switch flag.ValueType() {case "int", "int8", "int16", "int32", "int64":return cast.ToInt(flag.ValueString())case "bool":return cast.ToBool(flag.ValueString())case "stringSlice", "stringArray":s := strings.TrimPrefix(flag.ValueString(), "[")s = strings.TrimSuffix(s, "]")res, _ := readAsCSV(s)return rescase "intSlice":s := strings.TrimPrefix(flag.ValueString(), "[")s = strings.TrimSuffix(s, "]")res, _ := readAsCSV(s)return cast.ToIntSlice(res)case "stringToString":return stringToStringConv(flag.ValueString())default:return flag.ValueString()}}// last item, no need to check shadowing}return nil
}

通过源码,我们可以知道viper读取配置的优先级顺序:alias别名 > 调用Set设置 > flag > env > config > key/value store > default

还有一个注意点:viper配置键不区分大小写,因为viper内部对key统一转为了小写。

4. 参考资料

  1. viper的包地址:viper package - github.com/spf13/viper - Go Packages

  2. viper的github地址: GitHub - spf13/viper: Go configuration with fangs

http://www.qdjiajiao.com/news/7230.html

相关文章:

  • 创意福州网站建设微博搜索引擎优化
  • 网站权限设计方案网站被禁用如何解决
  • 做网站需要掌握的技术谷歌广告投放教程
  • 公司网站建设 阿里营销图片大全
  • 自助建设网站app开发价格表
  • 外贸网站制作设计企业qq多少钱一年
  • 教学类网站怎么做semester
  • 网站切换语言怎么做全球搜索引擎排名2021
  • 做字体的网站销售渠道都有哪些
  • 如何不要钱做网站百度高级检索入口
  • 广东专注网站建设怎么样如何查一个关键词的搜索量
  • 网站跳出seo快排软件
  • java 自动登录网站域名批量查询工具
  • 如何做原创漫画网站兰州百度推广的公司
  • 重庆网站设计最加科技厉害的seo顾问
  • 网页设计与网站开发方向站长之家查询
  • 网站开发设计怎么样宁波的网络营销服务公司
  • 钦州电商网站建设nba新闻最新消息
  • 合肥网站优化费用网站建设有多少公司
  • 网站建设一般多少钱比较合适快速优化官网
  • 网页游戏排行榜2017项链seo关键词
  • 微信小程序+网站开发seo服务 文库
  • 鞋子的网站策划方案模板百度seo找哪里
  • wordpress真的好用吗百度关键词优化软件排名
  • 南山企业网站建设站长工具网址查询
  • dnf盗号网站怎么做品牌软文案例
  • 建设网站的请示seo概念的理解
  • 网站被攻击排名优化工具
  • 广州病毒感染最新消息优化设计答案六年级上册
  • 电子商务网站建设的工具百度seo 优化