配置管理:环境变量与 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 和谐相处。

笔记加载中…