Go mod 好菜系列 - 0x05 viper 把配置从代码里请出去
从配置文件、环境变量、默认值和热更新角度详细讲 viper,看看它怎么帮你把项目配置真正组织起来。
只要项目稍微长一点,配置迟早会变成一个很现实的问题。数据库地址、端口、Redis、JWT 密钥、日志级别、运行环境,这些东西一开始你可能图快直接写在代码里,但只要环境一多,这种写法马上就会开始反噬你。
viper 是来解决什么的
它最核心的价值,是帮你把配置来源统一起来。你可以从:
- 配置文件
- 环境变量
- 默认值
- 命令行参数衔接
把这些来源揉成一套比较稳定的读取方式。
一个最常见的起手式
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./configs")
if err := viper.ReadInConfig(); err != nil {
return err
}这段代码的意思很朴素:去这些路径找一个叫 config.yaml 的文件,然后读进来。
配置文件读起来为什么比环境变量舒服
因为很多配置是有结构的。比如:
app:
port: 8080
mysql:
dsn: "user:pass@tcp(127.0.0.1:3306)/demo"
redis:
addr: "127.0.0.1:6379"如果全靠环境变量当然也能做,但层级感会差一些。配置文件尤其适合本地开发和多配置项项目。
默认值也很实用
viper.SetDefault("app.port", 8080)
viper.SetDefault("log.level", "info")默认值的意义不是偷懒,而是让你的程序在配置缺一小块时不至于立刻罢工。尤其是那些“有默认值就能跑”的选项,完全值得提前设好。
环境变量衔接
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")这样你就可以让环境变量去覆盖配置文件。这个模式在部署时特别好用:本地开发用 yaml,线上用环境变量改关键项。
绑定到结构体才更像样
type Config struct {
App struct {
Port int `mapstructure:"port"`
} `mapstructure:"app"`
MySQL struct {
DSN string `mapstructure:"dsn"`
} `mapstructure:"mysql"`
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return err
}这一步非常关键。很多人用 viper 用了半天,最后还是满项目写字符串 key 去取值,结果配置系统也变成了另一种散乱。绑结构体以后,配置才真正开始像一份“模型”。
热更新能用,但别先冲动
viper 确实支持监听配置文件变化,但这功能不是每个项目都需要。很多服务配置在启动时读一次已经够了。别因为看见它能热更新,就强行给项目加一层动态复杂度。
viper 最适合的项目位置
通常是启动阶段统一加载一次,然后把解析好的配置结构体往下传。别让业务代码到处直接 import viper 然后边走边读,那样后面很难维护,也难测试。
小结
viper 这道菜属于那种“看起来不炫,但工程里很常吃”的类型:
- 它让配置文件、环境变量和默认值能统一管理
- 配置最好最后落到结构体,而不是一地字符串 key
- 它适合放在启动阶段统一处理
- 热更新是可选项,不是必选项
下一篇我们收这一套系列的最后一口:cobra。虽然它不是 Web 项目必备,但凡是涉及 CLI、脚手架、管理命令的 Go 工程,几乎总会碰到它。