package config import ( "fmt" "os" "path/filepath" "strings" "github.com/spf13/viper" ) // Config 应用配置 type Config struct { Service ServiceConfig `mapstructure:"service"` Redis RedisConfig `mapstructure:"redis"` MySQL MySQLConfig `mapstructure:"mysql"` MongoDB MongoDBConfig `mapstructure:"mongodb"` OSS OSSConfig `mapstructure:"oss"` Wechat WechatConfig `mapstructure:"wechat"` Scheduler SchedulerConfig `mapstructure:"scheduler"` Admin AdminConfig `mapstructure:"admin"` } // ServiceConfig 服务配置 type ServiceConfig struct { Name string `mapstructure:"name"` Version string `mapstructure:"version"` Host string `mapstructure:"host"` AdminPort int `mapstructure:"admin_port"` APIPort int `mapstructure:"api_port"` } // RedisConfig Redis配置 type RedisConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Password string `mapstructure:"password"` DB int `mapstructure:"db"` PoolSize int `mapstructure:"pool_size"` MinIdleConns int `mapstructure:"min_idle_conns"` } // OSSConfig 阿里云OSS配置 type OSSConfig struct { AccessKeyID string `mapstructure:"accessKeyId"` AccessKeySecret string `mapstructure:"accessKeySecret"` RoleARN string `mapstructure:"roleArn"` RoleSessionName string `mapstructure:"roleSessionName"` Region string `mapstructure:"region"` BucketName string `mapstructure:"bucketName"` } // WechatConfig 微信支付配置 type WechatConfig struct { AppID string `mapstructure:"app_id"` // 微信小程序或公众号 APPID MchID string `mapstructure:"mch_id"` // 商户号 NotifyURL string `mapstructure:"notify_url"` // 支付结果通知地址 APIKeyV3 string `mapstructure:"api_key_v3"` // API v3密钥 SerialNo string `mapstructure:"serial_no"` // 证书序列号 PrivateKey string `mapstructure:"private_key"` // 私钥内容(PEM格式) PrivateKeyPath string `mapstructure:"private_key_path"` // 私钥文件路径 CertPath string `mapstructure:"cert_path"` // 证书文件路径(可选) } // MySQLConfig MySQL配置 type MySQLConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` Database string `mapstructure:"database"` Charset string `mapstructure:"charset"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` ConnMaxLifetime string `mapstructure:"conn_max_lifetime"` // 如 "3600s" } // MongoDBConfig MongoDB配置 type MongoDBConfig struct { URI string `mapstructure:"uri"` // 连接字符串,如: mongodb://admin:password@localhost:27017 Database string `mapstructure:"database"` // 数据库名 Timeout string `mapstructure:"timeout"` // 连接超时,如 "10s" } // SchedulerConfig 调度器配置 type SchedulerConfig struct { TickInterval string `mapstructure:"tick_interval"` // 时间轮刻度间隔,如 "100ms" SlotNum int `mapstructure:"slot_num"` // 槽位数量 } // AdminConfig 管理员配置 type AdminConfig struct { JWTSecret string `mapstructure:"jwt_secret"` // JWT密钥 JWTExpiresIn string `mapstructure:"jwt_expires_in"` // JWT过期时间,如 "24h" } // LoadConfig 加载配置文件 // 支持从多个位置查找配置文件(优先级从高到低): // 1. 命令行参数指定的路径 // 2. /app/data/config/config.yaml(容器内挂载路径) // 3. ./data/config/config.yaml(开发环境) // 4. ./config.yaml(项目根目录,兼容旧配置) func LoadConfig(configFile string) (*Config, error) { v := viper.New() v.SetConfigType("yaml") // 如果指定了配置文件且文件存在,直接使用 if configFile != "" && configFile != "config.yaml" { if _, err := os.Stat(configFile); err == nil { v.SetConfigFile(configFile) } } // 如果还没有设置配置文件,尝试从多个位置查找 if v.ConfigFileUsed() == "" { configPaths := []string{ "/app/data/config/config.yaml", // 容器内挂载路径 "./data/config/config.yaml", // 开发环境 "./config.yaml", // 项目根目录(兼容旧配置) } for _, path := range configPaths { if _, err := os.Stat(path); err == nil { v.SetConfigFile(path) break } } } // 如果仍然没有找到配置文件,使用默认路径 if v.ConfigFileUsed() == "" { if configFile != "" { v.SetConfigFile(configFile) } else { v.SetConfigFile("config.yaml") } } // 设置默认值 setDefaults(v) if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("读取配置文件失败: %w (尝试的路径: %s)", err, v.ConfigFileUsed()) } var config Config if err := v.Unmarshal(&config); err != nil { return nil, fmt.Errorf("解析配置文件失败: %w", err) } // 处理证书路径:如果是绝对路径,直接验证;如果是相对路径,尝试从多个位置查找 if config.Wechat.PrivateKeyPath != "" { if filepath.IsAbs(config.Wechat.PrivateKeyPath) { // 绝对路径:直接验证文件是否存在 if _, err := os.Stat(config.Wechat.PrivateKeyPath); err != nil { // 如果绝对路径不存在,尝试从其他位置查找 // 提取相对路径部分(去掉 /app/data/storage/ 或 storage/ 前缀) relPath := config.Wechat.PrivateKeyPath if strings.HasPrefix(relPath, "/app/data/storage/") { relPath = strings.TrimPrefix(relPath, "/app/data/storage/") } else if strings.HasPrefix(relPath, "storage/") { relPath = strings.TrimPrefix(relPath, "storage/") } // 尝试从多个位置查找 certBasePaths := []string{ "/app/data/storage", // 容器内挂载路径 "./deploy/storage", // 部署目录 "./data/storage", // 开发环境 "./storage", // 项目根目录(兼容旧配置) } for _, basePath := range certBasePaths { fullPath := filepath.Join(basePath, relPath) if _, err := os.Stat(fullPath); err == nil { config.Wechat.PrivateKeyPath = fullPath if config.Wechat.CertPath != "" { certRelPath := config.Wechat.CertPath if strings.HasPrefix(certRelPath, "/app/data/storage/") { certRelPath = strings.TrimPrefix(certRelPath, "/app/data/storage/") } else if strings.HasPrefix(certRelPath, "storage/") { certRelPath = strings.TrimPrefix(certRelPath, "storage/") } config.Wechat.CertPath = filepath.Join(basePath, certRelPath) } break } } } } else { // 相对路径:尝试从多个位置查找 certBasePaths := []string{ "/app/data/storage", // 容器内挂载路径 "./deploy/storage", // 部署目录 "./data/storage", // 开发环境 "./storage", // 项目根目录(兼容旧配置) } // 提取相对路径部分(去掉 storage/ 前缀) relPath := config.Wechat.PrivateKeyPath if strings.HasPrefix(relPath, "storage/") { relPath = strings.TrimPrefix(relPath, "storage/") } for _, basePath := range certBasePaths { fullPath := filepath.Join(basePath, relPath) if _, err := os.Stat(fullPath); err == nil { config.Wechat.PrivateKeyPath = fullPath if config.Wechat.CertPath != "" { certRelPath := config.Wechat.CertPath if strings.HasPrefix(certRelPath, "storage/") { certRelPath = strings.TrimPrefix(certRelPath, "storage/") } config.Wechat.CertPath = filepath.Join(basePath, certRelPath) } break } } } } return &config, nil } // setDefaults 设置默认配置值 func setDefaults(v *viper.Viper) { v.SetDefault("service.name", "dd_fiber_api") v.SetDefault("service.version", "1.0.0") v.SetDefault("service.host", "0.0.0.0") v.SetDefault("service.admin_port", 8080) v.SetDefault("service.api_port", 8081) v.SetDefault("redis.host", "localhost") v.SetDefault("redis.port", 6379) v.SetDefault("redis.password", "") v.SetDefault("redis.db", 0) v.SetDefault("redis.pool_size", 10) v.SetDefault("redis.min_idle_conns", 5) v.SetDefault("mysql.host", "localhost") v.SetDefault("mysql.port", 3306) v.SetDefault("mysql.username", "root") v.SetDefault("mysql.password", "") v.SetDefault("mysql.database", "") v.SetDefault("mysql.charset", "utf8mb4") v.SetDefault("mysql.max_open_conns", 100) v.SetDefault("mysql.max_idle_conns", 10) v.SetDefault("mysql.conn_max_lifetime", "3600s") v.SetDefault("scheduler.tick_interval", "100ms") v.SetDefault("scheduler.slot_num", 3600) v.SetDefault("admin.jwt_secret", "your-secret-key-change-in-production") v.SetDefault("admin.jwt_expires_in", "24h") }