chore: 初始化仓库,排除二进制文件和覆盖率文件
This commit is contained in:
304
pkg/config/config.go
Normal file
304
pkg/config/config.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config 应用配置结构体
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
RustFS RustFSConfig `mapstructure:"rustfs"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
Casbin CasbinConfig `mapstructure:"casbin"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
Upload UploadConfig `mapstructure:"upload"`
|
||||
Email EmailConfig `mapstructure:"email"`
|
||||
}
|
||||
|
||||
// ServerConfig 服务器配置
|
||||
type ServerConfig struct {
|
||||
Port string `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
ReadTimeout time.Duration `mapstructure:"read_timeout"`
|
||||
WriteTimeout time.Duration `mapstructure:"write_timeout"`
|
||||
}
|
||||
|
||||
// DatabaseConfig 数据库配置
|
||||
type DatabaseConfig struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database string `mapstructure:"database"`
|
||||
SSLMode string `mapstructure:"ssl_mode"`
|
||||
Timezone string `mapstructure:"timezone"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
}
|
||||
|
||||
// RedisConfig Redis配置
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database int `mapstructure:"database"`
|
||||
PoolSize int `mapstructure:"pool_size"`
|
||||
}
|
||||
|
||||
// RustFSConfig RustFS对象存储配置 (S3兼容)
|
||||
type RustFSConfig struct {
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
UseSSL bool `mapstructure:"use_ssl"`
|
||||
Buckets map[string]string `mapstructure:"buckets"`
|
||||
}
|
||||
|
||||
// JWTConfig JWT配置
|
||||
type JWTConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
ExpireHours int `mapstructure:"expire_hours"`
|
||||
}
|
||||
|
||||
// CasbinConfig Casbin权限配置
|
||||
type CasbinConfig struct {
|
||||
ModelPath string `mapstructure:"model_path"`
|
||||
PolicyAdapter string `mapstructure:"policy_adapter"`
|
||||
}
|
||||
|
||||
// LogConfig 日志配置
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
Output string `mapstructure:"output"`
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// UploadConfig 文件上传配置
|
||||
type UploadConfig struct {
|
||||
MaxSize int64 `mapstructure:"max_size"`
|
||||
AllowedTypes []string `mapstructure:"allowed_types"`
|
||||
TextureMaxSize int64 `mapstructure:"texture_max_size"`
|
||||
AvatarMaxSize int64 `mapstructure:"avatar_max_size"`
|
||||
}
|
||||
|
||||
// EmailConfig 邮件配置
|
||||
type EmailConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
SMTPHost string `mapstructure:"smtp_host"`
|
||||
SMTPPort int `mapstructure:"smtp_port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
FromName string `mapstructure:"from_name"`
|
||||
}
|
||||
|
||||
// Load 加载配置 - 完全从环境变量加载,不依赖YAML文件
|
||||
func Load() (*Config, error) {
|
||||
// 加载.env文件(如果存在)
|
||||
_ = godotenv.Load(".env")
|
||||
|
||||
// 设置默认值
|
||||
setDefaults()
|
||||
|
||||
// 设置环境变量前缀
|
||||
viper.SetEnvPrefix("CARROTSKIN")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// 手动设置环境变量映射
|
||||
setupEnvMappings()
|
||||
|
||||
// 直接从环境变量解析配置
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||
}
|
||||
|
||||
// 从环境变量中覆盖配置
|
||||
overrideFromEnv(&config)
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// setDefaults 设置默认配置值
|
||||
func setDefaults() {
|
||||
// 服务器默认配置
|
||||
viper.SetDefault("server.port", ":8080")
|
||||
viper.SetDefault("server.mode", "debug")
|
||||
viper.SetDefault("server.read_timeout", "30s")
|
||||
viper.SetDefault("server.write_timeout", "30s")
|
||||
|
||||
// 数据库默认配置
|
||||
viper.SetDefault("database.driver", "postgres")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
viper.SetDefault("database.port", 5432)
|
||||
viper.SetDefault("database.ssl_mode", "disable")
|
||||
viper.SetDefault("database.timezone", "Asia/Shanghai")
|
||||
viper.SetDefault("database.max_idle_conns", 10)
|
||||
viper.SetDefault("database.max_open_conns", 100)
|
||||
viper.SetDefault("database.conn_max_lifetime", "1h")
|
||||
|
||||
// Redis默认配置
|
||||
viper.SetDefault("redis.host", "localhost")
|
||||
viper.SetDefault("redis.port", 6379)
|
||||
viper.SetDefault("redis.database", 0)
|
||||
viper.SetDefault("redis.pool_size", 10)
|
||||
|
||||
// RustFS默认配置
|
||||
viper.SetDefault("rustfs.endpoint", "127.0.0.1:9000")
|
||||
viper.SetDefault("rustfs.use_ssl", false)
|
||||
|
||||
// JWT默认配置
|
||||
viper.SetDefault("jwt.expire_hours", 168)
|
||||
|
||||
// Casbin默认配置
|
||||
viper.SetDefault("casbin.model_path", "configs/casbin/rbac_model.conf")
|
||||
viper.SetDefault("casbin.policy_adapter", "gorm")
|
||||
|
||||
// 日志默认配置
|
||||
viper.SetDefault("log.level", "info")
|
||||
viper.SetDefault("log.format", "json")
|
||||
viper.SetDefault("log.output", "logs/app.log")
|
||||
viper.SetDefault("log.max_size", 100)
|
||||
viper.SetDefault("log.max_backups", 3)
|
||||
viper.SetDefault("log.max_age", 28)
|
||||
viper.SetDefault("log.compress", true)
|
||||
|
||||
// 文件上传默认配置
|
||||
viper.SetDefault("upload.max_size", 10485760)
|
||||
viper.SetDefault("upload.texture_max_size", 2097152)
|
||||
viper.SetDefault("upload.avatar_max_size", 1048576)
|
||||
viper.SetDefault("upload.allowed_types", []string{"image/png", "image/jpeg"})
|
||||
|
||||
// 邮件默认配置
|
||||
viper.SetDefault("email.enabled", false)
|
||||
viper.SetDefault("email.smtp_port", 587)
|
||||
}
|
||||
|
||||
// setupEnvMappings 设置环境变量映射
|
||||
func setupEnvMappings() {
|
||||
// 服务器配置
|
||||
viper.BindEnv("server.port", "SERVER_PORT")
|
||||
viper.BindEnv("server.mode", "SERVER_MODE")
|
||||
viper.BindEnv("server.read_timeout", "SERVER_READ_TIMEOUT")
|
||||
viper.BindEnv("server.write_timeout", "SERVER_WRITE_TIMEOUT")
|
||||
|
||||
// 数据库配置
|
||||
viper.BindEnv("database.driver", "DATABASE_DRIVER")
|
||||
viper.BindEnv("database.host", "DATABASE_HOST")
|
||||
viper.BindEnv("database.port", "DATABASE_PORT")
|
||||
viper.BindEnv("database.username", "DATABASE_USERNAME")
|
||||
viper.BindEnv("database.password", "DATABASE_PASSWORD")
|
||||
viper.BindEnv("database.database", "DATABASE_NAME")
|
||||
viper.BindEnv("database.ssl_mode", "DATABASE_SSL_MODE")
|
||||
viper.BindEnv("database.timezone", "DATABASE_TIMEZONE")
|
||||
|
||||
// Redis配置
|
||||
viper.BindEnv("redis.host", "REDIS_HOST")
|
||||
viper.BindEnv("redis.port", "REDIS_PORT")
|
||||
viper.BindEnv("redis.password", "REDIS_PASSWORD")
|
||||
viper.BindEnv("redis.database", "REDIS_DATABASE")
|
||||
|
||||
// RustFS配置
|
||||
viper.BindEnv("rustfs.endpoint", "RUSTFS_ENDPOINT")
|
||||
viper.BindEnv("rustfs.access_key", "RUSTFS_ACCESS_KEY")
|
||||
viper.BindEnv("rustfs.secret_key", "RUSTFS_SECRET_KEY")
|
||||
viper.BindEnv("rustfs.use_ssl", "RUSTFS_USE_SSL")
|
||||
|
||||
// JWT配置
|
||||
viper.BindEnv("jwt.secret", "JWT_SECRET")
|
||||
viper.BindEnv("jwt.expire_hours", "JWT_EXPIRE_HOURS")
|
||||
|
||||
// 日志配置
|
||||
viper.BindEnv("log.level", "LOG_LEVEL")
|
||||
viper.BindEnv("log.format", "LOG_FORMAT")
|
||||
viper.BindEnv("log.output", "LOG_OUTPUT")
|
||||
|
||||
// 邮件配置
|
||||
viper.BindEnv("email.enabled", "EMAIL_ENABLED")
|
||||
viper.BindEnv("email.smtp_host", "EMAIL_SMTP_HOST")
|
||||
viper.BindEnv("email.smtp_port", "EMAIL_SMTP_PORT")
|
||||
viper.BindEnv("email.username", "EMAIL_USERNAME")
|
||||
viper.BindEnv("email.password", "EMAIL_PASSWORD")
|
||||
viper.BindEnv("email.from_name", "EMAIL_FROM_NAME")
|
||||
}
|
||||
|
||||
// overrideFromEnv 从环境变量中覆盖配置
|
||||
func overrideFromEnv(config *Config) {
|
||||
// 处理RustFS存储桶配置
|
||||
if texturesBucket := os.Getenv("RUSTFS_BUCKET_TEXTURES"); texturesBucket != "" {
|
||||
if config.RustFS.Buckets == nil {
|
||||
config.RustFS.Buckets = make(map[string]string)
|
||||
}
|
||||
config.RustFS.Buckets["textures"] = texturesBucket
|
||||
}
|
||||
|
||||
if avatarsBucket := os.Getenv("RUSTFS_BUCKET_AVATARS"); avatarsBucket != "" {
|
||||
if config.RustFS.Buckets == nil {
|
||||
config.RustFS.Buckets = make(map[string]string)
|
||||
}
|
||||
config.RustFS.Buckets["avatars"] = avatarsBucket
|
||||
}
|
||||
|
||||
// 处理数据库连接池配置
|
||||
if maxIdleConns := os.Getenv("DATABASE_MAX_IDLE_CONNS"); maxIdleConns != "" {
|
||||
if val, err := strconv.Atoi(maxIdleConns); err == nil {
|
||||
config.Database.MaxIdleConns = val
|
||||
}
|
||||
}
|
||||
|
||||
if maxOpenConns := os.Getenv("DATABASE_MAX_OPEN_CONNS"); maxOpenConns != "" {
|
||||
if val, err := strconv.Atoi(maxOpenConns); err == nil {
|
||||
config.Database.MaxOpenConns = val
|
||||
}
|
||||
}
|
||||
|
||||
if connMaxLifetime := os.Getenv("DATABASE_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
|
||||
if val, err := time.ParseDuration(connMaxLifetime); err == nil {
|
||||
config.Database.ConnMaxLifetime = val
|
||||
}
|
||||
}
|
||||
|
||||
// 处理Redis池大小
|
||||
if poolSize := os.Getenv("REDIS_POOL_SIZE"); poolSize != "" {
|
||||
if val, err := strconv.Atoi(poolSize); err == nil {
|
||||
config.Redis.PoolSize = val
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件上传配置
|
||||
if maxSize := os.Getenv("UPLOAD_MAX_SIZE"); maxSize != "" {
|
||||
if val, err := strconv.ParseInt(maxSize, 10, 64); err == nil {
|
||||
config.Upload.MaxSize = val
|
||||
}
|
||||
}
|
||||
|
||||
if textureMaxSize := os.Getenv("UPLOAD_TEXTURE_MAX_SIZE"); textureMaxSize != "" {
|
||||
if val, err := strconv.ParseInt(textureMaxSize, 10, 64); err == nil {
|
||||
config.Upload.TextureMaxSize = val
|
||||
}
|
||||
}
|
||||
|
||||
if avatarMaxSize := os.Getenv("UPLOAD_AVATAR_MAX_SIZE"); avatarMaxSize != "" {
|
||||
if val, err := strconv.ParseInt(avatarMaxSize, 10, 64); err == nil {
|
||||
config.Upload.AvatarMaxSize = val
|
||||
}
|
||||
}
|
||||
|
||||
// 处理邮件配置
|
||||
if emailEnabled := os.Getenv("EMAIL_ENABLED"); emailEnabled != "" {
|
||||
config.Email.Enabled = emailEnabled == "true" || emailEnabled == "True" || emailEnabled == "TRUE" || emailEnabled == "1"
|
||||
}
|
||||
}
|
||||
67
pkg/config/manager.go
Normal file
67
pkg/config/manager.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// configInstance 全局配置实例
|
||||
configInstance *Config
|
||||
// rustFSConfigInstance 全局RustFS配置实例
|
||||
rustFSConfigInstance *RustFSConfig
|
||||
// once 确保只初始化一次
|
||||
once sync.Once
|
||||
// initError 初始化错误
|
||||
initError error
|
||||
)
|
||||
|
||||
// Init 初始化配置(线程安全,只会执行一次)
|
||||
func Init() error {
|
||||
once.Do(func() {
|
||||
configInstance, initError = Load()
|
||||
if initError != nil {
|
||||
return
|
||||
}
|
||||
rustFSConfigInstance = &configInstance.RustFS
|
||||
})
|
||||
return initError
|
||||
}
|
||||
|
||||
// GetConfig 获取配置实例(线程安全)
|
||||
func GetConfig() (*Config, error) {
|
||||
if configInstance == nil {
|
||||
return nil, fmt.Errorf("配置未初始化,请先调用 config.Init()")
|
||||
}
|
||||
return configInstance, nil
|
||||
}
|
||||
|
||||
// MustGetConfig 获取配置实例,如果未初始化则panic
|
||||
func MustGetConfig() *Config {
|
||||
cfg, err := GetConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// GetRustFSConfig 获取RustFS配置实例(线程安全)
|
||||
func GetRustFSConfig() (*RustFSConfig, error) {
|
||||
if rustFSConfigInstance == nil {
|
||||
return nil, fmt.Errorf("配置未初始化,请先调用 config.Init()")
|
||||
}
|
||||
return rustFSConfigInstance, nil
|
||||
}
|
||||
|
||||
// MustGetRustFSConfig 获取RustFS配置实例,如果未初始化则panic
|
||||
func MustGetRustFSConfig() *RustFSConfig {
|
||||
cfg, err := GetRustFSConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
70
pkg/config/manager_test.go
Normal file
70
pkg/config/manager_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetConfig_NotInitialized 测试未初始化时获取配置
|
||||
func TestGetConfig_NotInitialized(t *testing.T) {
|
||||
// 重置全局变量(在实际测试中可能需要更复杂的重置逻辑)
|
||||
// 注意:由于使用了 sync.Once,这个测试主要验证错误处理逻辑
|
||||
|
||||
// 测试未初始化时的错误消息
|
||||
_, err := GetConfig()
|
||||
if err == nil {
|
||||
t.Error("未初始化时应该返回错误")
|
||||
}
|
||||
|
||||
expectedError := "配置未初始化,请先调用 config.Init()"
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("错误消息 = %q, want %q", err.Error(), expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMustGetConfig_Panic 测试MustGetConfig在未初始化时panic
|
||||
func TestMustGetConfig_Panic(t *testing.T) {
|
||||
// 注意:这个测试会触发panic,需要recover
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("MustGetConfig 应该在未初始化时panic")
|
||||
}
|
||||
}()
|
||||
|
||||
// 尝试获取未初始化的配置
|
||||
_ = MustGetConfig()
|
||||
}
|
||||
|
||||
// TestGetRustFSConfig_NotInitialized 测试未初始化时获取RustFS配置
|
||||
func TestGetRustFSConfig_NotInitialized(t *testing.T) {
|
||||
_, err := GetRustFSConfig()
|
||||
if err == nil {
|
||||
t.Error("未初始化时应该返回错误")
|
||||
}
|
||||
|
||||
expectedError := "配置未初始化,请先调用 config.Init()"
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("错误消息 = %q, want %q", err.Error(), expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMustGetRustFSConfig_Panic 测试MustGetRustFSConfig在未初始化时panic
|
||||
func TestMustGetRustFSConfig_Panic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("MustGetRustFSConfig 应该在未初始化时panic")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = MustGetRustFSConfig()
|
||||
}
|
||||
|
||||
// TestInit_Once 测试Init只执行一次的逻辑
|
||||
func TestInit_Once(t *testing.T) {
|
||||
// 注意:由于sync.Once的特性,这个测试主要验证逻辑
|
||||
// 实际测试中可能需要重置机制
|
||||
|
||||
// 验证Init函数可调用(函数不能直接比较nil)
|
||||
// 这里只验证函数存在
|
||||
_ = Init
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user