Gin 多环境配置管理
多环境配置管理是应用部署的基础,确保开发、测试、生产环境配置独立且安全。
环境区分
环境类型
Go
- development:开发环境,详细日志、热重载
- test:测试环境,测试数据库、Mock 服务
- staging:预发布环境,接近生产配置
- production:生产环境,优化配置、安全设置
环境标识
Bash
import "os"
func GetEnv() string {
env := os.Getenv("APP_ENV")
if env == "" {
return "development"
}
return env
}
func main() {
env := GetEnv()
switch env {
case "development":
gin.SetMode(gin.DebugMode)
case "test":
gin.SetMode(gin.TestMode)
case "production":
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
r.Run(getPort(env))
}
func getPort(env string) string {
switch env {
case "production":
return ":80"
case "test":
return ":3000"
default:
return ":8080"
}
}
Viper 配置管理
安装 Viper
YAML
go get github.com/spf13/viper
配置文件结构
Go
# config/development.yaml
app:
name: gin-api
port: 8080
mode: debug
database:
host: localhost
port: 3306
user: root
password: root123
name: gin_dev
redis:
host: localhost
port: 6379
password: ""
db: 0
log:
level: debug
format: text
output: stdout
---
# config/production.yaml
app:
name: gin-api
port: 80
mode: release
database:
host: db.internal.example.com
port: 3306
user: gin_user
password: ${DB_PASSWORD} # 环境变量
name: gin_prod
redis:
host: redis.internal.example.com
port: 6379
password: ${REDIS_PASSWORD}
db: 1
log:
level: info
format: json
output: file
file: /var/log/gin-api/app.log
Viper 加载配置
Go
import (
"github.com/spf13/viper"
)
type Config struct {
App AppConfig
Database DatabaseConfig
Redis RedisConfig
Log LogConfig
}
type AppConfig struct {
Name string
Port int
Mode string
}
type DatabaseConfig struct {
Host string
Port int
User string
Password string
Name string
}
type RedisConfig struct {
Host string
Port int
Password string
DB int
}
type LogConfig struct {
Level string
Format string
Output string
File string
}
func LoadConfig() (*Config, error) {
env := GetEnv()
v := viper.New()
// 设置配置文件名和路径
v.SetConfigName(env)
v.SetConfigType("yaml")
v.AddConfigPath("config")
v.AddConfigPath("./config")
v.AddConfigPath("../config")
// 支持环境变量覆盖
v.AutomaticEnv()
v.SetEnvPrefix("APP")
// 读取配置
if err := v.ReadInConfig(); err != nil {
return nil, err
}
// 绑定到结构体
var config Config
if err := v.Unmarshal(&config); err != nil {
return nil, err
}
return &config, nil
}
环境变量覆盖
Go
func SetupViper() {
v := viper.New()
// 设置默认值
v.SetDefault("app.port", 8080)
v.SetDefault("log.level", "info")
// 配置文件
v.SetConfigName("config")
v.SetConfigType("yaml")
v.AddConfigPath(".")
// 环境变量绑定
v.BindEnv("database.password", "DB_PASSWORD")
v.BindEnv("redis.password", "REDIS_PASSWORD")
v.BindEnv("app.port", "APP_PORT")
// 自动环境变量
v.AutomaticEnv()
// 替换环境变量占位符
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.ReadInConfig()
}
配置目录结构
Go
project/
├── config/
│ ├── development.yaml
│ ├── test.yaml
│ ├── staging.yaml
│ ├── production.yaml
│ └── config.local.yaml # 本地覆盖(不提交)
├── .env # 环境变量文件(不提交)
├── .env.example # 环境变量模板
└── main.go
配置验证
dockerfile
func ValidateConfig(config *Config) error {
if config.App.Port < 1 || config.App.Port > 65535 {
return errors.New("invalid port")
}
if config.Database.Host == "" {
return errors.New("database host required")
}
if config.Database.Name == "" {
return errors.New("database name required")
}
if config.App.Mode != "debug" && config.App.Mode != "release" {
return errors.New("invalid app mode")
}
return nil
}
func main() {
config, err := LoadConfig()
if err != nil {
log.Fatal("Load config failed:", err)
}
if err := ValidateConfig(config); err != nil {
log.Fatal("Config validation failed:", err)
}
}
配置热更新
YAML
func WatchConfig(config *Config, callback func(*Config)) {
v := viper.GetViper()
v.OnConfigChange(func(e fsnotify.Event) {
log.Println("Config file changed:", e.Name)
var newConfig Config
if err := v.Unmarshal(&newConfig); err != nil {
log.Println("Config parse error:", err)
return
}
callback(&newConfig)
})
v.WatchConfig()
}
func main() {
config, _ := LoadConfig()
WatchConfig(config, func(newConfig *Config) {
// 更新全局配置
*config = *newConfig
// 动态调整日志级别
updateLogLevel(config.Log.Level)
log.Println("Config updated")
})
}
Docker 环境配置
Bash
# Dockerfile
FROM golang:1.21-alpine
ENV APP_ENV=production
ENV APP_PORT=80
WORKDIR /app
COPY . .
RUN go build -o gin-api .
CMD ["./gin-api"]
Go
# docker-compose.yml
version: '3'
services:
api:
build: .
environment:
- APP_ENV=production
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_PASSWORD=${REDIS_PASSWORD}
ports:
- "80:80"
depends_on:
- db
- redis
db:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
redis:
image: redis:alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
环境变量文件
Go
# .env.example(提交到仓库)
APP_ENV=development
APP_PORT=8080
DB_PASSWORD=
REDIS_PASSWORD=
# .env(本地,不提交)
APP_ENV=development
APP_PORT=8080
DB_PASSWORD=local_password
REDIS_PASSWORD=redis_pass
text
// 加载 .env 文件
import "github.com/joho/godotenv"
func LoadEnvFile() {
env := GetEnv()
envFile := ".env." + env
// 优先加载特定环境文件
if err := godotenv.Load(envFile); err != nil {
// 回退到默认文件
godotenv.Load(".env")
}
}
配置管理器
text
type ConfigManager struct {
config *Config
v *viper.Viper
callbacks []func(*Config)
}
func NewConfigManager() *ConfigManager {
cm := &ConfigManager{
v: viper.New(),
callbacks: make([]func(*Config), 0),
}
cm.loadConfig()
cm.watchConfig()
return cm
}
func (cm *ConfigManager) Get() *Config {
return cm.config
}
func (cm *ConfigManager) OnChange(callback func(*Config)) {
cm.callbacks = append(cm.callbacks, callback)
}
func (cm *ConfigManager) loadConfig() {
env := GetEnv()
cm.v.SetConfigName(env)
cm.v.SetConfigType("yaml")
cm.v.AddConfigPath("config")
cm.v.AutomaticEnv()
cm.v.ReadInConfig()
cm.v.Unmarshal(&cm.config)
}
func (cm *ConfigManager) watchConfig() {
cm.v.WatchConfig()
cm.v.OnConfigChange(func(e fsnotify.Event) {
cm.v.Unmarshal(&cm.config)
for _, cb := range cm.callbacks {
cb(cm.config)
}
})
}
环境配置对比
| 配置项 | development | production |
|---|---|---|
| Gin Mode | debug | release |
| Log Level | debug | info/warn |
| Log Format | text | json |
| DB Host | localhost | 内网地址 |
| Password | 明文 | 环境变量 |
注意:敏感信息(密码、密钥)禁止写入配置文件,使用环境变量。
要点总结
- 环境区分:通过
APP_ENV环境变量标识 - Viper:支持 YAML、JSON、TOML 多格式配置
- 环境变量:敏感信息通过环境变量注入
- 配置验证:加载后验证必要字段
- 热更新:
v.WatchConfig()监听配置变化 - 目录结构:config/ 目录存放各环境配置
📝 发现内容有误?点击此处直接编辑