package database import ( "carrotskin/internal/model" "carrotskin/pkg/config" "fmt" "sync" "go.uber.org/zap" "gorm.io/gorm" ) var ( // dbInstance 全局数据库实例 dbInstance *gorm.DB // once 确保只初始化一次 once sync.Once // initError 初始化错误 initError error ) // Init 初始化数据库连接(线程安全,只会执行一次) func Init(cfg config.DatabaseConfig, logger *zap.Logger) error { once.Do(func() { dbInstance, initError = New(cfg) if initError != nil { logger.Error("数据库初始化失败", zap.Error(initError)) return } logger.Info("数据库连接成功") }) return initError } // GetDB 获取数据库实例(线程安全) func GetDB() (*gorm.DB, error) { if dbInstance == nil { return nil, fmt.Errorf("数据库未初始化,请先调用 database.Init()") } return dbInstance, nil } // MustGetDB 获取数据库实例,如果未初始化则panic func MustGetDB() *gorm.DB { db, err := GetDB() if err != nil { panic(err) } return db } // AutoMigrate 自动迁移数据库表结构 func AutoMigrate(logger *zap.Logger) error { db, err := GetDB() if err != nil { return fmt.Errorf("获取数据库实例失败: %w", err) } logger.Info("开始执行数据库迁移...") // 迁移所有表 - 注意顺序:先创建被引用的表,再创建引用表 // 使用分批迁移,避免某些表的问题影响其他表 tables := []interface{}{ // 用户相关表(先创建,因为其他表可能引用它) &model.User{}, &model.UserPointLog{}, &model.UserLoginLog{}, // 档案相关表 &model.Profile{}, // 材质相关表 &model.Texture{}, &model.UserTextureFavorite{}, &model.TextureDownloadLog{}, // 认证相关表 &model.Token{}, // Yggdrasil相关表(在User之后创建,因为它引用User) &model.Yggdrasil{}, // 系统配置表 &model.SystemConfig{}, // 审计日志表 &model.AuditLog{}, // Casbin权限规则表 &model.CasbinRule{}, } // 逐个迁移表,以便更好地定位问题 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("数据库迁移完成") return nil } // Close 关闭数据库连接 func Close() error { if dbInstance == nil { return nil } sqlDB, err := dbInstance.DB() if err != nil { return err } return sqlDB.Close() }