diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 6f7bdbb3..b4e30878 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -28,6 +28,7 @@ sidebar: false ### 修复 +- 「切换日志数据库」迁移任务修复冻结期目标库用户访问日志写入被误拦:`BuildForMigration` 未将跳过冻结标记传播到用户访问日志存储,导致迁移任务在清空目标库阶段以「log database is migrating」失败;现在目标库的清空与复制全程放行,任务可正常完成切换。 - 「切换日志数据库」迁移任务补齐第 6 张表(用户访问日志)的复制与清空;目标为 PostgreSQL 时按源库时间范围预建分区,历史日志可正常迁移;冻结前先排空批写入队列,避免在途日志丢失。 - 节点监控最新指标读取仅在 ClickHouse 为当前日志库时走 ClickHouse 快速路径,日志库切换后仪表盘不再读到旧库数据。 - `log_database` / `log_db_migration` 为受保护配置,管理端创建/修改接口均拒绝手动写入。 diff --git a/frontend/lib/theme/themes.json b/frontend/lib/theme/themes.json index 40c4d4ba..9818de0f 100644 --- a/frontend/lib/theme/themes.json +++ b/frontend/lib/theme/themes.json @@ -4559,4 +4559,4 @@ } } } -] +] \ No newline at end of file diff --git a/internal/apps/openflare/tasks/log_db_switch_test.go b/internal/apps/openflare/tasks/log_db_switch_test.go index cef8b157..727895fb 100644 --- a/internal/apps/openflare/tasks/log_db_switch_test.go +++ b/internal/apps/openflare/tasks/log_db_switch_test.go @@ -172,6 +172,45 @@ func TestClearTargetLogTablesClearsUserAccessLogs(t *testing.T) { assert.Zero(t, count, "用户访问日志应被清空") } +// TestClearTargetLogTablesDuringMigration 回归:冻结标记置位后,BuildForMigration 构造的 +// 目标 store 必须放行用户访问日志清空/写入。skipFreeze 未传播到 UserAccessLogs store 时 +// DeleteAll 会误报 ErrMigrating,导致真实切换任务在清空目标库阶段失败。 +func TestClearTargetLogTablesDuringMigration(t *testing.T) { + logstore.ResetForTest() + defer logstore.ResetForTest() + + gdb := newLogDBSwitchDB(t) + db.SetDB(gdb) + t.Cleanup(func() { db.SetDB(nil) }) + ctx := context.Background() + + logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) { + cfg, err := repository.GetSystemConfigByKey(ctx, key) + if err != nil { + return "", err + } + return cfg.Value, nil + }) + + // 预置目标库已有日志(迁移「覆盖目标库已有日志」幂等前提)。 + now := time.Now().UTC() + require.NoError(t, gdb.Create(&analyticsmodel.UserAccessLog{ID: 1, UserID: 1, Path: "/a", CreatedAt: now}).Error) + + // 冻结标记置位(与真实任务 Execute 流程一致)。 + require.NoError(t, setMigrationFlag(ctx, "migrating")) + t.Cleanup(func() { _ = setMigrationFlag(ctx, "") }) + require.True(t, logstore.Migrating(ctx)) + + dst, err := logstore.BuildForMigration(ctx, "sqlite") + require.NoError(t, err) + + require.NoError(t, clearTargetLogTables(ctx, dst), "迁移冻结期间目标库清空必须放行") + + var count int64 + require.NoError(t, gdb.Model(&analyticsmodel.UserAccessLog{}).Count(&count).Error) + assert.Zero(t, count, "用户访问日志应被清空") +} + // TestValidateSwitch 各非法组合报错。 func TestValidateSwitch(t *testing.T) { oldDB, oldCH := config.Config.Database.Enabled, config.Config.ClickHouse.Enabled diff --git a/internal/repository/logstore/provider.go b/internal/repository/logstore/provider.go index 14df5cb7..d21f7fb4 100644 --- a/internal/repository/logstore/provider.go +++ b/internal/repository/logstore/provider.go @@ -105,20 +105,24 @@ func buildStore(ctx context.Context, database string, skipFreeze bool) (*Store, case dbNameClickHouse: ch := newClickHouseStore() ch.skipFreeze = skipFreeze + ual := newClickHouseUserAccessLogStore() + ual.skipFreeze = skipFreeze return &Store{ AccessLogs: ch, Observability: ch, - UserAccessLogs: newClickHouseUserAccessLogStore(), + UserAccessLogs: ual, Status: ch, }, nil case dbNamePostgres, dbNameSQLite: gdb := db.DB(ctx) g := newGormStore(gdb) g.skipFreeze = skipFreeze + ual := newUserAccessLogGormStore(gdb) + ual.skipFreeze = skipFreeze return &Store{ AccessLogs: g, Observability: g, - UserAccessLogs: newUserAccessLogGormStore(gdb), + UserAccessLogs: ual, Status: g, }, nil default: