Files
mengyastore/mengyastore-backend-go/internal/config/config.go
2026-05-13 12:11:46 +08:00

287 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 (
"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/onGin 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→TestRedisAddrproduction→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()
}