在Golang项目开发过程中,开发、测试、生产环境的配置参数往往存在差异,比如数据库连接地址、服务端口、第三方接口密钥等,硬编码这些配置会导致切换环境时需要频繁修改代码,增加出错概率。合理的多环境配置方案可以让项目在不同场景下自动适配对应参数,无需改动核心代码。

多环境配置的核心思路
多环境配置的核心是将环境相关的参数从代码中剥离,通过外部标识区分当前运行环境,再加载对应环境的配置内容。常见的实现方式有三种:环境变量区分、配置文件区分、结构体映射配置,实际项目中通常会结合多种方式使用。
1. 基于环境变量区分环境
环境变量是最常用的环境标识方式,我们可以在启动程序前设置APP_ENV变量,程序读取该变量的值判断当前环境。不同环境的变量值可以设置为dev、test、prod。
首先定义环境常量和配置结构体:
package config
import (
"os"
"fmt"
)
// 环境常量定义
const (
EnvDev = "dev"
EnvTest = "test"
EnvProd = "prod"
)
// 应用配置结构体
type AppConfig struct {
Env string // 当前环境
ServerPort string // 服务端口
DBHost string // 数据库主机地址
DBPort string // 数据库端口
DBUser string // 数据库用户名
DBPassword string // 数据库密码
DBName string // 数据库名称
}
然后编写加载配置的函数,根据环境变量的值返回对应配置:
// 加载配置
func LoadConfig() (*AppConfig, error) {
// 读取环境变量中的当前环境,默认是开发环境
env := os.Getenv("APP_ENV")
if env == "" {
env = EnvDev
}
// 根据环境返回对应配置
switch env {
case EnvDev:
return &AppConfig{
Env: EnvDev,
ServerPort: "8080",
DBHost: "127.0.0.1",
DBPort: "3306",
DBUser: "root",
DBPassword: "dev_password",
DBName: "dev_db",
}, nil
case EnvTest:
return &AppConfig{
Env: EnvTest,
ServerPort: "8081",
DBHost: "192.168.0.1",
DBPort: "3306",
DBUser: "test_user",
DBPassword: "test_password",
DBName: "test_db",
}, nil
case EnvProd:
// 生产环境配置可以从环境变量读取,避免敏感信息硬编码
return &AppConfig{
Env: EnvProd,
ServerPort: os.Getenv("SERVER_PORT"),
DBHost: os.Getenv("DB_HOST"),
DBPort: os.Getenv("DB_PORT"),
DBUser: os.Getenv("DB_USER"),
DBPassword: os.Getenv("DB_PASSWORD"),
DBName: os.Getenv("DB_NAME"),
}, nil
default:
return nil, fmt.Errorf("不支持的环境类型: %s", env)
}
}
2. 基于配置文件区分环境
当配置参数较多时,硬编码在代码中不利于维护,可以将不同环境的配置放在对应的配置文件中,比如config.dev.yaml、config.test.yaml、config.prod.yaml。这里以YAML配置文件为例,需要先安装YAML解析库:
go get gopkg.in/yaml.v3
开发环境配置文件config.dev.yaml内容如下:
server: port: "8080" database: host: "127.0.0.1" port: "3306" user: "root" password: "dev_password" db_name: "dev_db"
测试环境配置文件config.test.yaml内容如下:
server: port: "8081" database: host: "192.168.0.1" port: "3306" user: "test_user" password: "test_password" db_name: "test_db"
生产环境配置文件config.prod.yaml内容如下:
server: port: "80" database: host: "prod-db.ipipp.com" port: "3306" user: "prod_user" password: "prod_password" db_name: "prod_db"
编写配置文件加载逻辑:
package config
import (
"os"
"fmt"
"gopkg.in/yaml.v3"
)
// 从配置文件加载配置
func LoadConfigFromFile() (*AppConfig, error) {
env := os.Getenv("APP_ENV")
if env == "" {
env = EnvDev
}
// 拼接配置文件路径
fileName := fmt.Sprintf("config.%s.yaml", env)
file, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %v", err)
}
// 解析YAML内容
var tempConfig struct {
Server struct {
Port string `yaml:"port"`
} `yaml:"server"`
Database struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
DBName string `yaml:"db_name"`
} `yaml:"database"`
}
if err := yaml.Unmarshal(file, &tempConfig); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %v", err)
}
return &AppConfig{
Env: env,
ServerPort: tempConfig.Server.Port,
DBHost: tempConfig.Database.Host,
DBPort: tempConfig.Database.Port,
DBUser: tempConfig.Database.User,
DBPassword: tempConfig.Database.Password,
DBName: tempConfig.Database.DBName,
}, nil
}
3. 两种方式的结合使用
实际项目中通常会结合两种方式,敏感配置比如生产环境的数据库密码通过环境变量传入,非敏感配置放在配置文件中,既保证安全性又方便维护。示例代码如下:
// 结合环境变量和配置文件的加载方式
func LoadMixedConfig() (*AppConfig, error) {
env := os.Getenv("APP_ENV")
if env == "" {
env = EnvDev
}
// 非敏感配置从文件加载
fileName := fmt.Sprintf("config.%s.yaml", env)
file, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %v", err)
}
var tempConfig struct {
Server struct {
Port string `yaml:"port"`
} `yaml:"server"`
Database struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
DBName string `yaml:"db_name"`
} `yaml:"database"`
}
if err := yaml.Unmarshal(file, &tempConfig); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %v", err)
}
cfg := &AppConfig{
Env: env,
ServerPort: tempConfig.Server.Port,
DBHost: tempConfig.Database.Host,
DBPort: tempConfig.Database.Port,
DBUser: tempConfig.Database.User,
DBName: tempConfig.Database.DBName,
}
// 生产环境的敏感密码从环境变量读取
if env == EnvProd {
cfg.DBPassword = os.Getenv("DB_PASSWORD")
} else {
// 非生产环境密码从文件读取
var tempPwdConfig struct {
Database struct {
Password string `yaml:"password"`
} `yaml:"database"`
}
if err := yaml.Unmarshal(file, &tempPwdConfig); err == nil {
cfg.DBPassword = tempPwdConfig.Database.Password
}
}
return cfg, nil
}
环境切换的使用方式
完成配置逻辑后,切换环境只需要修改APP_ENV环境变量即可,无需修改任何代码:
- 开发环境启动:
APP_ENV=dev go run main.go - 测试环境启动:
APP_ENV=test go run main.go - 生产环境启动:先设置对应的环境变量,再启动程序,比如
APP_ENV=prod DB_PASSWORD=xxx go run main.go
注意事项
- 生产环境的敏感配置不要写在配置文件中,尽量通过环境变量或者配置中心传入,避免敏感信息泄露
- 配置结构体可以增加校验逻辑,加载配置后检查必填参数是否为空,提前发现问题
- 如果项目使用Docker部署,可以在Dockerfile或者docker-compose中设置
APP_ENV环境变量,适配不同部署场景 - 新增环境时只需要新增对应的配置分支或者配置文件,不需要修改核心配置逻辑,保证扩展性