配置管理:环境变量与 viper 读取 yaml
本章解决什么问题:端口、数据库地址等硬编码在代码里,换环境就得改代码重编译。业界通用做法是“12 因素”式的配置外置:默认值放 yaml,运行时可用环境变量覆盖。viper 是 Go 社区最常用的配置库,本章给出与 Gin 搭配的常用模式。
先装依赖
viper 不是 Gin 的一部分,需要单独引入:
go get github.com/spf13/viper
一份 yaml 配置
在项目根目录放 config.yaml:
server:
port: 8080
mode: release
appname: demo-gin
读取并应用
package main
import (
"log"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config") // 文件名(不含扩展名)
viper.SetConfigType("yaml") // 显式声明格式
viper.AddConfigPath(".") // 查找路径,可多次调用
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("read config: %v", err)
}
viper.AutomaticEnv() // 允许环境变量兜底
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
gin.SetMode(viper.GetString("server.mode"))
r := gin.Default()
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"pong": true}) })
r.Run(":" + strconv.Itoa(viper.GetInt("server.port")))
}
viper 读取 yaml 后,嵌套键用点号访问:viper.GetString("server.mode")、viper.GetInt("server.port")、viper.GetBool(...)。多环境部署时可用命令行参数或环境变量指定不同配置文件路径(如 APP_ENV=prod 时指向 config-prod.yaml)。
环境变量覆盖
AutomaticEnv 让“先找配置里有没有,没有再看环境变量”,配合 SetEnvKeyReplacer 把点号替换成下划线,server.port 就能被 SERVER_PORT 覆盖。viper 的取值优先级大致为:显式 Set > 环境变量 > 配置文件 > 默认值(细节以 viper 官方文档为准)。
结构化读取:Unmarshal
散落的 GetString/GetInt 代码一多也乱,可定义与 yaml 对应的结构体,用 mapstructure tag 声明映射,一次 Unmarshal 全部落位:
type Config struct {
Server struct {
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"`
AppName string `mapstructure:"appname"`
} `mapstructure:"server"`
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
log.Fatalf("unmarshal config: %v", err)
}
之后代码里统一用 cfg.Server.Port 访问,类型安全、IDE 提示友好。yaml 结构变化时记得同步改结构体。
关键点
- 读取三步:指定文件名/格式/路径 → ReadInConfig → 按键取值或 Unmarshal。
- 点号键路径配 AutomaticEnv + SetEnvKeyReplacer,就能获得“环境变量同名覆盖”。
- 结构体配置用 mapstructure tag 承接,启动时校验一次、失败即退出,避免带病运行。
小结
viper 把“默认值 + yaml + 环境变量覆盖”串成一条链,启动时读一次并尽早失败。下一章解决日志的最后一公里:让标准库 log/slog 与 Gin 和谐相处。