From 7ca625730efb1f4c68003ddb4206ad152ec49595 Mon Sep 17 00:00:00 2001 From: ddcat666 <26564566+ddcat666@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:44:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(database):=20=E8=AE=A4=E8=AF=81=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=20-=20=E8=A1=A5=E9=BD=90=20v35=20=E5=A2=9E=E9=87=8F?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/database/migrations.go | 4 +- backend/internal/database/migrations_test.go | 56 +++++++++++++++++++ backend/internal/database/schema.go | 17 ++++++ .../content/docs/backend/backend-database.mdx | 2 +- 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/backend/internal/database/migrations.go b/backend/internal/database/migrations.go index f2bc85c51..52dba129e 100644 --- a/backend/internal/database/migrations.go +++ b/backend/internal/database/migrations.go @@ -11,7 +11,7 @@ import ( "gorm.io/gorm" ) -const CurrentSchemaVersion int64 = 34 +const CurrentSchemaVersion int64 = 35 const baselineSchemaChecksum = "sha256:open-ai-canvas-schema-v1-20260830" const schemaMigrationAppliedAtIndexChecksum = "sha256:schema-migrations-applied-at-index-v2-20260830" @@ -22,6 +22,7 @@ const resourcePlaybackChecksum = "sha256:resource-playback-v6-20260902" const assetLibraryFoldersChecksum = "sha256:asset-library-folders-v6-20260902" const logicalModelActiveCodeChecksum = "sha256:logical-model-active-code-v8-20260905" const creationRuntimeChecksum = "sha256:creation-runtime-v10-20260909" +const authNotificationsChecksum = "sha256:auth-notifications-v35-20260924" const postgresSchemaMigrationLockID int64 = 73123910420260830 @@ -117,6 +118,7 @@ var schemaMigrations = []migration{ } return nil }}, + {version: 35, name: "auth_notifications", checksum: authNotificationsChecksum, apply: migrateSchemaV35}, } func migrateChannelModelTags(tx *gorm.DB) error { diff --git a/backend/internal/database/migrations_test.go b/backend/internal/database/migrations_test.go index d67c2a712..3a8075305 100644 --- a/backend/internal/database/migrations_test.go +++ b/backend/internal/database/migrations_test.go @@ -66,11 +66,67 @@ func TestMigrateSchemaRecordsAndValidatesVersion(t *testing.T) { if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "notice_type") { t.Fatal("schema migration v22 did not create banner announcements notice_type") } + if !db.Migrator().HasTable(&model.AuthVerification{}) || + !db.Migrator().HasTable(&model.NotificationQuota{}) || + !db.Migrator().HasTable(&model.SMSChannel{}) || + !db.Migrator().HasTable(&model.SMSRecord{}) { + t.Fatal("schema migration v35 did not create auth notification tables") + } + if !db.Migrator().HasColumn(&model.User{}, "phone") || + !db.Migrator().HasColumn(&model.User{}, "email_verified_at") || + !db.Migrator().HasColumn(&model.User{}, "phone_verified_at") || + !db.Migrator().HasColumn(&model.EmailVerificationCode{}, "attempts") { + t.Fatal("schema migration v35 did not add authentication verification fields") + } if err := MigrateSchema(db); err != nil { t.Fatalf("migration should be idempotent: %v", err) } } +func TestMigrateSchemaV35UpgradesExistingDatabase(t *testing.T) { + db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-auth-notifications-v35?mode=memory&cache=shared"}) + if err != nil { + t.Fatal(err) + } + if err := MigrateSchema(db); err != nil { + t.Fatal(err) + } + + for _, table := range []any{&model.AuthVerification{}, &model.NotificationQuota{}, &model.SMSChannel{}, &model.SMSRecord{}} { + if err := db.Migrator().DropTable(table); err != nil { + t.Fatal(err) + } + } + for _, column := range []string{"phone", "email_verified_at", "phone_verified_at"} { + if err := db.Migrator().DropColumn(&model.User{}, column); err != nil { + t.Fatal(err) + } + } + if err := db.Migrator().DropColumn(&model.EmailVerificationCode{}, "attempts"); err != nil { + t.Fatal(err) + } + if err := db.Where("version = ?", 35).Delete(&schemaMigration{}).Error; err != nil { + t.Fatal(err) + } + + if err := MigrateSchema(db); err != nil { + t.Fatalf("upgrade from v34: %v", err) + } + for _, table := range []any{&model.AuthVerification{}, &model.NotificationQuota{}, &model.SMSChannel{}, &model.SMSRecord{}} { + if !db.Migrator().HasTable(table) { + t.Fatalf("migration v35 did not restore table %T", table) + } + } + for _, column := range []string{"phone", "email_verified_at", "phone_verified_at"} { + if !db.Migrator().HasColumn(&model.User{}, column) { + t.Fatalf("migration v35 did not restore users.%s", column) + } + } + if !db.Migrator().HasColumn(&model.EmailVerificationCode{}, "attempts") { + t.Fatal("migration v35 did not restore email verification attempts") + } +} + func TestMigrateSchemaV15UpgradesExistingDatabase(t *testing.T) { db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-agent-profiles-v15?mode=memory&cache=shared"}) if err != nil { diff --git a/backend/internal/database/schema.go b/backend/internal/database/schema.go index 1be1f3705..98494b094 100644 --- a/backend/internal/database/schema.go +++ b/backend/internal/database/schema.go @@ -169,6 +169,23 @@ func migrateSchemaV1(db *gorm.DB) error { return db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_nonempty ON users(lower(email)) WHERE email <> ''").Error } +// migrateSchemaV35 installs the authentication notification tables and fields +// added after the v34 deployment line. They cannot be added to the baseline +// model registry alone because existing databases never rerun migration v1. +func migrateSchemaV35(db *gorm.DB) error { + if err := db.AutoMigrate( + &model.User{}, + &model.EmailVerificationCode{}, + &model.AuthVerification{}, + &model.NotificationQuota{}, + &model.SMSChannel{}, + &model.SMSRecord{}, + ); err != nil { + return err + } + return db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_phone_nonempty ON users(phone) WHERE phone <> ''").Error +} + func backfillProjectUnitWordCounts(db *gorm.DB) error { var units []model.ProjectUnit if err := db.Select("id", "source_text").Where("word_count = 0 AND source_text <> ''").Find(&units).Error; err != nil { diff --git a/docs/content/docs/backend/backend-database.mdx b/docs/content/docs/backend/backend-database.mdx index c4f28a0c7..5939b05d1 100644 --- a/docs/content/docs/backend/backend-database.mdx +++ b/docs/content/docs/backend/backend-database.mdx @@ -15,7 +15,7 @@ description: 影策后端核心持久化表与状态边界 ## 结构迁移与版本 -当前结构版本为 34。版本 34 `task_media_recovery` 为 `tasks` 增加 `media_recovery_json`(text)和 `media_stage`(最长 24 字)。版本 33 `oauth_state_accepted_terms` 为 `o_auth_states` 增加非空布尔列 `accepted_terms`,默认 `false`,不替历史流程补造同意记录。升级前备份,按既有流程执行 `migrate-schema up` 和 `verify` 后再启动新后端。旧版本二进制不支持版本 34,回退需恢复匹配的数据库备份。 +当前结构版本为 35。版本 35 `auth_notifications` 为认证通知与组合验证补齐 `auth_verifications`、`notification_quotas`、`sms_channels`、`sms_records` 四张表,并为 `users` 增加 `phone`、`email_verified_at`、`phone_verified_at`,为 `email_verification_codes` 增加 `attempts`;同时创建非空手机号唯一索引。版本 34 `task_media_recovery` 为 `tasks` 增加 `media_recovery_json`(text)和 `media_stage`(最长 24 字)。版本 33 `oauth_state_accepted_terms` 为 `o_auth_states` 增加非空布尔列 `accepted_terms`,默认 `false`,不替历史流程补造同意记录。升级前备份,按既有流程执行 `migrate-schema up` 和 `verify` 后再启动新后端。旧版本二进制不支持版本 35,回退需恢复匹配的数据库备份。关闭自动迁移的生产环境必须运行正式的 v35 migration;仅在数据库中手工补表不能更新 `schema_migrations` 的版本与校验和记录。 ### 生成成功后的作品保存恢复