os.getenv("env")在handler中为空是因为调用时机错误,必须在gin.default()和r.run()前通过godotenv.load()加载.env文件或设置系统变量,且linux/macos下变量名严格区分大小写。

多环境配置不是 Gin 自己的事,是 Viper + 环境变量 + 启动参数配合的结果;Gin 本身不识别 dev/sit/pro,全靠你手动加载对应配置文件。
为什么 os.Getenv("ENV") 在 handler 里读不到或为空
因为 os.Getenv 调用时机错了——它必须在 gin.Default() 和 r.Run() 之前就拿到值。常见错误包括:
- 在中间件或路由 handler 里才调
os.Getenv("ENV"),此时可能还没加载 .env 或容器注入延迟 - 没提前执行
godotenv.Load(".env"),导致系统环境变量根本没被加载进来 - Linux/macOS 下变量名大小写敏感:
ENV=prod和env=prod是两个不同变量 - 使用
viper.AutomaticEnv()后又忘了调viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")),导致app.port映射不到APP_PORT
viper.ReadInConfig() 和 MergeInConfig() 必须分清主次
想实现 application.yaml(公共)+ application-prod.yaml(生产覆盖),不能对两个文件都调 ReadInConfig()——后者会清空已有配置。
- 先调
viper.ReadInConfig()加载基础配置(如configs/application.yaml) - 再调
viper.MergeInConfig()合并环境配置(如configs/application-prod.yaml),同名 key 被覆盖 - 别漏掉
viper.SetConfigType("yaml"):每次设新文件路径后都要重设类型,否则解析失败 - YAML 文件缩进必须用空格,不能用 tab,否则直接报错
如何安全传入环境名:命令行参数比环境变量更可控
用 flag.StringVar 接收 -env=prod,比依赖 os.Getenv("ENV") 更可靠,避免本地残留变量污染线上行为。
- 启动时指定:
go run main.go -env=prod - 代码中捕获:
flag.StringVar(&env, "env", "dev", "environment: dev|sit|pro") - 拼接配置路径:
viper.SetConfigFile(fmt.Sprintf("configs/application-%s.yaml", env)) - 如果 fallback 到默认配置,加一句
viper.AddConfigPath(".")防止找不到文件
StaticFS 和 embed.FS 在多环境下的静态资源处理
静态文件路径本身不随环境切换,但资源内容可能需区分(如 CDN 域名、调试开关 JS)。这时不要硬编码路径,而是通过配置驱动:
- 在
application-dev.yaml中配static_cdn: http://localhost:8080/static,生产配static_cdn: https://cdn.example.com - 模板里用
{{.Config.StaticCdn}}渲染,而不是写死/static - 嵌入式资源(
go:embed)无法运行时切换,所以只用于不变的公共资源;动态路径必须走配置
最易被忽略的一点:Viper 的 MergeInConfig() 不会自动创建缺失的嵌套结构,比如公共配置里没有 database 节点,而生产配置写了 database.url,那整个 database 对象不会被创建——得确保公共配置至少有空节点占位。











