287 lines
8.1 KiB
Go
287 lines
8.1 KiB
Go
package config
|
||
|
||
import (
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/joho/godotenv"
|
||
)
|
||
|
||
// Config 全部由环境变量填充(可通过工作目录下 .env.development 或 ENV_FILE 设置,见 Load)。
|
||
type Config struct {
|
||
AppEnv string
|
||
AdminToken string
|
||
AuthAPIURL string
|
||
DatabaseDSN string
|
||
|
||
// 进程与对外访问(管理后台「系统状态」展示)
|
||
HTTPListenAddr string
|
||
PublicAPIBaseURL string
|
||
|
||
// Redis(可选,用于缓存时配置;未启用则不连接)
|
||
RedisEnabled bool
|
||
RedisAddr string
|
||
RedisPassword string
|
||
RedisDB int
|
||
RedisEnv string
|
||
|
||
RabbitMQEnabled bool
|
||
RabbitMQURL string
|
||
RabbitMQEnv string
|
||
|
||
WebhookMengyaSecret string
|
||
PaymentPendingTTLSecs int
|
||
|
||
// GinDebug:为 true 时启用 Gin Debug(打印路由表等);默认 false,日志更干净。
|
||
GinDebug bool
|
||
|
||
// EnableSwagger:为 true 时注册 /swagger UI;默认在 GIN_DEBUG 或 ENABLE_SWAGGER 为真时为 true。
|
||
EnableSwagger bool
|
||
}
|
||
|
||
// 应用环境:在未设置 DATABASE_DSN / RABBITMQ_ENV 时决定默认值。
|
||
// APP_ENV=production → 生产库 DSN、RABBITMQ_ENV 默认 prod。
|
||
// 否则 → 开发默认(测试库、RabbitMQ 开发环境)。
|
||
const (
|
||
EnvDevelopment = "development"
|
||
EnvProduction = "production"
|
||
)
|
||
|
||
// 内建 DSN:当 DATABASE_DSN 为空时使用。
|
||
const (
|
||
TestDSN = "mengyastore-test:mengyastore-test@tcp(10.1.1.100:3306)/mengyastore-test?charset=utf8mb4&parseTime=True&loc=Local"
|
||
ProdDSN = "mengyastore:mengyastore@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local"
|
||
|
||
DefaultRabbitMQHost = "10.1.1.233"
|
||
DefaultRabbitMQPort = "5672"
|
||
DefaultRabbitVHost = "mengyastore-dev"
|
||
DefaultRabbitMQUser = "admin"
|
||
ProdRabbitMQHost = "192.168.1.100"
|
||
DefaultRabbitVHostProd = "mengyastore-prod"
|
||
|
||
// Redis:与 MySQL 同网段;未设置 REDIS_ADDR / REDIS_PASSWORD 时按 APP_ENV 填入
|
||
TestRedisAddr = "10.1.1.100:6379"
|
||
ProdRedisAddr = "192.168.1.100:6379"
|
||
// 内网默认口令(可用环境变量 REDIS_PASSWORD 覆盖)
|
||
DefaultRedisPassword = "tyh@19900420"
|
||
)
|
||
|
||
// Load 读取可选的 env 文件,再从进程环境组装 Config。
|
||
//
|
||
// 解析 env 文件的顺序:
|
||
// 1. 环境变量 ENV_FILE 指定的文件(若设置了)
|
||
// 2. 当前工作目录下的 .env.development(不存在则忽略)
|
||
//
|
||
// 环境变量说明(除非注明否则均可选):
|
||
//
|
||
// APP_ENV — development | production(默认 development)
|
||
// ADMIN_TOKEN — 管理端 API 令牌(默认 changeme)
|
||
// AUTH_API_URL — 萌芽认证中心 SproutGate 根 URL
|
||
// DATABASE_DSN — MySQL DSN;为空则按 APP_ENV 选用 TestDSN / ProdDSN
|
||
// RABBITMQ_ENABLED — true/false
|
||
// RABBITMQ_URL — 完整 amqp URL
|
||
// RABBITMQ_ENV — dev | prod(默认随 APP_ENV)
|
||
// RABBITMQ_PASSWORD — 若已启用 MQ 但未设 RABBITMQ_URL,则用默认主机/vhost 拼 URL 时需要口令
|
||
//
|
||
// HTTP_LISTEN_ADDR — 进程监听,默认 :8080
|
||
// PUBLIC_API_BASE_URL — 对外 API 基地址(反向代理场景)
|
||
//
|
||
// WEBHOOK_MENGYA_SECRET — 若设置,到账 Webhook 须带相同请求头 X-Webhook-Secret
|
||
// PAYMENT_PENDING_SECONDS — 萌芽支付待支付超时(秒,默认 60)
|
||
//
|
||
// GIN_DEBUG — 1/true/yes/on:Gin Debug;默认关闭;为真时同时开启 Swagger UI(/swagger)
|
||
// ENABLE_SWAGGER — 1/true/yes/on:启用 Swagger UI;与 GIN_DEBUG 任一为真即注册 /swagger
|
||
// WEBHOOK_LOG_VERBOSE — 1/true:萌芽 Webhook 日志附带 notice/body 摘要(排查渠道用)
|
||
//
|
||
// REDIS_ENABLED — 默认 true;显式 false/0/off 则关闭
|
||
// REDIS_ADDR — 默认 development→TestRedisAddr,production→ProdRedisAddr
|
||
// REDIS_PASSWORD — 默认 DefaultRedisPassword(建议生产用环境变量覆盖)
|
||
// REDIS_DB — 逻辑库编号,默认 1
|
||
// REDIS_ENV — dev|prod(展示用 Key 前缀环境)
|
||
func Load() (*Config, error) {
|
||
loadDotenv()
|
||
|
||
appEnv := normalizeAppEnv(os.Getenv("APP_ENV"))
|
||
|
||
cfg := &Config{
|
||
AppEnv: appEnv,
|
||
AdminToken: strings.TrimSpace(os.Getenv("ADMIN_TOKEN")),
|
||
AuthAPIURL: strings.TrimSpace(os.Getenv("AUTH_API_URL")),
|
||
DatabaseDSN: strings.TrimSpace(os.Getenv("DATABASE_DSN")),
|
||
RabbitMQURL: strings.TrimSpace(os.Getenv("RABBITMQ_URL")),
|
||
}
|
||
|
||
if cfg.AdminToken == "" {
|
||
cfg.AdminToken = "changeme"
|
||
}
|
||
|
||
if cfg.DatabaseDSN == "" {
|
||
if appEnv == EnvProduction {
|
||
cfg.DatabaseDSN = ProdDSN
|
||
} else {
|
||
cfg.DatabaseDSN = TestDSN
|
||
}
|
||
}
|
||
|
||
if v := os.Getenv("RABBITMQ_ENABLED"); v != "" {
|
||
cfg.RabbitMQEnabled = v == "true" || v == "1"
|
||
}
|
||
|
||
cfg.RabbitMQEnv = strings.TrimSpace(os.Getenv("RABBITMQ_ENV"))
|
||
if cfg.RabbitMQEnv == "" {
|
||
if appEnv == EnvProduction {
|
||
cfg.RabbitMQEnv = "prod"
|
||
} else {
|
||
cfg.RabbitMQEnv = "dev"
|
||
}
|
||
}
|
||
|
||
if cfg.RabbitMQURL == "" && cfg.RabbitMQEnabled {
|
||
cfg.RabbitMQURL = composeRabbitMQURL(cfg)
|
||
}
|
||
|
||
if v := os.Getenv("HTTP_LISTEN_ADDR"); v != "" {
|
||
cfg.HTTPListenAddr = strings.TrimSpace(v)
|
||
}
|
||
if cfg.HTTPListenAddr == "" {
|
||
cfg.HTTPListenAddr = ":8080"
|
||
}
|
||
cfg.PublicAPIBaseURL = strings.TrimSpace(os.Getenv("PUBLIC_API_BASE_URL"))
|
||
|
||
cfg.RedisEnabled = redisEnabledFromEnv(os.Getenv("REDIS_ENABLED"))
|
||
cfg.RedisAddr = strings.TrimSpace(os.Getenv("REDIS_ADDR"))
|
||
cfg.RedisPassword = os.Getenv("REDIS_PASSWORD")
|
||
if v := os.Getenv("REDIS_DB"); v != "" {
|
||
if n, err := strconv.Atoi(v); err == nil {
|
||
cfg.RedisDB = n
|
||
}
|
||
}
|
||
cfg.RedisEnv = strings.TrimSpace(os.Getenv("REDIS_ENV"))
|
||
if cfg.RedisEnv == "" {
|
||
if appEnv == EnvProduction {
|
||
cfg.RedisEnv = "prod"
|
||
} else {
|
||
cfg.RedisEnv = "dev"
|
||
}
|
||
}
|
||
|
||
if cfg.RedisEnabled {
|
||
if cfg.RedisAddr == "" {
|
||
if appEnv == EnvProduction {
|
||
cfg.RedisAddr = ProdRedisAddr
|
||
} else {
|
||
cfg.RedisAddr = TestRedisAddr
|
||
}
|
||
}
|
||
if cfg.RedisPassword == "" {
|
||
cfg.RedisPassword = DefaultRedisPassword
|
||
}
|
||
if cfg.RedisDB == 0 {
|
||
cfg.RedisDB = 1
|
||
}
|
||
}
|
||
|
||
cfg.WebhookMengyaSecret = strings.TrimSpace(os.Getenv("WEBHOOK_MENGYA_SECRET"))
|
||
if sec := strings.TrimSpace(os.Getenv("PAYMENT_PENDING_SECONDS")); sec != "" {
|
||
if n, err := strconv.Atoi(sec); err == nil && n > 0 {
|
||
cfg.PaymentPendingTTLSecs = n
|
||
}
|
||
}
|
||
if cfg.PaymentPendingTTLSecs <= 0 {
|
||
cfg.PaymentPendingTTLSecs = 60
|
||
}
|
||
|
||
cfg.GinDebug = envTruthy(os.Getenv("GIN_DEBUG"))
|
||
|
||
cfg.EnableSwagger = cfg.GinDebug || envTruthy(os.Getenv("ENABLE_SWAGGER"))
|
||
|
||
return cfg, nil
|
||
}
|
||
|
||
// envTruthy:将 1、true、yes、on 视为真(不区分大小写)。
|
||
func envTruthy(s string) bool {
|
||
switch strings.ToLower(strings.TrimSpace(s)) {
|
||
case "1", "true", "yes", "on":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// redisEnabledFromEnv:除非显式关闭,否则默认启用 Redis。
|
||
func redisEnabledFromEnv(v string) bool {
|
||
s := strings.ToLower(strings.TrimSpace(v))
|
||
if s == "false" || s == "0" || s == "no" || s == "off" {
|
||
return false
|
||
}
|
||
if s == "true" || s == "1" || s == "yes" || s == "on" {
|
||
return true
|
||
}
|
||
return true
|
||
}
|
||
|
||
func loadDotenv() {
|
||
if f := strings.TrimSpace(os.Getenv("ENV_FILE")); f != "" {
|
||
_ = godotenv.Load(f)
|
||
return
|
||
}
|
||
// 本地开发默认文件名;不存在则忽略。
|
||
_ = godotenv.Load(filepath.Clean(".env.development"))
|
||
}
|
||
|
||
func normalizeAppEnv(s string) string {
|
||
switch strings.ToLower(strings.TrimSpace(s)) {
|
||
case "production", "prod":
|
||
return EnvProduction
|
||
default:
|
||
return EnvDevelopment
|
||
}
|
||
}
|
||
|
||
func composeRabbitMQURL(cfg *Config) string {
|
||
pass := os.Getenv("RABBITMQ_PASSWORD")
|
||
if pass == "" {
|
||
return ""
|
||
}
|
||
host := os.Getenv("RABBITMQ_HOST")
|
||
port := os.Getenv("RABBITMQ_PORT")
|
||
user := os.Getenv("RABBITMQ_USER")
|
||
vhost := os.Getenv("RABBITMQ_VHOST")
|
||
if host == "" {
|
||
if cfg.RabbitMQEnv == "prod" {
|
||
host = ProdRabbitMQHost
|
||
} else {
|
||
host = DefaultRabbitMQHost
|
||
}
|
||
}
|
||
if port == "" {
|
||
port = DefaultRabbitMQPort
|
||
}
|
||
if user == "" {
|
||
user = DefaultRabbitMQUser
|
||
}
|
||
if vhost == "" {
|
||
if cfg.RabbitMQEnv == "prod" {
|
||
vhost = DefaultRabbitVHostProd
|
||
} else {
|
||
vhost = DefaultRabbitVHost
|
||
}
|
||
}
|
||
var path string
|
||
if vhost == "/" {
|
||
path = "/%2F"
|
||
} else {
|
||
path = "/" + url.PathEscape(vhost)
|
||
}
|
||
u := url.URL{
|
||
Scheme: "amqp",
|
||
User: url.UserPassword(user, pass),
|
||
Host: host + ":" + port,
|
||
Path: path,
|
||
}
|
||
return u.String()
|
||
}
|