Files
soul-yongping/soul-api/internal/config/config.go

262 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 将路径拼接到 BaseURLpath 应以 / 开头
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.developmentAPP_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
}