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

257 lines
7.8 KiB
Go
Raw Normal View History

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
}
// 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"
}
syncOrdersInterval := 5
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,
}, nil
}