前言
在 Go 项目里,配置通常会越来越多:接口地址、模型参数、数据库连接、Redis
地址,都会慢慢堆起来。
如果继续把这些值硬编码在代码里,后期维护会很痛苦。
这时候就可以用 Viper 来做配置管理。
这篇文章不讲一堆抽象概念,而是直接基于示例代码进行讲解,带你完成一套最常见的流程:
准备
yaml配置文件用结构体定义配置模型
用 Viper 读取配置
把配置映射到 Go 结构体
在程序里直接使用配置值
需要先说明一点:当前示例实现的是 基础配置读取,并 没有 包含热更新、环境变量覆盖、多环境切换这些功能,文章也只围绕已经实现的内容展开。
示例目录结构
目录非常小,但非常适合入门:
viper_use/
├── config/
│ ├── config.go
│ └── config.yaml
├── go.mod
└── main.go
各文件职责如下:
config/config.yaml:配置文件本体config/config.go:配置结构体定义main.go:Viper 读取配置并打印结果go.mod:项目依赖声明
安装 Viper
go get github.com/spf13/viper
第一步:准备配置文件
示例里的配置文件是 config/config.yaml:
api:
url: "https://api.example.com"
key: "123456"
model: "gpt-5.4"
max_tokens: 1024
temperature: 1.0
redis:
host: "localhost"
port: 6379
db: 0
password: "123456"
这里定义了两组配置:
api:接口地址、密钥、模型名、最大 token、温度redis:Redis 主机、端口、库编号、密码
Viper 支持多种配置格式,像 json、toml、yaml、env 都很常见。
这份示例使用的是最直观的 yaml。
第二步:定义配置结构体
配置文件准备好之后,下一步就是在 Go 里定义结构体。
示例把结构体放在 config/config.go:
package config
type Config struct {
API struct {
URL string `mapstructure:"url"`
Key string `mapstructure:"key"`
Model string `mapstructure:"model"`
MaxTokens int `mapstructure:"max_tokens"`
Temperature float64 `mapstructure:"temperature"`
} `mapstructure:"api"`
Redis struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
DB int `mapstructure:"db"`
Password string `mapstructure:"password"`
} `mapstructure:"redis"`
}
这里最关键的是 mapstructure 标签。
比如:
api.max_tokens对应MaxTokens int \mapstructure:“max_tokens”``redis.port对应Port int \mapstructure:“port”``
也就是说,结构体字段名可以按 Go 风格命名,但标签必须和配置文件中的键一致。
如果标签写错了,Unmarshal 时就可能映射不到正确字段。
第三步:告诉 Viper 去哪里找配置
接下来进入 main.go。
第一段关键逻辑,就是把配置文件的信息告诉 Viper:
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./config/")
这三行分别表示:
SetConfigName("config"):配置文件名叫configSetConfigType("yaml"):配置文件类型是yamlAddConfigPath("./config/"):去当前项目下的config目录里找
把这三项组合起来,Viper 最终会去读取:
./config/config.yaml
这一步很好理解,但有一个常见坑:AddConfigPath("./config/") 使用的是相对路径,所以你必须确保程序的运行目录正确。
如果你不是在 viper_use 根目录执行程序,而是在别的目录运行,
就很可能出现“找不到配置文件”的错误。
第四步:读取配置文件
路径和类型都设置好之后,就可以真正读取配置了:
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
ReadInConfig() 的作用很直接:读取并解析配置文件。
如果配置文件不存在、路径写错、或者文件格式不合法,这里就会返回错误。
示例里为了简单直接用了 panic(err),这样一旦出错,程序会立刻退出。
在生产项目里,更推荐你这样处理:
- 记录错误日志
- 返回给上层统一处理
- 在启动阶段明确提示配置错误原因
第五步:把配置映射到结构体
光把配置读进来还不够,实际开发里我们更希望把它变成一个结构化对象。
这正是 Unmarshal 的用途:
var cfg config.Config
err = viper.Unmarshal(&cfg)
if err != nil {
panic(err)
}
执行完成后,cfg 里就会拥有完整的配置数据。
相比一项一项调用 viper.GetString()、viper.GetInt(),Unmarshal 的好处是:
- 配置结构更清晰
- 更适合配置项较多的项目
- 对 IDE 补全和类型检查更友好
- 后续维护成本更低
这也是我更推荐的用法。
第六步:在代码中使用配置
配置成功映射到结构体之后,就可以像普通字段一样使用:
log.Printf("API URL: %s", cfg.API.URL)
log.Printf("API Key: %s", cfg.API.Key)
log.Printf("API Model: %s", cfg.API.Model)
log.Printf("API MaxTokens: %d", cfg.API.MaxTokens)
log.Printf("API Temperature: %f", cfg.API.Temperature)
log.Printf("Redis Host: %s", cfg.Redis.Host)
log.Printf("Redis Port: %d", cfg.Redis.Port)
log.Printf("Redis DB: %d", cfg.Redis.DB)
log.Printf("Redis Password: %s", cfg.Redis.Password)
这种写法的好处是很直观:
- API 相关配置集中在
cfg.API - Redis 相关配置集中在
cfg.Redis
当项目继续扩展时,你还可以继续增加:
MySQLJWTLogServer
结构依然能保持清晰。
完整示例代码
结合起来,main.go 的完整逻辑如下:
package main
import (
"log"
"useviper/config"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./config/")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
var cfg config.Config
err = viper.Unmarshal(&cfg)
if err != nil {
panic(err)
}
log.Printf("API URL: %s", cfg.API.URL)
log.Printf("API Key: %s", cfg.API.Key)
log.Printf("API Model: %s", cfg.API.Model)
log.Printf("API MaxTokens: %d", cfg.API.MaxTokens)
log.Printf("API Temperature: %f", cfg.API.Temperature)
log.Printf("Redis Host: %s", cfg.Redis.Host)
log.Printf("Redis Port: %d", cfg.Redis.Port)
log.Printf("Redis DB: %d", cfg.Redis.DB)
log.Printf("Redis Password: %s", cfg.Redis.Password)
}
整个流程可以概括成一张小图:
config.yaml
↓
ReadInConfig()
↓
Unmarshal(&cfg)
↓
cfg.API / cfg.Redis
↓
业务代码直接使用
运行效果
在 viper_use 目录下执行:
go run .
示例输出如下:
2026/03/08 16:25:11 API URL: https://api.example.com
2026/03/08 16:25:11 API Key: 123456
2026/03/08 16:25:11 API Model: gpt-5.4
2026/03/08 16:25:11 API MaxTokens: 1024
2026/03/08 16:25:11 API Temperature: 1.000000
2026/03/08 16:25:11 Redis Host: localhost
2026/03/08 16:25:11 Redis Port: 6379
2026/03/08 16:25:11 Redis DB: 0
2026/03/08 16:25:11 Redis Password: 123456
这说明两件事:
- Viper 已经成功读取了
yaml文件 Unmarshal已经把内容正确映射到了结构体
实战里要注意的几个点
虽然这个示例已经足够入门,但在真实项目里还需要注意下面这些问题。
1. 相对路径问题
AddConfigPath("./config/") 对运行目录敏感。
如果你的程序从别的目录启动,可能会直接找不到 config.yaml。
比较稳妥的做法有两种:
- 约定固定启动目录
- 用绝对路径或启动参数传入配置路径
2. mapstructure 标签必须对齐
像下面这些字段:
max_tokenstemperaturepassword
都必须和结构体标签严格对应。
只要拼写不一致,就会导致映射失败或者得到零值。
3. 字段类型必须匹配
例如:
port应该是inttemperature应该是float64url应该是string
如果配置文件里的值类型和结构体字段类型不一致,解析时就可能出错。
4. 敏感信息不要明文提交
示例里把 key 和 password 直接写进了 yaml,这是为了演示方便。
在生产环境里,建议:
- 敏感配置放环境变量
- 本地配置文件加入
.gitignore - 区分开发、测试、生产环境
5. 不要把“基础读取”写成“完整配置中心”
当前示例只实现了:
- 指定配置文件名
- 指定配置类型
- 指定查找路径
- 读取配置
- 映射到结构体
它没有实现:
- 配置热更新
- 环境变量自动覆盖
- 多配置文件合并
- 远程配置中心接入
所以在自己封装配置模块时,建议按项目需要逐步扩展,不要一开始就过度设计。
小结
如果你刚开始学 Go 配置管理,那这份示例已经足够说明 Viper 的核心用法:
- 用
yaml编写配置 - 用结构体描述配置模型
- 用
ReadInConfig()读取配置 - 用
Unmarshal()映射到结构体 - 在业务代码中通过字段访问配置
一句话总结就是:
Viper 负责把配置文件读进来,结构体负责把配置组织清楚。
