feat: 添加环境变量管理工具和部署配置改版
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
主要改动: - 新增交互式环境配置脚本 (scripts/setup-env.sh) - 新增本地启动快捷脚本 (scripts/run-local.sh) - 新增环境变量模板文件 (.env.example) - 部署模式改版:使用嵌入式配置 + 环境变量覆盖 - 添加对象存储功能支持 - 改进 IoT 卡片导入任务 - 优化 OpenAPI 文档生成 - 删除旧的配置文件,改用嵌入式默认配置
This commit is contained in:
@@ -2,92 +2,120 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Load 从文件和环境变量加载配置
|
||||
const envPrefix = "JUNHONG"
|
||||
|
||||
func Load() (*Config, error) {
|
||||
// 确定配置路径
|
||||
configPath := os.Getenv(constants.EnvConfigPath)
|
||||
if configPath == "" {
|
||||
configPath = constants.DefaultConfigPath
|
||||
}
|
||||
v := viper.New()
|
||||
|
||||
// 检查环境特定配置(dev, staging, prod)
|
||||
configEnv := os.Getenv(constants.EnvConfigEnv)
|
||||
if configEnv != "" {
|
||||
// 优先尝试环境特定配置
|
||||
envConfigPath := fmt.Sprintf("configs/config.%s.yaml", configEnv)
|
||||
if _, err := os.Stat(envConfigPath); err == nil {
|
||||
configPath = envConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 Viper
|
||||
viper.SetConfigFile(configPath)
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
// 启用环境变量覆盖
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvPrefix("APP")
|
||||
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
}
|
||||
|
||||
// 反序列化到 Config 结构体
|
||||
cfg := &Config{}
|
||||
if err := viper.Unmarshal(cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设为全局配置
|
||||
globalConfig.Store(cfg)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Reload 重新加载当前配置文件
|
||||
func Reload() (*Config, error) {
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("failed to reload config: %w", err)
|
||||
}
|
||||
|
||||
cfg := &Config{}
|
||||
if err := viper.Unmarshal(cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
// 设置前验证
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 原子交换
|
||||
globalConfig.Store(cfg)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// GetConfigPath 返回当前已加载配置文件的绝对路径
|
||||
func GetConfigPath() string {
|
||||
configFile := viper.ConfigFileUsed()
|
||||
if configFile == "" {
|
||||
return ""
|
||||
}
|
||||
absPath, err := filepath.Abs(configFile)
|
||||
embeddedReader, err := getEmbeddedConfig()
|
||||
if err != nil {
|
||||
return configFile
|
||||
return nil, fmt.Errorf("读取嵌入配置失败: %w", err)
|
||||
}
|
||||
|
||||
v.SetConfigType("yaml")
|
||||
if err := v.ReadConfig(embeddedReader); err != nil {
|
||||
return nil, fmt.Errorf("解析嵌入配置失败: %w", err)
|
||||
}
|
||||
|
||||
v.SetEnvPrefix(envPrefix)
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
bindEnvVariables(v)
|
||||
|
||||
cfg := &Config{}
|
||||
if err := v.Unmarshal(cfg); err != nil {
|
||||
return nil, fmt.Errorf("反序列化配置失败: %w", err)
|
||||
}
|
||||
|
||||
if err := cfg.ValidateRequired(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
globalConfig.Store(cfg)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func bindEnvVariables(v *viper.Viper) {
|
||||
bindings := []string{
|
||||
"server.address",
|
||||
"server.read_timeout",
|
||||
"server.write_timeout",
|
||||
"server.shutdown_timeout",
|
||||
"server.prefork",
|
||||
"database.host",
|
||||
"database.port",
|
||||
"database.user",
|
||||
"database.password",
|
||||
"database.dbname",
|
||||
"database.sslmode",
|
||||
"database.max_open_conns",
|
||||
"database.max_idle_conns",
|
||||
"database.conn_max_lifetime",
|
||||
"redis.address",
|
||||
"redis.port",
|
||||
"redis.password",
|
||||
"redis.db",
|
||||
"redis.pool_size",
|
||||
"redis.min_idle_conns",
|
||||
"redis.dial_timeout",
|
||||
"redis.read_timeout",
|
||||
"redis.write_timeout",
|
||||
"storage.provider",
|
||||
"storage.temp_dir",
|
||||
"storage.s3.endpoint",
|
||||
"storage.s3.region",
|
||||
"storage.s3.bucket",
|
||||
"storage.s3.access_key_id",
|
||||
"storage.s3.secret_access_key",
|
||||
"storage.s3.use_ssl",
|
||||
"storage.s3.path_style",
|
||||
"storage.presign.upload_expires",
|
||||
"storage.presign.download_expires",
|
||||
"logging.level",
|
||||
"logging.development",
|
||||
"logging.app_log.filename",
|
||||
"logging.app_log.max_size",
|
||||
"logging.app_log.max_backups",
|
||||
"logging.app_log.max_age",
|
||||
"logging.app_log.compress",
|
||||
"logging.access_log.filename",
|
||||
"logging.access_log.max_size",
|
||||
"logging.access_log.max_backups",
|
||||
"logging.access_log.max_age",
|
||||
"logging.access_log.compress",
|
||||
"queue.concurrency",
|
||||
"queue.retry_max",
|
||||
"queue.timeout",
|
||||
"jwt.secret_key",
|
||||
"jwt.token_duration",
|
||||
"jwt.access_token_ttl",
|
||||
"jwt.refresh_token_ttl",
|
||||
"middleware.enable_rate_limiter",
|
||||
"middleware.rate_limiter.max",
|
||||
"middleware.rate_limiter.expiration",
|
||||
"middleware.rate_limiter.storage",
|
||||
"sms.gateway_url",
|
||||
"sms.username",
|
||||
"sms.password",
|
||||
"sms.signature",
|
||||
"sms.timeout",
|
||||
"default_admin.username",
|
||||
"default_admin.password",
|
||||
"default_admin.phone",
|
||||
}
|
||||
|
||||
for _, key := range bindings {
|
||||
_ = v.BindEnv(key)
|
||||
}
|
||||
return absPath
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user