Loading...
Loading...
Compare original and translation side by side
go get github.com/spf13/viper@latestgo get github.com/spf13/viper@latestPersistentPreRunEBindPFlagsamber/cc-skills-golang@golang-spf13-cobraPersistentPreRunEBindPFlagsamber/cc-skills-golang@golang-spf13-cobra1. explicit Set() — viper.Set("key", val) highest priority
2. flag — bound pflag.Flag
3. env var — BindEnv / AutomaticEnv
4. config file — ReadInConfig / MergeInConfig
5. KV remote — etcd / Consul
6. default — viper.SetDefault("key", val) lowest priority1. 显式Set() — viper.Set("key", val) 最高优先级
2. 命令行参数 — 绑定的pflag.Flag
3. 环境变量 — BindEnv / AutomaticEnv
4. 配置文件 — ReadInConfig / MergeInConfig
5. 远程KV存储 — etcd / Consul
6. 默认值 — viper.SetDefault("key", val) 最低优先级viper.SetConfigName("config")
viper.AddConfigPath("$HOME/.myapp")
if err := viper.ReadInConfig(); err != nil {
var notFound *viper.ConfigFileNotFoundError
if !errors.As(err, ¬Found) {
return fmt.Errorf("reading config: %w", err) // propagate real errors only
}
}ConfigFileNotFoundErrorMergeInConfigviper.SetConfigName("config")
viper.AddConfigPath("$HOME/.myapp")
if err := viper.ReadInConfig(); err != nil {
var notFound *viper.ConfigFileNotFoundError
if !errors.As(err, ¬Found) {
return fmt.Errorf("reading config: %w", err) // 仅传播真实错误
}
}ConfigFileNotFoundErrorMergeInConfig// ✓ Good — all three wired together at startup
viper.SetEnvPrefix("MYAPP") // prevent collisions: PORT → MYAPP_PORT
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // database.host → MYAPP_DATABASE_HOST
viper.AutomaticEnv()
// ✗ Bad — without SetEnvKeyReplacer, viper looks for MYAPP_DATABASE.HOST (dot preserved)BindEnvAllowEmptyEnv// ✓ 正确做法——启动时同时配置三项
viper.SetEnvPrefix("MYAPP") // 避免冲突:PORT → MYAPP_PORT
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // database.host → MYAPP_DATABASE_HOST
viper.AutomaticEnv()
// ✗ 错误做法——未设置SetEnvKeyReplacer时,viper会查找MYAPP_DATABASE.HOST(保留点号)BindEnvAllowEmptyEnvinit()PersistentPreRunERunERunEfunc init() {
rootCmd.PersistentFlags().Int("port", 8080, "listen port")
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
// viper.BindPFlags(cmd.Flags()) — bind an entire FlagSet at once
}AllowEmptyEnvinit()PersistentPreRunERunERunEfunc init() {
rootCmd.PersistentFlags().Int("port", 8080, "listen port")
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
// viper.BindPFlags(cmd.Flags()) — 一次性绑定整个FlagSet
}AllowEmptyEnvviper.Unmarshalmapstructuretype Config struct {
Port int `mapstructure:"port"`
Database struct {
MaxConn int `mapstructure:"max_conn"` // explicit tag: mapstructure won't convert underscore→camelCase
} `mapstructure:"database"`
}
var cfg Config
viper.Unmarshal(&cfg)mapstructureUnmarshalKey("database", &dbCfg)Sub("database").UnmarshalSubtime.Durationnet.IPDecodeHookviper.Unmarshalmapstructuretype Config struct {
Port int `mapstructure:"port"`
Database struct {
MaxConn int `mapstructure:"max_conn"` // 显式标签:mapstructure不会自动将下划线转换为驼峰式
} `mapstructure:"database"`
}
var cfg Config
viper.Unmarshal(&cfg)mapstructureUnmarshalKey("database", &dbCfg)Sub("database").UnmarshalSubtime.Durationnet.IPDecodeHookviper.Sub("database")*viper.ViperUnmarshalKey("database", &dbCfg)viper.Sub("database")*viper.ViperUnmarshalKey("database", &dbCfg)viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { /* re-apply changed values */ })WatchConfigecho >> config.yamlviper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) { /* 重新应用变更后的值 */ })WatchConfigecho >> config.yamlviper.New()v := viper.New()
v.SetConfigFile("testdata/config.yaml")
require.NoError(t, v.ReadInConfig())t.SetenvReset()viper.New()v := viper.New()
v.SetConfigFile("testdata/config.yaml")
require.NoError(t, v.ReadInConfig())t.SetenvReset()database.hostDATABASE.HOSTDATABASE_HOSTConfigFileNotFoundErrormapstructureviper.New()Execute()RunERunEdatabase.hostDATABASE.HOSTDATABASE_HOSTConfigFileNotFoundErrormapstructureviper.New()Execute()RunERunE| Mistake | Why it fails | Fix |
|---|---|---|
| | Add |
No | Silently misses nested and underscore-named fields | Add |
| Using global viper in tests | State from one test contaminates the next, causing flaky ordering | Create |
Missing | Missing config file crashes a service that should run on flags/env alone | |
| 错误操作 | 失败原因 | 修复方案 |
|---|---|---|
使用 | | 在 |
结构体字段未添加 | 会遗漏嵌套字段和带下划线的字段 | 为每个字段添加 |
| 测试中使用全局viper实例 | 一个测试的状态会影响下一个测试,导致测试结果不稳定 | 每个测试创建 |
未检查 | 缺失配置文件会导致本应通过参数/环境变量运行的服务崩溃 | 使用 |
samber/cc-skills-golang@golang-clisamber/cc-skills-golang@golang-spf13-cobrasamber/cc-skills-golang@golang-testingsamber/cc-skills-golang@golang-clisamber/cc-skills-golang@golang-spf13-cobrasamber/cc-skills-golang@golang-testing