2. Go 环境变量管理:os.Getenv、os.LookupEnv、.env 文件与 godotenv 库的使用
环境变量是 Go 项目多环境配置的基石。本章深入剖析 Go 标准库提供的环境变量操作函数,并引入 .env 文件与 godotenv 库,帮助你在开发、测试、生产等不同环境间无缝切换配置。
2.1 标准库环境变量操作
Go 标准库 os 包提供了两个核心函数用于读取环境变量:os.Getenv 和 os.LookupEnv。理解它们的差异是正确使用环境变量的前提。
| 函数 | 签名 | 返回值 | 适用场景 |
|---|---|---|---|
os.Getenv |
func Getenv(key string) string |
返回 key 对应的值;若不存在则返回空字符串 | 快速读取,不关心变量是否存在 |
os.LookupEnv |
func LookupEnv(key string) (string, bool) |
返回 (值, 是否存在) 二元组 | 需要区分“变量存在但值为空”与“变量不存在” |
关键区别:os.Getenv 无法区分“变量未设置”和“变量设置为空字符串”两种情况。在配置校验场景中,os.LookupEnv 是更安全的选择。
2.1.1 基础使用示例
package main
import (
"fmt"
"os"
)
func main() {
// 使用 os.Getenv 快速读取
dbHost := os.Getenv("DB_HOST")
fmt.Printf("DB_HOST: %s\n", dbHost)
// 使用 os.LookupEnv 安全读取
dbPort, exists := os.LookupEnv("DB_PORT")
if !exists {
fmt.Println("DB_PORT 未设置,使用默认值 5432")
dbPort = "5432"
}
fmt.Printf("DB_PORT: %s\n", dbPort)
}
2.2 .env 文件与 godotenv 库
直接依赖系统环境变量在开发阶段存在明显痛点:每个开发者需要手动设置大量变量,且无法在代码仓库中共享默认配置。.env 文件配合 godotenv 库完美解决了这一问题。
2.2.1 .env 文件规范
.env 文件是一个纯文本文件,每行定义一个环境变量,格式为 KEY=VALUE。支持注释(以 # 开头)和空行。
# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_USER=app_user
DB_PASSWORD=dev_password
# Redis 配置
REDIS_ADDR=127.0.0.1:6379
REDIS_PASSWORD=
# 应用配置
APP_ENV=development
LOG_LEVEL=debug
最佳实践:将 .env 文件添加到 .gitignore 中,避免敏感信息泄露。同时提供 .env.example 作为模板提交到仓库,供其他开发者参考。
2.2.2 godotenv 库的使用
godotenv 是 Go 生态中最流行的 .env 文件加载库。它会在程序启动时读取 .env 文件,并将其中定义的变量注入到当前进程的环境变量中。
安装:
go get github.com/joho/godotenv
基本用法:
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
// 加载 .env 文件
err := godotenv.Load()
if err != nil {
log.Fatal("加载 .env 文件失败:", err)
}
// 之后就可以像使用普通环境变量一样读取
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
fmt.Printf("连接数据库: %s:%s\n", dbHost, dbPort)
}
2.3 环境变量加载优先级与生命周期
理解环境变量的加载顺序和生命周期,是避免配置混乱的关键。
2.4 多环境 .env 文件策略
在实际项目中,通常需要为不同环境维护不同的 .env 文件。以下是推荐的命名规范和加载策略。
| 文件名 | 用途 | 是否提交到 Git | 加载优先级 |
|---|---|---|---|
.env |
默认配置,所有环境共享的基础值 | 是(仅包含非敏感默认值) | 低 |
.env.development |
开发环境专用配置 | 是 | 中 |
.env.production |
生产环境专用配置 | 否(包含敏感信息) | 中 |
.env.local |
本地覆盖配置,每个开发者可自定义 | 否 | 高 |
2.4.1 多文件加载实现
package main
import (
"fmt"
"os"
"github.com/joho/godotenv"
)
func loadEnvConfig() {
// 获取当前环境,默认为 development
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
// 按优先级从低到高加载
// 1. 基础配置
godotenv.Load(".env")
// 2. 环境特定配置
godotenv.Load(fmt.Sprintf(".env.%s", env))
// 3. 本地覆盖配置(最高优先级)
godotenv.Load(".env.local")
// 注意:后加载的文件会覆盖先加载的相同变量
}
func main() {
loadEnvConfig()
// 读取配置
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
fmt.Printf("当前环境: %s\n", os.Getenv("APP_ENV"))
fmt.Printf("数据库地址: %s:%s\n", dbHost, dbPort)
}
安全警告:永远不要将包含真实密码、API Key、Token 等敏感信息的 .env 文件提交到 Git 仓库。使用 .env.example 作为模板,并在 .gitignore 中明确排除 .env、.env.local、.env.production 等文件。
2.5 环境变量校验与默认值
在生产环境中,环境变量缺失或格式错误可能导致运行时异常。建议在应用启动时进行集中校验。
package config
import (
"fmt"
"os"
"strconv"
)
// Config 应用配置结构体
type Config struct {
DBHost string
DBPort int
DBUser string
DBPassword string
RedisAddr string
LogLevel string
}
// LoadConfig 加载并校验配置
func LoadConfig() (*Config, error) {
cfg := &Config{}
// 字符串类型,带默认值
cfg.DBHost = getEnv("DB_HOST", "localhost")
cfg.DBUser = getEnv("DB_USER", "app")
cfg.RedisAddr = getEnv("REDIS_ADDR", "127.0.0.1:6379")
cfg.LogLevel = getEnv("LOG_LEVEL", "info")
// 整数类型,需要转换
portStr := getEnv("DB_PORT", "5432")
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("DB_PORT 格式错误: %s", portStr)
}
cfg.DBPort = port
// 必填字段校验
cfg.DBPassword = os.Getenv("DB_PASSWORD")
if cfg.DBPassword == "" {
return nil, fmt.Errorf("DB_PASSWORD 是必填字段")
}
return cfg, nil
}
// getEnv 读取环境变量,若不存在则返回默认值
func getEnv(key, defaultVal string) string {
if val, exists := os.LookupEnv(key); exists {
return val
}
return defaultVal
}
2.6 实战:构建可扩展的环境变量加载器
将上述知识整合为一个可复用的环境变量加载器,支持多文件加载、变量覆盖和配置校验。
生产建议:在 CI/CD 流水线中,建议将敏感环境变量通过平台的安全机制(如 GitHub Secrets、GitLab CI Variables、Kubernetes Secrets)注入,而非依赖 .env 文件。仅在本地开发时使用 .env 文件提升效率。
通过本章的学习,你已经掌握了 Go 环境变量管理的完整知识体系:从标准库的基础操作,到 .env 文件与 godotenv 库的进阶使用,再到多环境策略和配置校验。这些技能将为后续的依赖注入和领域边界适配打下坚实基础。