package config import ( "os" "path/filepath" "strconv" "strings" "github.com/joho/godotenv" ) // Config 应用配置(从环境变量读取,启动时加载 .env) type Config struct { Port string Mode string DBDSN string TrustedProxies []string CORSOrigins []string Version string // APP_VERSION,打包/部署前写在 .env,/health 返回 // 统一 API 域名字段:支付回调、转账回调、apiDomain 等均由 BaseURL 拼接 BaseURL string // API_BASE_URL,如 https://soulapi.quwanzhi.com(无尾部斜杠) // 微信小程序配置 WechatAppID string WechatAppSecret string WechatMchID string WechatMchKey string WechatNotifyURL string // 由 BaseURL + /api/miniprogram/pay/notify 派生 WechatMiniProgramState string // 订阅消息跳转版本:developer/formal,从 .env WECHAT_MINI_PROGRAM_STATE 读取 // 微信转账配置(API v3) WechatAPIv3Key string WechatCertPath string WechatKeyPath string WechatSerialNo string WechatTransferURL string // 由 BaseURL + /api/payment/wechat/transfer/notify 派生 // 管理端登录(与 next-project 一致:ADMIN_USERNAME / ADMIN_PASSWORD / ADMIN_SESSION_SECRET) AdminUsername string AdminPassword string AdminSessionSecret string // 订单对账定时任务间隔(分钟),0 表示不启动内置定时任务 SyncOrdersIntervalMinutes int // 存客宝-链接卡若:可选,用于「添加好友」的 apiKey(与 GET scenarios 示例一致时可填 CKB_LEAD_API_KEY) CkbLeadAPIKey string } // BaseURLJoin 将路径拼接到 BaseURL,path 应以 / 开头 func (c *Config) BaseURLJoin(path string) string { base := strings.TrimSuffix(c.BaseURL, "/") if base == "" { return "" } p := strings.TrimSpace(path) if p != "" && p[0] != '/' { p = "/" + p } return base + p } // 默认 CORS 允许的源(零配置:不设环境变量也能用) var defaultCORSOrigins = []string{ "http://localhost:5174", "http://127.0.0.1:5174", "https://soul.quwanzhi.com", "http://soul.quwanzhi.com", "https://souladmin.quwanzhi.com", "http://souladmin.quwanzhi.com", } // current 由 main 在 Load 后设置,供 handler/middleware 读取 var current *Config // SetCurrent 设置全局配置(main 启动时调用一次) func SetCurrent(cfg *Config) { current = cfg } // Get 返回当前配置,未设置时返回 nil func Get() *Config { return current } // parseCORSOrigins 从环境变量 CORS_ORIGINS 读取(逗号分隔),未设置则用默认值 func parseCORSOrigins() []string { s := os.Getenv("CORS_ORIGINS") if s == "" { return defaultCORSOrigins } parts := strings.Split(s, ",") origins := make([]string, 0, len(parts)) for _, p := range parts { if o := strings.TrimSpace(p); o != "" { origins = append(origins, o) } } if len(origins) == 0 { return defaultCORSOrigins } return origins } // Load 加载配置,端口等从 .env 读取。 // 环境区分:APP_ENV=development 加载 .env.development,APP_ENV=production 加载 .env.production; // air 运行时通过 env_files 或 full_bin 设置 APP_ENV,开发用 .env.development,部署用 .env.production。 func Load() (*Config, error) { workDir, _ := os.Getwd() execDir := "" if execPath, err := os.Executable(); err == nil { execDir = filepath.Dir(execPath) } loadEnv := func(name string) { for _, dir := range []string{execDir, workDir, "."} { if dir == "" { continue } p := filepath.Join(dir, name) if _, err := os.Stat(p); err == nil { _ = godotenv.Load(p) break } } } overloadEnv := func(name string) { for _, dir := range []string{execDir, workDir, "."} { if dir == "" { continue } p := filepath.Join(dir, name) if _, err := os.Stat(p); err == nil { _ = godotenv.Overload(p) break } } } // 1. 加载 .env 作为基础 loadEnv(".env") // 2. 按 APP_ENV 覆盖(优先读已设置的 APP_ENV,如 air 的 env_files 已注入) appEnv := strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV"))) if appEnv == "development" { overloadEnv(".env.development") } else if appEnv == "production" { overloadEnv(".env.production") } port := os.Getenv("PORT") if port == "" { port = "8080" } mode := os.Getenv("GIN_MODE") if mode == "" { mode = "debug" } dsn := os.Getenv("DB_DSN") if dsn == "" { dsn = "user:pass@tcp(127.0.0.1:3306)/soul?charset=utf8mb4&parseTime=True" } version := os.Getenv("APP_VERSION") if version == "" { version = "0.0.0" } // 微信配置 wechatAppID := os.Getenv("WECHAT_APPID") if wechatAppID == "" { wechatAppID = "wxb8bbb2b10dec74aa" // 默认小程序AppID } wechatAppSecret := os.Getenv("WECHAT_APPSECRET") if wechatAppSecret == "" { wechatAppSecret = "3c1fb1f63e6e052222bbcead9d07fe0c" // 默认小程序AppSecret } wechatMchID := os.Getenv("WECHAT_MCH_ID") if wechatMchID == "" { wechatMchID = "1318592501" // 默认商户号 } wechatMchKey := os.Getenv("WECHAT_MCH_KEY") if wechatMchKey == "" { wechatMchKey = "wx3e31b068be59ddc131b068be59ddc2" // 默认API密钥(v2) } // 统一域名:API_BASE_URL 派生支付/转账回调,可选 WECHAT_NOTIFY_URL 覆盖 baseURL := strings.TrimSpace(strings.TrimSuffix(os.Getenv("API_BASE_URL"), "/")) if baseURL == "" { baseURL = "https://soulapi.quwanzhi.com" } wechatNotifyURL := os.Getenv("WECHAT_NOTIFY_URL") if wechatNotifyURL == "" { wechatNotifyURL = baseURL + "/api/miniprogram/pay/notify" } wechatMiniProgramState := strings.TrimSpace(os.Getenv("WECHAT_MINI_PROGRAM_STATE")) if wechatMiniProgramState != "developer" && wechatMiniProgramState != "trial" { wechatMiniProgramState = "formal" // 默认正式版,避免生成开发版码导致「开发版已过期」 } // 转账配置 wechatAPIv3Key := os.Getenv("WECHAT_APIV3_KEY") if wechatAPIv3Key == "" { wechatAPIv3Key = "wx3e31b068be59ddc131b068be59ddc2" // 默认 API v3 密钥 } wechatCertPath := os.Getenv("WECHAT_CERT_PATH") if wechatCertPath == "" { wechatCertPath = "certs/apiclient_cert.pem" // 默认证书路径 } wechatKeyPath := os.Getenv("WECHAT_KEY_PATH") if wechatKeyPath == "" { wechatKeyPath = "certs/apiclient_key.pem" // 默认私钥路径 } wechatSerialNo := os.Getenv("WECHAT_SERIAL_NO") if wechatSerialNo == "" { wechatSerialNo = "4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5" // 默认证书序列号 } wechatTransferURL := os.Getenv("WECHAT_TRANSFER_URL") if wechatTransferURL == "" { wechatTransferURL = baseURL + "/api/payment/wechat/transfer/notify" } adminUsername := os.Getenv("ADMIN_USERNAME") if adminUsername == "" { adminUsername = "admin" } adminPassword := os.Getenv("ADMIN_PASSWORD") if adminPassword == "" { adminPassword = "admin123" } adminSessionSecret := os.Getenv("ADMIN_SESSION_SECRET") if adminSessionSecret == "" { adminSessionSecret = "soul-admin-secret-change-in-prod" } // 默认 0:不启动全量定时对账,以「支付发起后单笔订单轮询」为主;需兜底时可设 SYNC_ORDERS_INTERVAL_MINUTES=60 等 syncOrdersInterval := 0 if s := os.Getenv("SYNC_ORDERS_INTERVAL_MINUTES"); s != "" { if n, e := strconv.Atoi(s); e == nil && n >= 0 { syncOrdersInterval = n } } return &Config{ Port: port, Mode: mode, DBDSN: dsn, TrustedProxies: []string{"127.0.0.1", "::1"}, CORSOrigins: parseCORSOrigins(), Version: version, BaseURL: baseURL, WechatAppID: wechatAppID, WechatAppSecret: wechatAppSecret, WechatMchID: wechatMchID, WechatMchKey: wechatMchKey, WechatNotifyURL: wechatNotifyURL, WechatMiniProgramState: wechatMiniProgramState, WechatAPIv3Key: wechatAPIv3Key, WechatCertPath: wechatCertPath, WechatKeyPath: wechatKeyPath, WechatSerialNo: wechatSerialNo, WechatTransferURL: wechatTransferURL, AdminUsername: adminUsername, AdminPassword: adminPassword, AdminSessionSecret: adminSessionSecret, SyncOrdersIntervalMinutes: syncOrdersInterval, CkbLeadAPIKey: strings.TrimSpace(os.Getenv("CKB_LEAD_API_KEY")), }, nil }