Skip to content

配置写入 ​

Viper 支持动态修改和保存配置。

设置配置值 ​

基本设置 ​

go
viper.Set("app.name", "newapp")
viper.Set("app.port", 9090)
viper.Set("database.host", "localhost")

设置嵌套配置 ​

go
viper.Set("database.connection.max", 100)
viper.Set("database.connection.timeout", "30s")

设置复杂类型 ​

go
viper.Set("hosts", []string{"localhost", "127.0.0.1"})
viper.Set("ports", []int{8080, 8081, 8082})
viper.Set("settings", map[string]interface{}{
    "key1": "value1",
    "key2": "value2",
})

写入配置文件 ​

WriteConfig ​

覆盖写入配置文件。

go
viper.SetConfigFile("config.yaml")

// 修改配置
viper.Set("app.name", "updatedapp")
viper.Set("app.port", 9090)

// 覆盖写入
if err := viper.WriteConfig(); err != nil {
    log.Printf("写入配置失败: %v", err)
}

SafeWriteConfig ​

安全写入,文件存在则不覆盖。

go
viper.SetConfigFile("config.yaml")
viper.Set("app.name", "newapp")

// 安全写入,文件存在则返回错误
if err := viper.SafeWriteConfig(); err != nil {
    if os.IsExist(err) {
        log.Println("配置文件已存在")
    } else {
        log.Printf("写入配置失败: %v", err)
    }
}

WriteConfigAs ​

写入指定文件路径。

go
viper.Set("app.name", "myapp")
viper.Set("app.port", 8080)

// 写入指定文件
if err := viper.WriteConfigAs("config-new.yaml"); err != nil {
    log.Printf("写入配置失败: %v", err)
}

SafeWriteConfigAs ​

安全写入指定路径。

go
viper.Set("app.name", "myapp")

// 安全写入,文件存在则返回错误
if err := viper.SafeWriteConfigAs("config-new.yaml"); err != nil {
    log.Printf("写入配置失败: %v", err)
}

完整示例 ​

动态修改配置 ​

go
package main

import (
    "log"
    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigFile("config.yaml")
    if err := viper.ReadInConfig(); err != nil {
        log.Fatal(err)
    }
    
    log.Printf("当前应用名称: %s", viper.GetString("app.name"))
    
    viper.Set("app.name", "updated-app")
    
    if err := viper.WriteConfig(); err != nil {
        log.Printf("写入配置失败: %v", err)
    }
    
    log.Printf("更新后应用名称: %s", viper.GetString("app.name"))
}

创建新配置文件 ​

go
package main

import (
    "log"
    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigType("yaml")
    
    viper.Set("app.name", "newapp")
    viper.Set("app.version", "1.0.0")
    viper.Set("app.port", 8080)
    
    viper.Set("database.host", "localhost")
    viper.Set("database.port", 3306)
    
    viper.Set("cache.enabled", true)
    viper.Set("cache.ttl", 3600)
    
    if err := viper.SafeWriteConfigAs("config-new.yaml"); err != nil {
        log.Fatal(err)
    }
    
    log.Println("配置文件创建成功")
}

生成的 config-new.yaml:

yaml
app:
  name: newapp
  port: 8080
  version: "1.0.0"
cache:
  enabled: true
  ttl: 3600
database:
  host: localhost
  port: 3306

注册别名 ​

设置别名 ​

go
viper.RegisterAlias("app.name", "appName")

viper.Set("app.name", "myapp")
fmt.Println(viper.GetString("appName"))

viper.Set("appName", "newapp")
fmt.Println(viper.GetString("app.name"))

删除配置 ​

删除单个配置 ​

go
// 删除配置
viper.Set("app.name", "myapp")
fmt.Println(viper.GetString("app.name")) // myapp

viper.Set("app.name", nil)
fmt.Println(viper.IsSet("app.name")) // false

删除所有配置 ​

go
// 重置配置
viper.Reset()

配置序列化 ​

输出配置内容 ​

go
settings := viper.AllSettings()
fmt.Printf("%+v\n", settings)

获取所有键 ​

go
keys := viper.AllKeys()
for _, key := range keys {
    fmt.Println(key, "=", viper.Get(key))
}

获取配置为 YAML ​

go
import "github.com/spf13/viper"

// 获取所有配置
settings := viper.AllSettings()

// 转换为 YAML 字节
yamlData, err := yaml.Marshal(settings)
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(yamlData))

获取配置为 JSON ​

go
import "encoding/json"

settings := viper.AllSettings()

