feat: 添加Yggdrasil密码重置功能,更新依赖和配置

This commit is contained in:
lafay
2025-11-30 18:56:56 +08:00
parent a4b6c5011e
commit 4188ee1555
18 changed files with 683 additions and 95 deletions

View File

@@ -12,15 +12,16 @@ import (
// 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"`
Environment string `mapstructure:"environment"`
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 服务器配置
@@ -78,21 +79,21 @@ type CasbinConfig struct {
// 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"`
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"`
MaxSize int64 `mapstructure:"max_size"`
AllowedTypes []string `mapstructure:"allowed_types"`
TextureMaxSize int64 `mapstructure:"texture_max_size"`
AvatarMaxSize int64 `mapstructure:"avatar_max_size"`
}
// EmailConfig 邮件配置
@@ -109,14 +110,14 @@ type EmailConfig struct {
func Load() (*Config, error) {
// 加载.env文件如果存在
_ = godotenv.Load(".env")
// 设置默认值
setDefaults()
// 设置环境变量前缀
viper.SetEnvPrefix("CARROTSKIN")
viper.AutomaticEnv()
// 手动设置环境变量映射
setupEnvMappings()
@@ -125,7 +126,7 @@ func Load() (*Config, error) {
if err := viper.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("解析配置失败: %w", err)
}
// 从环境变量中覆盖配置
overrideFromEnv(&config)
@@ -139,7 +140,7 @@ func setDefaults() {
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")
@@ -149,24 +150,24 @@ func setDefaults() {
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")
@@ -175,13 +176,13 @@ func setDefaults() {
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)
@@ -194,7 +195,7 @@ func setupEnvMappings() {
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")
@@ -204,28 +205,28 @@ func setupEnvMappings() {
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")
@@ -244,61 +245,71 @@ func overrideFromEnv(config *Config) {
}
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"
}
// 处理环境配置
if env := os.Getenv("ENVIRONMENT"); env != "" {
config.Environment = env
}
}
// IsTestEnvironment 判断是否为测试环境
func (c *Config) IsTestEnvironment() bool {
return c.Environment == "test"
}

View File

@@ -59,7 +59,8 @@ func AutoMigrate(logger *zap.Logger) error {
logger.Info("开始执行数据库迁移...")
// 迁移所有表 - 注意顺序:先创建被引用的表,再创建引用表
err = db.AutoMigrate(
// 使用分批迁移,避免某些表的问题影响其他表
tables := []interface{}{
// 用户相关表(先创建,因为其他表可能引用它)
&model.User{},
&model.UserPointLog{},
@@ -87,11 +88,30 @@ func AutoMigrate(logger *zap.Logger) error {
// Casbin权限规则表
&model.CasbinRule{},
)
}
if err != nil {
logger.Error("数据库迁移失败", zap.Error(err))
return fmt.Errorf("数据库迁移失败: %w", err)
// 逐个迁移表,以便更好地定位问题
for _, table := range tables {
tableName := fmt.Sprintf("%T", table)
logger.Info("正在迁移表", zap.String("table", tableName))
if err := db.AutoMigrate(table); err != nil {
logger.Error("数据库迁移失败", zap.Error(err), zap.String("table", tableName))
// 如果是 User 表且错误是 insufficient arguments可能是 Properties 字段问题
if tableName == "*model.User" {
logger.Warn("User 表迁移失败,可能是 Properties 字段问题,尝试修复...")
// 尝试手动添加 properties 字段(如果不存在)
if err := db.Exec("ALTER TABLE \"user\" ADD COLUMN IF NOT EXISTS properties jsonb").Error; err != nil {
logger.Error("添加 properties 字段失败", zap.Error(err))
}
// 再次尝试迁移
if err := db.AutoMigrate(table); err != nil {
return fmt.Errorf("数据库迁移失败 (表: %T): %w", table, err)
}
} else {
return fmt.Errorf("数据库迁移失败 (表: %T): %w", table, err)
}
}
logger.Info("表迁移成功", zap.String("table", tableName))
}
logger.Info("数据库迁移完成")