1eab1b99c1
- Rename all QINIU_* config/code/docs to OSS_* to match actual Alibaba Cloud OSS - Refactor upload module from internal/common/qiniu to internal/common/upload - Add backend proxy upload endpoint (POST /api/admin/marketing/upload) to avoid CORS - Auto-generate compressed thumbnail (800px, JPEG 80%) on admin image upload - Add dev-login endpoint (POST /api/v1/auth/dev-login) for H5 debugging - Add imageutil package for server-side image resizing Made-with: Cursor
245 lines
7.1 KiB
Go
Executable File
245 lines
7.1 KiB
Go
Executable File
package config
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig
|
|
Database DatabaseConfig
|
|
JWT JWTConfig
|
|
ShortVideo ShortVideoConfig
|
|
AI AIConfig
|
|
Admin AdminConfig
|
|
OSS OSSConfig
|
|
WeChatOA WeChatOfficialConfig
|
|
Redis RedisConfig
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Port string
|
|
Mode string
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Default DatabaseInstanceConfig
|
|
Additional map[string]DatabaseInstanceConfig
|
|
}
|
|
|
|
type DatabaseInstanceConfig struct {
|
|
Host string
|
|
Port string
|
|
User string
|
|
Password string
|
|
DBName string
|
|
}
|
|
|
|
type JWTConfig struct {
|
|
Secret string
|
|
Expire int
|
|
}
|
|
|
|
type ShortVideoConfig struct {
|
|
APIKey string
|
|
FreeDailyQuota int
|
|
RequestTimeout time.Duration
|
|
// 代理相关配置
|
|
ProxyEnabled bool // 是否启用媒体代理
|
|
ProxyAllowedDomains []string // 允许代理的域名白名单(为空表示允许所有)
|
|
ProxyMaxSize int64 // 代理文件最大大小(字节),0 表示不限制
|
|
ProxyTimeout time.Duration // 代理请求超时时间
|
|
}
|
|
|
|
// AIConfig 用于“AI 建议/问答”等能力的通用配置(OpenAI-compatible)。
|
|
type AIConfig struct {
|
|
BaseURL string
|
|
APIKey string
|
|
Model string
|
|
RequestTimeout time.Duration
|
|
}
|
|
|
|
// AdminConfig 用于简单的后台/运维接口鉴权(如生成兑换码)。
|
|
type AdminConfig struct {
|
|
Token string
|
|
DefaultUsername string
|
|
DefaultPassword string
|
|
}
|
|
|
|
// OSSConfig 用于阿里云 OSS 直传相关配置。
|
|
// 前端通常会向后端请求上传凭证,然后直传文件到 OSS。
|
|
type OSSConfig struct {
|
|
AccessKey string
|
|
SecretKey string
|
|
Bucket string
|
|
UploadURL string
|
|
CDNDomain string
|
|
KeyPrefix string
|
|
TokenExpireSeconds int
|
|
CallbackURL string
|
|
CallbackBody string
|
|
CallbackBodyType string
|
|
}
|
|
|
|
// WeChatOfficialConfig 用于微信公众号网页授权(OAuth2)相关接口。
|
|
type WeChatOfficialConfig struct {
|
|
AppID string
|
|
AppSecret string
|
|
RequestTimeout time.Duration
|
|
}
|
|
|
|
// RedisConfig 用于可选的 Redis 缓存(例如缓存 session_key -> user,减少 DB 查询)。
|
|
type RedisConfig struct {
|
|
Addr string
|
|
Password string
|
|
DB int
|
|
KeyPrefix string
|
|
SessionTTLSeconds int
|
|
}
|
|
|
|
var AppConfig *Config
|
|
|
|
func LoadConfig() {
|
|
// 加载 .env 文件
|
|
if err := godotenv.Load(); err != nil {
|
|
log.Println("未找到 .env 文件,使用环境变量")
|
|
}
|
|
|
|
defaultDB := DatabaseInstanceConfig{
|
|
Host: getEnv("DB_HOST", "localhost"),
|
|
Port: getEnv("DB_PORT", "3306"),
|
|
User: getEnv("DB_USER", "root"),
|
|
Password: getEnv("DB_PASSWORD", ""),
|
|
DBName: getEnv("DB_NAME", "wx_service"),
|
|
}
|
|
|
|
AppConfig = &Config{
|
|
Server: ServerConfig{
|
|
Port: getEnv("SERVER_PORT", "8080"),
|
|
Mode: getEnv("GIN_MODE", "debug"),
|
|
},
|
|
Database: DatabaseConfig{
|
|
Default: defaultDB,
|
|
Additional: loadAdditionalDBConfigs(defaultDB),
|
|
},
|
|
JWT: JWTConfig{
|
|
Secret: getEnv("JWT_SECRET", "your-secret-key"),
|
|
Expire: 86400, // 24小时
|
|
},
|
|
ShortVideo: ShortVideoConfig{
|
|
APIKey: getEnv("SHORT_VIDEO_API_KEY", ""),
|
|
FreeDailyQuota: getEnvAsInt("SHORT_VIDEO_FREE_QUOTA", 20),
|
|
RequestTimeout: time.Duration(getEnvAsInt("SHORT_VIDEO_TIMEOUT_SECONDS", 5)) * time.Second,
|
|
ProxyEnabled: getEnvAsBool("SHORT_VIDEO_PROXY_ENABLED", true),
|
|
ProxyAllowedDomains: getEnvAsStringSlice("SHORT_VIDEO_PROXY_ALLOWED_DOMAINS", nil),
|
|
ProxyMaxSize: int64(getEnvAsInt("SHORT_VIDEO_PROXY_MAX_SIZE_MB", 100)) * 1024 * 1024,
|
|
ProxyTimeout: time.Duration(getEnvAsInt("SHORT_VIDEO_PROXY_TIMEOUT_SECONDS", 60)) * time.Second,
|
|
},
|
|
AI: AIConfig{
|
|
BaseURL: getEnv("AI_BASE_URL", "https://api.openai.com/v1"),
|
|
APIKey: getEnv("AI_API_KEY", ""),
|
|
Model: getEnv("AI_MODEL", "gpt-4o-mini"),
|
|
RequestTimeout: time.Duration(getEnvAsInt("AI_TIMEOUT_SECONDS", 15)) * time.Second,
|
|
},
|
|
Admin: AdminConfig{
|
|
Token: getEnv("ADMIN_API_TOKEN", ""),
|
|
DefaultUsername: getEnv("ADMIN_DEFAULT_USERNAME", "admin"),
|
|
DefaultPassword: getEnv("ADMIN_DEFAULT_PASSWORD", "admin123"),
|
|
},
|
|
OSS: OSSConfig{
|
|
AccessKey: getEnv("OSS_ACCESS_KEY", ""),
|
|
SecretKey: getEnv("OSS_SECRET_KEY", ""),
|
|
Bucket: getEnv("OSS_BUCKET", ""),
|
|
UploadURL: getEnv("OSS_UPLOAD_URL", ""),
|
|
CDNDomain: getEnv("OSS_CDN_DOMAIN", ""),
|
|
KeyPrefix: getEnv("OSS_KEY_PREFIX", "uploads/"),
|
|
TokenExpireSeconds: getEnvAsInt("OSS_TOKEN_EXPIRE_SECONDS", 300),
|
|
CallbackURL: getEnv("OSS_CALLBACK_URL", ""),
|
|
CallbackBody: getEnv("OSS_CALLBACK_BODY", "key=$(key)&hash=$(etag)&fsize=$(fsize)&mimeType=$(mimeType)"),
|
|
CallbackBodyType: getEnv("OSS_CALLBACK_BODY_TYPE", "application/x-www-form-urlencoded"),
|
|
},
|
|
WeChatOA: WeChatOfficialConfig{
|
|
AppID: getEnv("WECHAT_OA_APP_ID", ""),
|
|
AppSecret: getEnv("WECHAT_OA_APP_SECRET", ""),
|
|
RequestTimeout: time.Duration(getEnvAsInt("WECHAT_OA_TIMEOUT_SECONDS", 5)) * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Addr: getEnv("REDIS_ADDR", ""),
|
|
Password: getEnv("REDIS_PASSWORD", ""),
|
|
DB: getEnvAsInt("REDIS_DB", 0),
|
|
KeyPrefix: getEnv("REDIS_KEY_PREFIX", "wx_service:"),
|
|
SessionTTLSeconds: getEnvAsInt("REDIS_SESSION_TTL_SECONDS", 86400),
|
|
},
|
|
}
|
|
}
|
|
|
|
func loadAdditionalDBConfigs(defaultCfg DatabaseInstanceConfig) map[string]DatabaseInstanceConfig {
|
|
instances := strings.Split(getEnv("DB_INSTANCES", ""), ",")
|
|
result := make(map[string]DatabaseInstanceConfig)
|
|
for _, rawName := range instances {
|
|
name := strings.TrimSpace(rawName)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
upperName := strings.ToUpper(name)
|
|
prefix := "DB_" + upperName + "_"
|
|
result[strings.ToLower(name)] = DatabaseInstanceConfig{
|
|
Host: getEnv(prefix+"HOST", defaultCfg.Host),
|
|
Port: getEnv(prefix+"PORT", defaultCfg.Port),
|
|
User: getEnv(prefix+"USER", defaultCfg.User),
|
|
Password: getEnv(prefix+"PASSWORD", defaultCfg.Password),
|
|
DBName: getEnv(prefix+"NAME", defaultCfg.DBName),
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsInt(key string, defaultValue int) int {
|
|
if value := os.Getenv(key); value != "" {
|
|
if v, err := strconv.Atoi(value); err == nil {
|
|
return v
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsBool(key string, defaultValue bool) bool {
|
|
if value := os.Getenv(key); value != "" {
|
|
v := strings.ToLower(value)
|
|
return v == "true" || v == "1" || v == "yes"
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvAsStringSlice(key string, defaultValue []string) []string {
|
|
if value := os.Getenv(key); value != "" {
|
|
parts := strings.Split(value, ",")
|
|
result := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
trimmed := strings.TrimSpace(part)
|
|
if trimmed != "" {
|
|
result = append(result, trimmed)
|
|
}
|
|
}
|
|
if len(result) > 0 {
|
|
return result
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|