package models import ( "fmt" "gorm.io/driver/postgres" "gorm.io/gorm" ) var DBService *gorm.DB // migrationAdvisoryLockKey scopes the PostgreSQL session lock to SenlinAI schema changes. const migrationAdvisoryLockKey int64 = 0x53454e4c494e4149 func New(dsn string) error { // 启用 Gorm 跨驱动错误翻译,业务层无需解析 Postgres 错误文本或 SQLSTATE。 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{TranslateError: true}) if err != nil { return err } if err := AutoMigrate(db); err != nil { return err } DBService = db return nil } func AutoMigrate(database *gorm.DB) error { if !migrationAdvisoryLockRequired(database.Dialector.Name()) { return autoMigrateUnlocked(database) } // Connection pins every migration statement to one PostgreSQL session so the // advisory lock covers both Gorm's schema work and the versioned data repairs. return database.Connection(func(connection *gorm.DB) (resultErr error) { if err := connection.Exec("SELECT pg_advisory_lock(?)", migrationAdvisoryLockKey).Error; err != nil { return fmt.Errorf("acquire schema migration advisory lock: %w", err) } defer func() { if err := connection.Exec("SELECT pg_advisory_unlock(?)", migrationAdvisoryLockKey).Error; resultErr == nil && err != nil { resultErr = fmt.Errorf("release schema migration advisory lock: %w", err) } }() return autoMigrateUnlocked(connection) }) } func migrationAdvisoryLockRequired(dialect string) bool { return dialect == "postgres" } func autoMigrateUnlocked(database *gorm.DB) error { if err := database.AutoMigrate( &SenlinAgentSchemaMigration{}, &SenlinAgentUser{}, &SenlinAgentProject{}, &SenlinAgentInboxItem{}, &SenlinAgentInboxSuggestion{}, &SenlinAgentTask{}, &SenlinAgentNote{}, &SenlinAgentSource{}, &SenlinAgentAISession{}, &SenlinAgentTag{}, &SenlinAgentProjectChannel{}, &SenlinAgentCronPlan{}, &SenlinAgentProjectEvent{}, &SenlinAgentAIKey{}, &SenlinAgentAICallLog{}, &SenlinAgentAIRateBucket{}, &SenlinAgentTaskShare{}, ); err != nil { return err } return runVersionedMigrations(database) }