Set up project files and add .gitignore to exclude local build/runtime artifacts. Made-with: Cursor
394 lines
18 KiB
Go
394 lines
18 KiB
Go
package config
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/minio/minio-go/v7"
|
||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||
"github.com/redis/go-redis/v9"
|
||
"github.com/spf13/viper"
|
||
)
|
||
|
||
type Config struct {
|
||
Server ServerConfig `mapstructure:"server"`
|
||
Database DatabaseConfig `mapstructure:"database"`
|
||
Redis RedisConfig `mapstructure:"redis"`
|
||
Cache CacheConfig `mapstructure:"cache"`
|
||
S3 S3Config `mapstructure:"s3"`
|
||
JWT JWTConfig `mapstructure:"jwt"`
|
||
Log LogConfig `mapstructure:"log"`
|
||
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
||
Upload UploadConfig `mapstructure:"upload"`
|
||
Gorse GorseConfig `mapstructure:"gorse"`
|
||
OpenAI OpenAIConfig `mapstructure:"openai"`
|
||
Email EmailConfig `mapstructure:"email"`
|
||
}
|
||
|
||
type ServerConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
Mode string `mapstructure:"mode"`
|
||
}
|
||
|
||
type DatabaseConfig struct {
|
||
Type string `mapstructure:"type"`
|
||
SQLite SQLiteConfig `mapstructure:"sqlite"`
|
||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||
LogLevel string `mapstructure:"log_level"`
|
||
SlowThresholdMs int `mapstructure:"slow_threshold_ms"`
|
||
IgnoreRecordNotFound bool `mapstructure:"ignore_record_not_found"`
|
||
ParameterizedQueries bool `mapstructure:"parameterized_queries"`
|
||
}
|
||
|
||
type SQLiteConfig struct {
|
||
Path string `mapstructure:"path"`
|
||
}
|
||
|
||
type PostgresConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
User string `mapstructure:"user"`
|
||
Password string `mapstructure:"password"`
|
||
DBName string `mapstructure:"dbname"`
|
||
SSLMode string `mapstructure:"sslmode"`
|
||
}
|
||
|
||
func (d PostgresConfig) DSN() string {
|
||
return fmt.Sprintf(
|
||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||
d.Host, d.Port, d.User, d.Password, d.DBName, d.SSLMode,
|
||
)
|
||
}
|
||
|
||
type RedisConfig struct {
|
||
Type string `mapstructure:"type"`
|
||
Redis RedisServerConfig `mapstructure:"redis"`
|
||
Miniredis MiniredisConfig `mapstructure:"miniredis"`
|
||
PoolSize int `mapstructure:"pool_size"`
|
||
}
|
||
|
||
type CacheConfig struct {
|
||
Enabled bool `mapstructure:"enabled"`
|
||
KeyPrefix string `mapstructure:"key_prefix"`
|
||
DefaultTTL int `mapstructure:"default_ttl"`
|
||
NullTTL int `mapstructure:"null_ttl"`
|
||
JitterRatio float64 `mapstructure:"jitter_ratio"`
|
||
DisableFlushDB bool `mapstructure:"disable_flushdb"`
|
||
Modules CacheModuleTTL `mapstructure:"modules"`
|
||
}
|
||
|
||
type CacheModuleTTL struct {
|
||
PostList int `mapstructure:"post_list_ttl"`
|
||
Conversation int `mapstructure:"conversation_ttl"`
|
||
UnreadCount int `mapstructure:"unread_count_ttl"`
|
||
GroupMembers int `mapstructure:"group_members_ttl"`
|
||
}
|
||
|
||
type RedisServerConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
Password string `mapstructure:"password"`
|
||
DB int `mapstructure:"db"`
|
||
}
|
||
|
||
func (r RedisServerConfig) Addr() string {
|
||
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
||
}
|
||
|
||
type MiniredisConfig struct {
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
}
|
||
|
||
type S3Config struct {
|
||
Endpoint string `mapstructure:"endpoint"`
|
||
AccessKey string `mapstructure:"access_key"`
|
||
SecretKey string `mapstructure:"secret_key"`
|
||
Bucket string `mapstructure:"bucket"`
|
||
UseSSL bool `mapstructure:"use_ssl"`
|
||
Region string `mapstructure:"region"`
|
||
Domain string `mapstructure:"domain"` // 自定义域名,如 s3.carrot.skin
|
||
}
|
||
|
||
type JWTConfig struct {
|
||
Secret string `mapstructure:"secret"`
|
||
AccessTokenExpire time.Duration `mapstructure:"access_token_expire"`
|
||
RefreshTokenExpire time.Duration `mapstructure:"refresh_token_expire"`
|
||
}
|
||
|
||
type LogConfig struct {
|
||
Level string `mapstructure:"level"`
|
||
Encoding string `mapstructure:"encoding"`
|
||
OutputPaths []string `mapstructure:"output_paths"`
|
||
}
|
||
|
||
type RateLimitConfig struct {
|
||
Enabled bool `mapstructure:"enabled"`
|
||
RequestsPerMinute int `mapstructure:"requests_per_minute"`
|
||
}
|
||
|
||
type UploadConfig struct {
|
||
MaxFileSize int64 `mapstructure:"max_file_size"`
|
||
AllowedTypes []string `mapstructure:"allowed_types"`
|
||
}
|
||
|
||
type GorseConfig struct {
|
||
Address string `mapstructure:"address"`
|
||
APIKey string `mapstructure:"api_key"`
|
||
Enabled bool `mapstructure:"enabled"`
|
||
Dashboard string `mapstructure:"dashboard"`
|
||
ImportPassword string `mapstructure:"import_password"`
|
||
EmbeddingAPIKey string `mapstructure:"embedding_api_key"`
|
||
EmbeddingURL string `mapstructure:"embedding_url"`
|
||
EmbeddingModel string `mapstructure:"embedding_model"`
|
||
}
|
||
|
||
type OpenAIConfig struct {
|
||
Enabled bool `mapstructure:"enabled"`
|
||
BaseURL string `mapstructure:"base_url"`
|
||
APIKey string `mapstructure:"api_key"`
|
||
ModerationModel string `mapstructure:"moderation_model"`
|
||
ModerationMaxImagesPerRequest int `mapstructure:"moderation_max_images_per_request"`
|
||
RequestTimeout int `mapstructure:"request_timeout"`
|
||
StrictModeration bool `mapstructure:"strict_moderation"`
|
||
}
|
||
|
||
type EmailConfig struct {
|
||
Enabled bool `mapstructure:"enabled"`
|
||
Host string `mapstructure:"host"`
|
||
Port int `mapstructure:"port"`
|
||
Username string `mapstructure:"username"`
|
||
Password string `mapstructure:"password"`
|
||
FromAddress string `mapstructure:"from_address"`
|
||
FromName string `mapstructure:"from_name"`
|
||
UseTLS bool `mapstructure:"use_tls"`
|
||
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
||
Timeout int `mapstructure:"timeout"`
|
||
}
|
||
|
||
func Load(configPath string) (*Config, error) {
|
||
viper.SetConfigFile(configPath)
|
||
viper.SetConfigType("yaml")
|
||
|
||
// 启用环境变量支持
|
||
viper.SetEnvPrefix("APP")
|
||
viper.AutomaticEnv()
|
||
// 允许环境变量使用下划线或连字符
|
||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||
|
||
// Set default values
|
||
viper.SetDefault("server.port", 8080)
|
||
viper.SetDefault("server.mode", "debug")
|
||
viper.SetDefault("server.host", "0.0.0.0")
|
||
viper.SetDefault("database.type", "sqlite")
|
||
viper.SetDefault("database.sqlite.path", "./data/carrot_bbs.db")
|
||
viper.SetDefault("database.max_idle_conns", 10)
|
||
viper.SetDefault("database.max_open_conns", 100)
|
||
viper.SetDefault("database.log_level", "warn")
|
||
viper.SetDefault("database.slow_threshold_ms", 200)
|
||
viper.SetDefault("database.ignore_record_not_found", true)
|
||
viper.SetDefault("database.parameterized_queries", true)
|
||
viper.SetDefault("redis.type", "miniredis")
|
||
viper.SetDefault("redis.redis.host", "localhost")
|
||
viper.SetDefault("redis.redis.port", 6379)
|
||
viper.SetDefault("redis.redis.password", "")
|
||
viper.SetDefault("redis.redis.db", 0)
|
||
viper.SetDefault("redis.miniredis.host", "localhost")
|
||
viper.SetDefault("redis.miniredis.port", 6379)
|
||
viper.SetDefault("redis.pool_size", 10)
|
||
viper.SetDefault("cache.enabled", true)
|
||
viper.SetDefault("cache.key_prefix", "")
|
||
viper.SetDefault("cache.default_ttl", 30)
|
||
viper.SetDefault("cache.null_ttl", 5)
|
||
viper.SetDefault("cache.jitter_ratio", 0.1)
|
||
viper.SetDefault("cache.disable_flushdb", true)
|
||
viper.SetDefault("cache.modules.post_list_ttl", 30)
|
||
viper.SetDefault("cache.modules.conversation_ttl", 60)
|
||
viper.SetDefault("cache.modules.unread_count_ttl", 30)
|
||
viper.SetDefault("cache.modules.group_members_ttl", 120)
|
||
viper.SetDefault("jwt.secret", "your-jwt-secret-key-change-in-production")
|
||
viper.SetDefault("jwt.access_token_expire", 86400)
|
||
viper.SetDefault("jwt.refresh_token_expire", 604800)
|
||
viper.SetDefault("log.level", "info")
|
||
viper.SetDefault("log.encoding", "json")
|
||
viper.SetDefault("log.output_paths", []string{"stdout", "./logs/app.log"})
|
||
viper.SetDefault("rate_limit.enabled", true)
|
||
viper.SetDefault("rate_limit.requests_per_minute", 60)
|
||
viper.SetDefault("upload.max_file_size", 10485760)
|
||
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/gif", "image/webp"})
|
||
viper.SetDefault("s3.endpoint", "")
|
||
viper.SetDefault("s3.access_key", "")
|
||
viper.SetDefault("s3.secret_key", "")
|
||
viper.SetDefault("s3.bucket", "")
|
||
viper.SetDefault("s3.use_ssl", true)
|
||
viper.SetDefault("s3.region", "us-east-1")
|
||
viper.SetDefault("s3.domain", "")
|
||
viper.SetDefault("sensitive.enabled", true)
|
||
viper.SetDefault("sensitive.replace_str", "***")
|
||
viper.SetDefault("audit.enabled", false)
|
||
viper.SetDefault("audit.provider", "local")
|
||
viper.SetDefault("gorse.enabled", false)
|
||
viper.SetDefault("gorse.address", "http://localhost:8087")
|
||
viper.SetDefault("gorse.api_key", "")
|
||
viper.SetDefault("gorse.dashboard", "http://localhost:8088")
|
||
viper.SetDefault("gorse.import_password", "")
|
||
viper.SetDefault("gorse.embedding_api_key", "")
|
||
viper.SetDefault("gorse.embedding_url", "https://api.littlelan.cn/v1/embeddings")
|
||
viper.SetDefault("gorse.embedding_model", "BAAI/bge-m3")
|
||
viper.SetDefault("openai.enabled", true)
|
||
viper.SetDefault("openai.base_url", "https://api.littlelan.cn/")
|
||
viper.SetDefault("openai.api_key", "")
|
||
viper.SetDefault("openai.moderation_model", "qwen3.5-122b")
|
||
viper.SetDefault("openai.moderation_max_images_per_request", 1)
|
||
viper.SetDefault("openai.request_timeout", 30)
|
||
viper.SetDefault("openai.strict_moderation", false)
|
||
viper.SetDefault("email.enabled", false)
|
||
viper.SetDefault("email.host", "")
|
||
viper.SetDefault("email.port", 587)
|
||
viper.SetDefault("email.username", "")
|
||
viper.SetDefault("email.password", "")
|
||
viper.SetDefault("email.from_address", "")
|
||
viper.SetDefault("email.from_name", "Carrot BBS")
|
||
viper.SetDefault("email.use_tls", true)
|
||
viper.SetDefault("email.insecure_skip_verify", false)
|
||
viper.SetDefault("email.timeout", 15)
|
||
|
||
if err := viper.ReadInConfig(); err != nil {
|
||
return nil, fmt.Errorf("failed to read config: %w", err)
|
||
}
|
||
|
||
var cfg Config
|
||
if err := viper.Unmarshal(&cfg); err != nil {
|
||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||
}
|
||
|
||
// Convert seconds to duration
|
||
cfg.JWT.AccessTokenExpire = time.Duration(viper.GetInt("jwt.access_token_expire")) * time.Second
|
||
cfg.JWT.RefreshTokenExpire = time.Duration(viper.GetInt("jwt.refresh_token_expire")) * time.Second
|
||
|
||
// 环境变量覆盖(显式处理敏感配置)
|
||
cfg.JWT.Secret = getEnvOrDefault("APP_JWT_SECRET", cfg.JWT.Secret)
|
||
cfg.Database.SQLite.Path = getEnvOrDefault("APP_DATABASE_SQLITE_PATH", cfg.Database.SQLite.Path)
|
||
cfg.Database.Postgres.Host = getEnvOrDefault("APP_DATABASE_POSTGRES_HOST", cfg.Database.Postgres.Host)
|
||
cfg.Database.Postgres.Port, _ = strconv.Atoi(getEnvOrDefault("APP_DATABASE_POSTGRES_PORT", fmt.Sprintf("%d", cfg.Database.Postgres.Port)))
|
||
cfg.Database.Postgres.User = getEnvOrDefault("APP_DATABASE_POSTGRES_USER", cfg.Database.Postgres.User)
|
||
cfg.Database.Postgres.Password = getEnvOrDefault("APP_DATABASE_POSTGRES_PASSWORD", cfg.Database.Postgres.Password)
|
||
cfg.Database.Postgres.DBName = getEnvOrDefault("APP_DATABASE_POSTGRES_DBNAME", cfg.Database.Postgres.DBName)
|
||
cfg.Database.LogLevel = getEnvOrDefault("APP_DATABASE_LOG_LEVEL", cfg.Database.LogLevel)
|
||
cfg.Database.SlowThresholdMs, _ = strconv.Atoi(getEnvOrDefault("APP_DATABASE_SLOW_THRESHOLD_MS", fmt.Sprintf("%d", cfg.Database.SlowThresholdMs)))
|
||
cfg.Database.IgnoreRecordNotFound, _ = strconv.ParseBool(getEnvOrDefault("APP_DATABASE_IGNORE_RECORD_NOT_FOUND", fmt.Sprintf("%t", cfg.Database.IgnoreRecordNotFound)))
|
||
cfg.Database.ParameterizedQueries, _ = strconv.ParseBool(getEnvOrDefault("APP_DATABASE_PARAMETERIZED_QUERIES", fmt.Sprintf("%t", cfg.Database.ParameterizedQueries)))
|
||
cfg.Redis.Redis.Host = getEnvOrDefault("APP_REDIS_REDIS_HOST", cfg.Redis.Redis.Host)
|
||
cfg.Redis.Redis.Port, _ = strconv.Atoi(getEnvOrDefault("APP_REDIS_REDIS_PORT", fmt.Sprintf("%d", cfg.Redis.Redis.Port)))
|
||
cfg.Redis.Redis.Password = getEnvOrDefault("APP_REDIS_REDIS_PASSWORD", cfg.Redis.Redis.Password)
|
||
cfg.Redis.Redis.DB, _ = strconv.Atoi(getEnvOrDefault("APP_REDIS_REDIS_DB", fmt.Sprintf("%d", cfg.Redis.Redis.DB)))
|
||
cfg.Redis.Miniredis.Host = getEnvOrDefault("APP_REDIS_MINIREDIS_HOST", cfg.Redis.Miniredis.Host)
|
||
cfg.Redis.Miniredis.Port, _ = strconv.Atoi(getEnvOrDefault("APP_REDIS_MINIREDIS_PORT", fmt.Sprintf("%d", cfg.Redis.Miniredis.Port)))
|
||
cfg.Redis.Type = getEnvOrDefault("APP_REDIS_TYPE", cfg.Redis.Type)
|
||
cfg.Cache.KeyPrefix = getEnvOrDefault("APP_CACHE_KEY_PREFIX", cfg.Cache.KeyPrefix)
|
||
cfg.Cache.Enabled, _ = strconv.ParseBool(getEnvOrDefault("APP_CACHE_ENABLED", fmt.Sprintf("%t", cfg.Cache.Enabled)))
|
||
cfg.Cache.DisableFlushDB, _ = strconv.ParseBool(getEnvOrDefault("APP_CACHE_DISABLE_FLUSHDB", fmt.Sprintf("%t", cfg.Cache.DisableFlushDB)))
|
||
cfg.Cache.DefaultTTL, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_DEFAULT_TTL", fmt.Sprintf("%d", cfg.Cache.DefaultTTL)))
|
||
cfg.Cache.NullTTL, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_NULL_TTL", fmt.Sprintf("%d", cfg.Cache.NullTTL)))
|
||
cfg.Cache.JitterRatio, _ = strconv.ParseFloat(getEnvOrDefault("APP_CACHE_JITTER_RATIO", fmt.Sprintf("%.2f", cfg.Cache.JitterRatio)), 64)
|
||
cfg.Cache.Modules.PostList, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_MODULES_POST_LIST_TTL", fmt.Sprintf("%d", cfg.Cache.Modules.PostList)))
|
||
cfg.Cache.Modules.Conversation, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_MODULES_CONVERSATION_TTL", fmt.Sprintf("%d", cfg.Cache.Modules.Conversation)))
|
||
cfg.Cache.Modules.UnreadCount, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_MODULES_UNREAD_COUNT_TTL", fmt.Sprintf("%d", cfg.Cache.Modules.UnreadCount)))
|
||
cfg.Cache.Modules.GroupMembers, _ = strconv.Atoi(getEnvOrDefault("APP_CACHE_MODULES_GROUP_MEMBERS_TTL", fmt.Sprintf("%d", cfg.Cache.Modules.GroupMembers)))
|
||
cfg.S3.Endpoint = getEnvOrDefault("APP_S3_ENDPOINT", cfg.S3.Endpoint)
|
||
cfg.S3.AccessKey = getEnvOrDefault("APP_S3_ACCESS_KEY", cfg.S3.AccessKey)
|
||
cfg.S3.SecretKey = getEnvOrDefault("APP_S3_SECRET_KEY", cfg.S3.SecretKey)
|
||
cfg.S3.Bucket = getEnvOrDefault("APP_S3_BUCKET", cfg.S3.Bucket)
|
||
cfg.S3.Domain = getEnvOrDefault("APP_S3_DOMAIN", cfg.S3.Domain)
|
||
cfg.Server.Host = getEnvOrDefault("APP_SERVER_HOST", cfg.Server.Host)
|
||
cfg.Server.Port, _ = strconv.Atoi(getEnvOrDefault("APP_SERVER_PORT", fmt.Sprintf("%d", cfg.Server.Port)))
|
||
cfg.Server.Mode = getEnvOrDefault("APP_SERVER_MODE", cfg.Server.Mode)
|
||
cfg.Gorse.Address = getEnvOrDefault("APP_GORSE_ADDRESS", cfg.Gorse.Address)
|
||
cfg.Gorse.APIKey = getEnvOrDefault("APP_GORSE_API_KEY", cfg.Gorse.APIKey)
|
||
cfg.Gorse.Dashboard = getEnvOrDefault("APP_GORSE_DASHBOARD", cfg.Gorse.Dashboard)
|
||
cfg.Gorse.ImportPassword = getEnvOrDefault("APP_GORSE_IMPORT_PASSWORD", cfg.Gorse.ImportPassword)
|
||
cfg.Gorse.EmbeddingAPIKey = getEnvOrDefault("APP_GORSE_EMBEDDING_API_KEY", cfg.Gorse.EmbeddingAPIKey)
|
||
cfg.Gorse.EmbeddingURL = getEnvOrDefault("APP_GORSE_EMBEDDING_URL", cfg.Gorse.EmbeddingURL)
|
||
cfg.Gorse.EmbeddingModel = getEnvOrDefault("APP_GORSE_EMBEDDING_MODEL", cfg.Gorse.EmbeddingModel)
|
||
cfg.OpenAI.BaseURL = getEnvOrDefault("APP_OPENAI_BASE_URL", cfg.OpenAI.BaseURL)
|
||
cfg.OpenAI.APIKey = getEnvOrDefault("APP_OPENAI_API_KEY", cfg.OpenAI.APIKey)
|
||
cfg.OpenAI.ModerationModel = getEnvOrDefault("APP_OPENAI_MODERATION_MODEL", cfg.OpenAI.ModerationModel)
|
||
cfg.OpenAI.ModerationMaxImagesPerRequest, _ = strconv.Atoi(getEnvOrDefault("APP_OPENAI_MODERATION_MAX_IMAGES_PER_REQUEST", fmt.Sprintf("%d", cfg.OpenAI.ModerationMaxImagesPerRequest)))
|
||
cfg.OpenAI.RequestTimeout, _ = strconv.Atoi(getEnvOrDefault("APP_OPENAI_REQUEST_TIMEOUT", fmt.Sprintf("%d", cfg.OpenAI.RequestTimeout)))
|
||
cfg.OpenAI.Enabled, _ = strconv.ParseBool(getEnvOrDefault("APP_OPENAI_ENABLED", fmt.Sprintf("%t", cfg.OpenAI.Enabled)))
|
||
cfg.OpenAI.StrictModeration, _ = strconv.ParseBool(getEnvOrDefault("APP_OPENAI_STRICT_MODERATION", fmt.Sprintf("%t", cfg.OpenAI.StrictModeration)))
|
||
cfg.Email.Enabled, _ = strconv.ParseBool(getEnvOrDefault("APP_EMAIL_ENABLED", fmt.Sprintf("%t", cfg.Email.Enabled)))
|
||
cfg.Email.Host = getEnvOrDefault("APP_EMAIL_HOST", cfg.Email.Host)
|
||
cfg.Email.Port, _ = strconv.Atoi(getEnvOrDefault("APP_EMAIL_PORT", fmt.Sprintf("%d", cfg.Email.Port)))
|
||
cfg.Email.Username = getEnvOrDefault("APP_EMAIL_USERNAME", cfg.Email.Username)
|
||
cfg.Email.Password = getEnvOrDefault("APP_EMAIL_PASSWORD", cfg.Email.Password)
|
||
cfg.Email.FromAddress = getEnvOrDefault("APP_EMAIL_FROM_ADDRESS", cfg.Email.FromAddress)
|
||
cfg.Email.FromName = getEnvOrDefault("APP_EMAIL_FROM_NAME", cfg.Email.FromName)
|
||
cfg.Email.UseTLS, _ = strconv.ParseBool(getEnvOrDefault("APP_EMAIL_USE_TLS", fmt.Sprintf("%t", cfg.Email.UseTLS)))
|
||
cfg.Email.InsecureSkipVerify, _ = strconv.ParseBool(getEnvOrDefault("APP_EMAIL_INSECURE_SKIP_VERIFY", fmt.Sprintf("%t", cfg.Email.InsecureSkipVerify)))
|
||
cfg.Email.Timeout, _ = strconv.Atoi(getEnvOrDefault("APP_EMAIL_TIMEOUT", fmt.Sprintf("%d", cfg.Email.Timeout)))
|
||
|
||
return &cfg, nil
|
||
}
|
||
|
||
// getEnvOrDefault 获取环境变量值,如果未设置则返回默认值
|
||
func getEnvOrDefault(key, defaultValue string) string {
|
||
if value := os.Getenv(key); value != "" {
|
||
return value
|
||
}
|
||
return defaultValue
|
||
}
|
||
|
||
// NewRedis 创建Redis客户端(真实Redis)
|
||
func NewRedis(cfg *RedisConfig) (*redis.Client, error) {
|
||
client := redis.NewClient(&redis.Options{
|
||
Addr: cfg.Redis.Addr(),
|
||
Password: cfg.Redis.Password,
|
||
DB: cfg.Redis.DB,
|
||
PoolSize: cfg.PoolSize,
|
||
})
|
||
|
||
ctx := context.Background()
|
||
if err := client.Ping(ctx).Err(); err != nil {
|
||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||
}
|
||
|
||
return client, nil
|
||
}
|
||
|
||
// NewS3 创建S3客户端
|
||
func NewS3(cfg *S3Config) (*minio.Client, error) {
|
||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||
defer cancel()
|
||
|
||
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
||
Secure: cfg.UseSSL,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create S3 client: %w", err)
|
||
}
|
||
|
||
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to check bucket: %w", err)
|
||
}
|
||
|
||
if !exists {
|
||
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
|
||
Region: cfg.Region,
|
||
}); err != nil {
|
||
return nil, fmt.Errorf("failed to create bucket: %w", err)
|
||
}
|
||
}
|
||
|
||
return client, nil
|
||
}
|