fix(migrations): 数据库升级 - 兼容旧工作台 v51 账本
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.6.7
|
||||
|
||||
- 修复旧版工作台曾占用 Schema v51 导致本地启动和线上更新校验失败的问题;迁移器会在精确匹配旧账本且工作台表完整时自动无损归一到 v54,再继续执行 v51–v53。
|
||||
- 修复服务端出站 URL 对 DNS 解析结果的私网 IP 拦截被注释的问题,恢复对环回、内网和链路本地地址的拒绝,避免 SSRF。
|
||||
- 修复 Quality checks 中工作台测试模块路径、懒加载画布路由合同、模型促销标签和影视剧作工坊完整性基线问题。
|
||||
- 正式镜像包含影视剧作工坊内置 Skill;服务启动时同步技能目录、版本和文件记录,并保留用户加入/收藏状态。
|
||||
- 升级注意:数据库 Schema 为 v54;生产 Compose 的 `migrate` 服务会在 Backend 启动前执行 `migrate-schema up`,旧 v51 工作台账本会自动兼容升级,无需手工修改 `schema_migrations`。
|
||||
|
||||
## v1.6.6.1
|
||||
|
||||
- 修复旧版工作台曾占用 Schema v51 导致本地启动和线上更新校验失败的问题;迁移器会在精确匹配旧账本且表完整时自动无损归一到 v54,再继续执行 v51–v53。
|
||||
- 修复服务端出站 URL 对 DNS 解析结果的私网 IP 拦截被注释的问题,恢复对环回、内网和链路本地地址的拒绝,避免 SSRF。
|
||||
- 修复质量检查:工作台测试模块路径、懒加载画布路由合同和模型促销标签断言;修正影视剧作工坊内置脚本树完整性基线。
|
||||
- 修复创作页上一条任务仍在生成时无法提交下一条任务的问题。
|
||||
|
||||
@@ -29,6 +29,9 @@ const prefixedIDSequenceReconcileChecksum = "sha256:prefixed-id-sequence-reconci
|
||||
const skillLibraryCategoriesChecksum = "sha256:skill-library-categories-v39-20260926"
|
||||
const builtinSkillTombstonesChecksum = "sha256:builtin-skill-tombstones-v40-20260927"
|
||||
const resourceThumbnailChecksum = "sha256:resource-thumbnail-v41-20260927"
|
||||
const workbenchToolsMigrationName = "workbench_tools_and_presets"
|
||||
const workbenchToolsMigrationChecksum = "sha256:workbench-tools-presets-v54-20261014"
|
||||
const legacyWorkbenchToolsMigrationChecksum = "sha256:workbench-tools-presets-v51-20261014"
|
||||
|
||||
const postgresSchemaMigrationLockID int64 = 73123910420260830
|
||||
|
||||
@@ -178,7 +181,7 @@ var schemaMigrations = []migration{
|
||||
{version: 53, name: "user_login_logs", checksum: "sha256:user-login-logs-v53", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.LoginLog{})
|
||||
}},
|
||||
{version: 54, name: "workbench_tools_and_presets", checksum: "sha256:workbench-tools-presets-v54-20261014", apply: func(tx *gorm.DB) error {
|
||||
{version: 54, name: workbenchToolsMigrationName, checksum: workbenchToolsMigrationChecksum, apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.WorkbenchTool{}, &model.WorkbenchPreset{}, &model.Task{})
|
||||
}},
|
||||
}
|
||||
@@ -510,6 +513,47 @@ func migrateSchemaV12(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeLegacyWorkbenchMigration(tx *gorm.DB) error {
|
||||
var legacy schemaMigration
|
||||
if err := tx.First(&legacy, "version = ?", 51).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("读取旧工作台迁移记录:%w", err)
|
||||
}
|
||||
if legacy.Name != workbenchToolsMigrationName || legacy.Checksum != legacyWorkbenchToolsMigrationChecksum {
|
||||
return nil
|
||||
}
|
||||
|
||||
var later []schemaMigration
|
||||
if err := tx.Where("version > ?", legacy.Version).Order("version asc").Find(&later).Error; err != nil {
|
||||
return fmt.Errorf("读取旧工作台迁移后的记录:%w", err)
|
||||
}
|
||||
if len(later) > 0 {
|
||||
return fmt.Errorf("数据库迁移 51 使用旧工作台身份,但已存在后续迁移记录 %d;拒绝自动改写迁移账本", later[0].Version)
|
||||
}
|
||||
if !tx.Migrator().HasTable(&model.WorkbenchTool{}) || !tx.Migrator().HasTable(&model.WorkbenchPreset{}) {
|
||||
return fmt.Errorf("数据库迁移 51 记录为旧工作台迁移,但工作台表不完整;请从备份恢复后再升级")
|
||||
}
|
||||
|
||||
result := tx.Model(&schemaMigration{}).
|
||||
Where("version = ? AND name = ? AND checksum = ?", legacy.Version, legacy.Name, legacy.Checksum).
|
||||
Updates(map[string]any{
|
||||
"version": workbenchToolsMigrationVersion,
|
||||
"name": workbenchToolsMigrationName,
|
||||
"checksum": workbenchToolsMigrationChecksum,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("归一化旧工作台迁移记录:%w", result.Error)
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
return fmt.Errorf("归一化旧工作台迁移记录影响 %d 行,拒绝继续启动", result.RowsAffected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const workbenchToolsMigrationVersion int64 = 54
|
||||
|
||||
func MigrateSchema(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if tx.Dialector.Name() == "postgres" {
|
||||
@@ -520,6 +564,9 @@ func MigrateSchema(db *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&schemaMigration{}); err != nil {
|
||||
return fmt.Errorf("初始化数据库迁移记录:%w", err)
|
||||
}
|
||||
if err := normalizeLegacyWorkbenchMigration(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
plan, err := migrationsForDatabase(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -98,6 +98,53 @@ func TestMigrateSchemaRecordsAndValidatesVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaNormalizesLegacyWorkbenchMigrationV51(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-legacy-workbench-v51?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version IN ?", []int64{51, 52, 53}).Delete(&schemaMigration{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Model(&schemaMigration{}).Where("version = ?", 54).Updates(map[string]any{
|
||||
"version": 51,
|
||||
"name": workbenchToolsMigrationName,
|
||||
"checksum": legacyWorkbenchToolsMigrationChecksum,
|
||||
}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("legacy workbench migration should be normalized: %v", err)
|
||||
}
|
||||
status, err := ReadSchemaStatus(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !status.Ready || status.Current != CurrentSchemaVersion {
|
||||
t.Fatalf("unexpected normalized schema status: %#v", status)
|
||||
}
|
||||
var normalized schemaMigration
|
||||
if err := db.First(&normalized, "version = ?", workbenchToolsMigrationVersion).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if normalized.Name != workbenchToolsMigrationName || normalized.Checksum != workbenchToolsMigrationChecksum {
|
||||
t.Fatalf("normalized workbench migration = %#v", normalized)
|
||||
}
|
||||
for _, item := range schemaMigrations {
|
||||
var count int64
|
||||
if err := db.Model(&schemaMigration{}).Where("version = ? AND name = ? AND checksum = ?", item.version, item.name, item.checksum).Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.version != workbenchToolsMigrationVersion && count != 1 {
|
||||
t.Fatalf("migration %d was not restored: %#v", item.version, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV35UpgradesExistingDatabase(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-auth-notifications-v35?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
|
||||
@@ -76,6 +76,8 @@ Schema 45 新增 `upload_reservations`:`id`、`user_id`、`size`、`day`、`ex
|
||||
|
||||
渠道模型的文本能力 JSON 还可声明 `contextWindowTokens` 与 `maxOutputTokens`。这两个字段是模型能力合同,不是数据库或 Agent checkpoint 的容量配置;旧记录读取时补 128K / 16K 默认值。运行状态、journal 和 transcript 的持久化边界仍由既有 runtime 表及其 512KB checkpoint 校验负责,本阶段没有通过扩大 checkpoint 绕过模型上下文限制。
|
||||
|
||||
历史发布曾使用版本 51 `workbench_tools_and_presets`,后续版本将个人资料安全、头像预设和登录日志依次固定为 51–53,并将工作台迁移规范为 54。迁移器仅在版本 51 的名称为 `workbench_tools_and_presets` 且校验和精确为 `sha256:workbench-tools-presets-v51-20261014`、不存在更高迁移记录、且工作台表完整时,将该已成功应用记录在同一事务中归一到规范版本 54,然后继续应用 51–53;既有应用时间保留。其他未知名称、校验和或部分迁移状态仍拒绝启动,不通过删除、改名或伪造记录绕过检查。
|
||||
|
||||
历史发布曾使用版本 6 `asset_library_folders`,之后合并引入了上述 6/7 顺序冲突。迁移器仅在该历史记录的名称和校验和均精确匹配时选择历史顺序:保留版本 6 的素材目录记录及时间,在版本 7 应用 `resource_playback_variant`,再继续版本 8。新数据库及已采用“6 播放副本、7 素材目录”的数据库保持原顺序;状态检查与启动校验采用同一套识别规则。未知名称、被改动的校验和或冲突的版本 7 记录仍会拒绝迁移,不通过删除、改名或伪造已应用记录绕过检查。
|
||||
|
||||
升级前备份数据库,迁移命令必须沿用部署实际配置的数据库连接和数据目录。开发环境启用自动迁移时重新启动新代码即可;关闭自动迁移的部署先使用新构建的 `migrate-schema up`,然后 `migrate-schema verify`,成功后启动 Backend。迁移与记录写入在事务中执行;此修复不提供降级迁移,回退旧二进制可能无法识别历史顺序,应恢复匹配的备份和版本,不手工改迁移表。
|
||||
|
||||
Reference in New Issue
Block a user