golang 每日一库 Viper (2)

 


本文主要介绍一个轻便好用的Golang配置库viper

正文

viper 的功能

  viper 支持以下功能:

  1. 支持Yaml、Json 等格式的配置
  2. 可以从文件、io、环境变量、command line中提取配置
  3. 支持自动转换的类型解析
  4. 实时观看和重新读取配置文件(可选)
  5. 可以远程从etcd中读取配置

示例

定义一个config类型:

type config struct {
    v  *viper.Viper;
}

读取yaml:

注意:如果不用AddConfigPath去指定路径,它会在程序执行的目录去寻找app.yaml结尾文件

 yaml配置app.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
   Data:
     Name: "lisi"
     Alias:
     - "l"
     - "a"

Favorite:
  Sport:
  - "swimming"
  - "football"
  Music:
  - "zui xuan min zu feng"
  LuckyNumber: 99
复制代码

代码示例:

复制代码
func LoadConfigFromYaml(c *config) error {
    c.v = viper.New()
    //设置配置文件的名字
    c.v.SetConfigName("app")
    //添加配置文件所在的路径
    c.v.AddConfigPath("./etc")
    c.v.AddConfigPath("./")
    //设置配置文件类型
    c.v.SetConfigType("yaml")

    if err := c.v.ReadInConfig(); err != nil {
        return err
    }
    log.Println(c.v.AllKeys())
    log.Printf("age: %s, name: %s \n", c.v.Get("information.age"),
        c.v.Get("information.name"))
    return nil
}
复制代码

 从文件中读取配置:

复制代码
func LoadConfigReadFile(c *config, fileType, filePath string) error {
    c.v = viper.New()
    switch fileType {
    case "yaml", "yml":
        //设置配置文件类型
        c.v.SetConfigType("yaml")
    case "json":
        c.v.SetConfigType("json")case "env":
        c.v.SetConfigType("env")
    default:
        c.v.SetConfigType("yaml")
        log.Println("default config file type to yaml")
    }
    log.Println("current config file type is", fileType)
    if f, err := os.Open("app.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))
        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
}
复制代码

从json中读取:

配置文件app.json:

复制代码
{
    "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,
        "Data": {
            "Name": "lisi",
            "Alias": [
                "l",
                "a"
            ]
        }
    },
    "Favorite": {
        "Sport": [
            "swimming",
            "football"
        ],
        "Music": [
            "zui xuan min zu feng"
        ],
        "LuckyNumber": 99
    }
}
复制代码

代码示例:

复制代码
func LoadConfigFromJson(c *config) error {
    c.v = viper.New()
    //设置配置文件的名字
    c.v.SetConfigName("app")
    //添加配置文件所在的路径,注意在Linux环境下%GOPATH要替换为$GOPATH
    c.v.AddConfigPath("./etc")
    c.v.AddConfigPath("./")
    //设置配置文件类型
    c.v.SetConfigType("json")

    if err := c.v.ReadInConfig(); err != nil {
        return err
    }
    log.Println(c.v.AllKeys())
    log.Printf("file type json age: %s, name: %s \n", c.v.Get("information.age"),
        c.v.Get("information.name"))
    return nil
}
复制代码

 文件修改自动reload

Viper支持让您的应用程序在运行时实时读取配置文件。

需要重新启动服务器才能使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,不会错过任何一个节拍。

只需告诉viper实例watchConfig即可。您可以为Viper提供一个函数,以便在每次发生更改时运行。

请确保在调用WatchConfig()之前添加了所有的configPaths

viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()

代码示例:

复制代码
package main

import (
    "bytes"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/fsnotify/fsnotify"
    "github.com/gin-gonic/gin"
    "github.com/spf13/viper"
)
func main() {// 设置默认值
    viper.SetDefault("fileDir", "./")
    // 读取配置文件
    viper.SetConfigFile("./app.yaml")     // 指定配置文件路径
    viper.SetConfigName("app")            // 配置文件名称(无扩展名)
    viper.SetConfigType("yaml")           // 如果配置文件的名称中没有扩展名,则需要配置此项
    viper.AddConfigPath("/etc/appname/")  // 查找配置文件所在的路径
    viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径
    viper.AddConfigPath(".")              // 还可以在工作目录中查找配置

    err := viper.ReadInConfig() // 查找并读取配置文件
    if err != nil {             // 处理读取配置文件的错误
        panic(fmt.Errorf("Fatal error config file: %s \n", err))
    }

    // 实时监控配置文件的变化 WatchConfig 开始监视配置文件的更改。
    viper.WatchConfig()
    // OnConfigChange设置配置文件更改时调用的事件处理程序。
    // 当配置文件变化之后调用的一个回调函数
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("Config file changed:", e.Name)
    })

    r := gin.Default()
    r.GET("/version", func(c *gin.Context) {
        // GetString以字符串的形式返回与键相关的值。
        c.String(http.StatusOK, viper.GetString("version"))
    })
    r.Run()
}
复制代码

代码运行输出结果:

