配置写入
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)
}最佳实践
- 验证配置:写入前验证配置有效性
- 备份配置:修改前备份原配置
- 原子写入:使用临时文件 + 重命名
- 错误处理:处理写入失败情况
- 权限控制:设置适当的文件权限
- 日志记录:记录配置变更操作
- 版本控制:保留配置历史版本