主题
13 · 配置管理 Viper
目标:用文件 + 环境变量管理配置,告别硬编码端口与密钥。
前置:12 · Gin
文档:spf13/viper · 对照 Node:dotenv+ 自研 config
1. 为何需要
| 坏做法 | 后果 |
|---|---|
port := ":8080" 写死 | 本地/容器/云无法切换 |
| 密钥进仓库 | 泄露;轮换困难 |
每处 os.Getenv | 键名散落、无默认值、难测 |
Viper:读 yaml/json/toml、环境变量、默认值;可与结构体反序列化。
2. 最小可跑
config.yaml:
yaml
server:
port: "8080"
mode: debug
mysql:
dsn: "user:pass@tcp(127.0.0.1:3306)/notes?parseTime=true&charset=utf8mb4"1
2
3
4
5
2
3
4
5
go
package config
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
Server struct {
Port string `mapstructure:"port"`
Mode string `mapstructure:"mode"`
} `mapstructure:"server"`
MySQL struct {
DSN string `mapstructure:"dsn"`
} `mapstructure:"mysql"`
}
func Load(path string) (*Config, error) {
v := viper.New()
v.SetConfigFile(path)
v.SetEnvPrefix("APP")
v.AutomaticEnv() // APP_SERVER_PORT 等需绑定或自行约定
v.SetDefault("server.port", "8080")
v.SetDefault("server.mode", "debug")
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("unmarshal: %w", err)
}
return &cfg, nil
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
main:cfg, err := config.Load("config.yaml") → r.Run(":" + cfg.Server.Port)。
3. 环境变量优先级(建议约定)
- 进程环境变量(部署注入)
config.local.yaml(本机、gitignore)config.yaml(仓库默认、无密钥)
密钥(JWT secret、DB 密码)只走环境变量,不进 git。
4. 动手清单
- [ ] 端口、mode 从配置读
- [ ]
.gitignore忽略config.local.yaml/.env - [ ] 缺文件时有清晰 error(不要 silent 默认到生产危险值)
- [ ] 写一个
config_test.go:用临时 yaml 测 Unmarshal
5. 项目驱动
| 场景 | 配置项 |
|---|---|
| 本地 vs Docker | MySQL host:127.0.0.1 vs mysql |
| JWT | jwt.secret 仅环境变量 |
| Redis 地址 | 20 篇接上 |
6. 常见坑 + AI 审查
| 坑 | 说明 |
|---|---|
AutomaticEnv 键名与嵌套不一致 | 需 BindEnv 或文档化前缀规则 |
| 配置热更新过度设计 | 入门先「启动时加载一次」 |
| AI 把 secret 写进示例 yaml 并建议提交 | 拒绝;用占位符 |