jsonData, err := json.MarshalIndent(settings, "", "  ")
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(jsonData))

配置合并 ​

合并配置 ​

go
// 从多个配置文件合并
viper.SetConfigFile("config-default.yaml")
viper.ReadInConfig()

viper.SetConfigFile("config-override.yaml")
viper.MergeInConfig()

// 合并后的配置
port := viper.GetInt("app.port")

合并配置映射 ​

go
override := map[string]interface{}{
    "app": map[string]interface{}{
        "port": 9090,
    },
}

viper.MergeConfigMap(override)

合并远程配置 ​

go
viper.AddRemoteProvider("consul", "localhost:8500", "/config/base")
viper.SetConfigType("yaml")
viper.ReadRemoteConfig()

viper.AddRemoteProvider("consul", "localhost:8500", "/config/override")
viper.MergeRemoteConfig()

配置模板 ​

使用模板生成配置 ​

go
package main

import (
    "log"
    "github.com/spf13/viper"
)

type AppConfig struct {
    Name    string `mapstructure:"name"`
    Version string `mapstructure:"version"`
    Port    int    `mapstructure:"port"`
}

type DatabaseConfig struct {
    Host     string `mapstructure:"host"`
    Port     int    `mapstructure:"port"`
    Name     string `mapstructure:"name"`
    User     string `mapstructure:"user"`
    Password string `mapstructure:"password"`
}

type Config struct {
    App      AppConfig      `mapstructure:"app"`
    Database DatabaseConfig `mapstructure:"database"`
}

func main() {
    config := Config{
        App: AppConfig{
            Name:    "myapp",
            Version: "1.0.0",
            Port:    8080,
        },
        Database: DatabaseConfig{
            Host:     "localhost",
            Port:     3306,
            Name:     "mydb",
            User:     "root",
            Password: "password",
        },
    }
    
    viper.SetConfigType("yaml")
    viper.Set("app", config.App)
    viper.Set("database", config.Database)
    
    if err := viper.SafeWriteConfigAs("config-generated.yaml"); err != nil {
        log.Fatal(err)
    }
}

环境差异配置 ​

基于环境写入配置 ​

go
func writeConfig(env string) error {
    viper.SetConfigType("yaml")
    
    switch env {
    case "dev":
        viper.Set("app.port", 8080)
        viper.Set("database.host", "localhost")
        viper.Set("database.port", 3306)
    case "prod":
        viper.Set("app.port", 80)
        viper.Set("database.host", "prod-db")
        viper.Set("database.port", 3306)
    }
    
    return viper.WriteConfigAs(fmt.Sprintf("config-%s.yaml", env))
}

合并环境配置 ​

go
func loadConfig(env string) error {
    viper.SetConfigFile("config-base.yaml")
    if err := viper.ReadInConfig(); err != nil {
        return err
    }
    
    viper.SetConfigFile(fmt.Sprintf("config-%s.yaml", env))
    if err := viper.MergeInConfig(); err != nil {
        return err
    }
    
    return nil
}

配置验证 ​

写入前验证 ​

go
func validateAndWrite() error {
    viper.Set("app.name", "myapp")
    viper.Set("app.port", 8080)
    
    if viper.GetString("app.name") == "" {
        return errors.New("app.name is required")
    }
    
    if port := viper.GetInt("app.port"); port < 1 || port > 65535 {
        return errors.New("invalid port")
    }
    
    return viper.WriteConfig()
}

使用结构体验证 ​

go
type Config struct {
    App struct {
        Name string `mapstructure:"name" validate:"required"`
        Port int    `mapstructure:"port" validate:"min=1,max=65535"`
    } `mapstructure:"app"`
}

func validateConfig() error {
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return err
    }
    
    validate := validator.New()
    return validate.Struct(config)
}

并发安全 ​

线程安全操作 ​

Viper 的操作是线程安全的。

go
var wg sync.WaitGroup

for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(idx int) {
        defer wg.Done()
        key := fmt.Sprintf("key%d", idx)
        viper.Set(key, idx)
    }(i)
}

wg.Wait()

if err := viper.WriteConfig(); err != nil {
    log.Fatal(err)
}

最佳实践 ​

  1. 验证配置:写入前验证配置有效性
  2. 备份配置:修改前备份原配置
  3. 原子写入:使用临时文件 + 重命名
  4. 错误处理:处理写入失败情况
  5. 权限控制:设置适当的文件权限
  6. 日志记录:记录配置变更操作
  7. 版本控制:保留配置历史版本

参考:https://github.com/spf13/viper