配置不可硬编码,必须通过环境变量或外部配置文件动态注入,优先使用环境变量;Viper需按“文件→环境变量→默认值”顺序显式加载并绑定,启动时校验关键字段避免运行时panic。

配置不能硬编码在代码里
Go 应用一旦部署到不同环境(dev/staging/prod),端口、数据库地址、密钥这些值必然不同。把它们写死在 main.go 或 config struct 里,等于每次换环境都要改代码、重新编译——这直接违反「可复现」和「可隔离」原则。
常见错误现象:panic: failed to connect to database: dial tcp 127.0.0.1:5432: connect: connection refused,其实只是 prod 环境连的是远程 RDS,而代码里还写着本地 localhost。
- 所有与环境强相关的字段(
DB_URL、HTTP_PORT、REDIS_ADDR)必须抽离为外部输入 - 优先走环境变量,而不是配置文件:Docker/K8s 原生支持
env注入,且无需挂载额外 volume - 若必须用文件(如 YAML),确保它不随代码提交——加进
.gitignore,并用 CI 流水线按环境注入
Viper 初始化时要设对配置源优先级
Viper 是最常用的 Go 配置库,但它默认行为容易踩坑:它会合并多个来源的键值,但没明确声明哪一层覆盖哪一层。比如环境变量和 YAML 文件里都有 LOG_LEVEL,最终取哪个?不显式控制,结果不可控。
正确做法是按确定性顺序加载,并关闭自动合并:
立即学习“go语言免费学习笔记(深入)”;
- 先调
viper.SetConfigType("yaml"),再viper.ReadInConfig()读文件(仅用于 fallback) - 紧接着调
viper.AutomaticEnv(),再手动用viper.BindEnv("HTTP_PORT", "HTTP_PORT")显式绑定关键变量 - 最后调
viper.SetDefault("LOG_LEVEL", "info")—— 默认值只在所有来源都未提供时生效
这样能确保:环境变量 > 配置文件 > 默认值,逻辑清晰,调试时查 viper.AllSettings() 一眼可知来源。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
启动时必须做配置校验,别等 runtime panic
很多团队把校验逻辑放在 handler 里,比如第一次访问 API 才发现 DB_URL 是空字符串,然后 panic 或返回 500。这属于线上事故前兆。
应该在 main() 初始化阶段就失败快出:
- 定义一个
type Config struct { DBURL string `mapstructure:"db_url"`; HTTPPort int `mapstructure:"http_port"` } - 用
viper.Unmarshal(&cfg)解析后,立刻检查cfg.DBURL == ""或cfg.HTTPPort - 错误信息要带上下文,例如
missing required config: DB_URL (env or config file),别只写"config invalid"
CI 构建镜像后,可加一步 docker run --rm -e DB_URL="" myapp --validate-config 提前暴露问题。
敏感配置别进 Git,也别靠 runtime 拼接
有人把密钥存在 .env 文件里,然后用 godotenv.Load() 加载——这文件如果误提交,密钥就泄露了。还有人写 os.Getenv("ENV") + "_KEY" 拼环境变量名,结果 staging_KEY 没定义,程序静默用空值连 Redis。
安全底线很朴素:
-
.env必须在.gitignore中,且 CI/CD 流水线禁止上传该文件 - 密钥类字段(
JWT_SECRET、AWS_ACCESS_KEY_ID)只允许从环境变量读,不支持任何 fallback - K8s 场景下,统一用
Secret挂载为 env,不要挂成文件再读——减少权限和路径依赖
配置管理不是越灵活越好,而是越确定、越不可绕过越好。漏掉一个 required 校验,或容忍一次空值 fallback,都可能让服务在凌晨三点突然不可用。

