复制代码
===================viper read from yaml===================
2024/01/08 16:15:33 [favorite.sport favorite.luckynumber information.alise information.data.alias information.image information.data.name passwd information.name favorite.music version information.age information.public timestamp author]
2024/01/08 16:15:33 file type yaml age: 37sss, name: Harry1 
===================viper read yaml from file===================
2024/01/08 16:15:33 current config file type is yaml
2024/01/08 16:15:33 TimeStamp: "2018-07-16 10:23:19"
Author: "WZP"
PassWd: "Hello"
version: "v1.011111"
Information:
   Name: "Harry"
   Age: "37"
   Alise:
   - "Lion"
   - "NK"
   - "KaQS"
   Image: "/path/header.rpg"
   Public: false
   Data:
     Name: "lisi"
     Alias:
     - "l"
     - "a"

Favorite:
  Sport:
  - "swimming"
  - "football"
  Music:
  - "zui xuan min zu feng"
  LuckyNumber: 99

2024/01/08 16:15:33 age: 37, name: Harry 
===================viper read from json===================
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:    export GIN_MODE=release
 - using code:    gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /version                  --> main.LoadConfigFromJson.func2 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
复制代码

 完整代码:

复制代码
package main

import (
    "bytes"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/fsnotify/fsnotify"
    "github.com/gin-gonic/gin"
    "github.com/spf13/viper"
)

type config struct {
    v *viper.Viper
}

func LoadConfigFromYaml(c *config) error {
    c.v = viper.New()
    //设置配置文件的名字
    c.v.SetConfigName("app")
    //添加配置文件所在的路径
    c.v.AddConfigPath("./etc")
    c.v.AddConfigPath("./")
    //设置配置文件类型
    c.v.SetConfigType("yaml")

    if err := c.v.ReadInConfig(); err != nil {
        return err
    }
    log.Println(c.v.AllKeys())
    log.Printf("file type yaml age: %s, name: %s \n", c.v.Get("information.age"),
        c.v.Get("information.name"))
    return nil
}

func LoadConfigFromJson(c *config) error {
    c.v = viper.New()
    //设置配置文件的名字
    c.v.SetConfigName("app")
    //添加配置文件所在的路径
    c.v.AddConfigPath("./etc")
    c.v.AddConfigPath("./")
    //设置配置文件类型
    // because there is no file extension in a stream of bytes,
    // supported extensions are "json", "toml", "yaml", "yml",
    // "properties", "props", "prop", "env", "dotenv"
    c.v.SetConfigType("json")

    if err := c.v.ReadInConfig(); err != nil {
        return err
    }
    log.Println(c.v.AllKeys())
    log.Printf("file type json age: %s, name: %s \n", c.v.Get("information.age"),
        c.v.Get("information.name"))
    return nil
}

func LoadConfigReadFile(c *config, fileType, filePath string) error {
    c.v = viper.New()
    switch fileType {
    case "yaml", "yml":
        //设置配置文件类型
        c.v.SetConfigType("yaml")
    case "json":
        c.v.SetConfigType("json")
    case "toml":
        c.v.SetConfigType("toml")
    case "hcl":
        c.v.SetConfigType("hcl")
    case "env":
        c.v.SetConfigType("env")
    default:
        c.v.SetConfigType("yaml")
        log.Println("default config file type to yaml")
    }
    log.Println("current config file type is", fileType)
    if f, err := os.Open("app.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))
        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
}
func main() {
    fmt.Println("===================viper read from yaml===================")
    if err := LoadConfigFromYaml(&config{}); err != nil {
        panic(err)
    }
    fmt.Println("===================viper read yaml from file===================")
    if err := LoadConfigReadFile(&config{}, "yaml", "./app.yaml"); err != nil {
        panic(err)
    }
    fmt.Println("===================viper read from json===================")
    if err := LoadConfigFromJson(&config{}); err != nil {
        panic(err)
    }
    fmt.Println("===================viper listen file change ===================")
    // 设置默认值
    viper.SetDefault("fileDir", "./")
    // 读取配置文件
    viper.SetConfigFile("./app.yaml")     // 指定配置文件路径
    viper.SetConfigName("app")            // 配置文件名称(无扩展名)
    viper.SetConfigType("yaml")           // 如果配置文件的名称中没有扩展名,则需要配置此项
    viper.AddConfigPath("/etc/appname/")  // 查找配置文件所在的路径
    viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径
    viper.AddConfigPath(".")              // 还可以在工作目录中查找配置

    err := viper.ReadInConfig() // 查找并读取配置文件
    if err != nil {             // 处理读取配置文件的错误
        panic(fmt.Errorf("Fatal error config file: %s \n", err))
    }

    // 实时监控配置文件的变化 WatchConfig 开始监视配置文件的更改。
    viper.WatchConfig()
    // OnConfigChange设置配置文件更改时调用的事件处理程序。
    // 当配置文件变化之后调用的一个回调函数
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("Config file changed:", e.Name)
    })

    r := gin.Default()
    r.GET("/version", func(c *gin.Context) {
        // GetString以字符串的形式返回与键相关的值。
        c.String(http.StatusOK, viper.GetString("version"))
    })
    r.Run()
}
复制代码

 

posted @   haonan071  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!

喜欢请打赏

扫描二维码打赏

了解更多

点击右上角即可分享
微信分享提示