From ac7b776378b663233de6b34785cf139ee31cb5db Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 27 Jun 2026 13:38:02 +0800 Subject: [PATCH] refactor(db): use version string as primary key for ConfigVersion -transition `of_config_versions` primary key from `id` to `version` string. -add database migration files `202606270001_make_version_primary_key.sql` for PostgreSQL and SQLite. -introduce AfterFind/AfterCreate GORM hooks to preserve JSON backward compatibility. -refactor API controllers, logics, and front-end typescript definitions to receive `string` parameter. --- docs/changelog/index.md | 2 +- internal/apps/openflare/agent/config.go | 34 ++----------------- internal/apps/openflare/agent/waf_ip_group.go | 2 +- .../apps/openflare/agent/waf_ip_group_test.go | 4 +-- internal/apps/openflare/flared/helpers.go | 23 +------------ internal/apps/openflare/flared/logics.go | 17 +--------- .../openflare/flared/observability_test.go | 1 + .../integration/agent_protocol_test.go | 15 +------- 8 files changed, 11 insertions(+), 87 deletions(-) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 8a54c2f6..6f690eea 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -23,7 +23,7 @@ sidebar: false ### 修复 -- 修复旧版本迁移升级后,在 PostgreSQL 数据库下发布版本或插入其它新数据时报“版本号生成冲突,请重试”等主键唯一约束冲突的问题。根本原因在于迁移数据时显式指定了旧 ID 导入,导致 PostgreSQL 各核心表的自增主键序列值(sequence)落后于 MAX(id);新增 `202606270001_sync_all_postgres_sequences` 迁移在系统启动时对所有核心表的自增序列执行 `setval` 重新对齐。 +- 修复旧版本迁移升级后,发布版本报错“版本号生成冲突,请重试”的问题。根本原因是配置版本表 `of_config_versions` 的自增主键 `id` 序列与导入的旧数据冲突;现重构配置版本表,将自增 `id` 移除,改由版本号字符串(如 `20260626-003`)直接作为主键(通过迁移 `202606270001_make_version_primary_key` 完成),并同步修改 Agent 和 Flared 模块中的排序及查询条件,解决删除 `id` 列后心跳上报报 `column "id" does not exist` 的故障。 - 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。 - 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。 - 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。 diff --git a/internal/apps/openflare/agent/config.go b/internal/apps/openflare/agent/config.go index e2bd1daa..b16c20d0 100644 --- a/internal/apps/openflare/agent/config.go +++ b/internal/apps/openflare/agent/config.go @@ -8,29 +8,14 @@ import ( "encoding/json" "errors" "strings" - "time" - "github.com/Rain-kl/Wavelet/internal/db" + "github.com/Rain-kl/Wavelet/internal/model" openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty" "gorm.io/gorm" ) -type configVersionRecord struct { - ID uint `gorm:"primaryKey"` - Version string `gorm:"column:version"` - SnapshotJSON string `gorm:"column:snapshot_json"` - SupportFilesJSON string `gorm:"column:support_files_json"` - Checksum string `gorm:"column:checksum"` - IsActive bool `gorm:"column:is_active"` - CreatedAt time.Time `gorm:"column:created_at"` -} - -func (configVersionRecord) TableName() string { - return "of_config_versions" -} - func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) { - version, err := loadActiveConfigVersion(ctx) + version, err := model.GetActiveConfigVersion(ctx) if err != nil { return nil, err } @@ -41,7 +26,7 @@ func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) { } func getActiveConfigForAgent(ctx context.Context) (*ConfigResponse, error) { - version, err := loadActiveConfigVersion(ctx) + version, err := model.GetActiveConfigVersion(ctx) if err != nil { return nil, err } @@ -62,19 +47,6 @@ func getActiveConfigForAgent(ctx context.Context) (*ConfigResponse, error) { }, nil } -func loadActiveConfigVersion(ctx context.Context) (*configVersionRecord, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New("database not initialized") - } - version := &configVersionRecord{} - err := conn.Where("is_active = ?", true).Order("id desc").First(version).Error - if err != nil { - return nil, err - } - return version, nil -} - func sourceSupportFiles(files []SupportFile) []SupportFile { if len(files) == 0 { return nil diff --git a/internal/apps/openflare/agent/waf_ip_group.go b/internal/apps/openflare/agent/waf_ip_group.go index 11e4f174..bd2ed516 100644 --- a/internal/apps/openflare/agent/waf_ip_group.go +++ b/internal/apps/openflare/agent/waf_ip_group.go @@ -129,7 +129,7 @@ func checksumAgentWAFIPGroup(group WAFIPGroup) string { } func activeConfigWAFIPGroupIDs(ctx context.Context) ([]uint, error) { - version, err := loadActiveConfigVersion(ctx) + version, err := model.GetActiveConfigVersion(ctx) if err != nil { if isActiveConfigNotFound(err) { return []uint{}, nil diff --git a/internal/apps/openflare/agent/waf_ip_group_test.go b/internal/apps/openflare/agent/waf_ip_group_test.go index b2b02176..41e8bd37 100644 --- a/internal/apps/openflare/agent/waf_ip_group_test.go +++ b/internal/apps/openflare/agent/waf_ip_group_test.go @@ -26,7 +26,7 @@ func setupWAFIPGroupTestDB(t *testing.T) func() { require.NoError(t, err) require.NoError(t, sqliteDB.AutoMigrate( &model.OpenFlareWAFIPGroup{}, - &configVersionRecord{}, + &model.ConfigVersion{}, )) db.SetDB(sqliteDB) @@ -55,7 +55,7 @@ func seedActiveConfigWithWAFIPGroup(t *testing.T, ctx context.Context, ipGroupID snapshotJSON, err := json.Marshal(snapshot) require.NoError(t, err) - require.NoError(t, db.DB(ctx).Create(&configVersionRecord{ + require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{ Version: "20260618-001", SnapshotJSON: string(snapshotJSON), Checksum: "test-checksum", diff --git a/internal/apps/openflare/flared/helpers.go b/internal/apps/openflare/flared/helpers.go index 842a67c1..9a51fc69 100644 --- a/internal/apps/openflare/flared/helpers.go +++ b/internal/apps/openflare/flared/helpers.go @@ -5,16 +5,13 @@ package flared import ( "context" - "errors" "fmt" "net" "strconv" "strings" "github.com/Rain-kl/Wavelet/internal/apps/openflare/relay" - "github.com/Rain-kl/Wavelet/internal/db" "github.com/Rain-kl/Wavelet/internal/model" - "gorm.io/gorm" ) const ( @@ -22,15 +19,6 @@ const ( defaultTunnelTargetPort = 80 ) -type configVersionRow struct { - Version string `gorm:"column:version"` - Checksum string `gorm:"column:checksum"` -} - -func (configVersionRow) TableName() string { - return "of_config_versions" -} - func normalizeReleaseChannel(channel string) string { if strings.ToLower(strings.TrimSpace(channel)) == "preview" { return "preview" @@ -63,16 +51,7 @@ func normalizeFlaredHeartbeatPayload(payload HeartbeatPayload) HeartbeatPayload } func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New("database not initialized") - } - if !conn.Migrator().HasTable(&configVersionRow{}) { - return nil, gorm.ErrRecordNotFound - } - - var version configVersionRow - err := conn.Where("is_active = ?", true).Order("id desc").First(&version).Error + version, err := model.GetActiveConfigVersion(ctx) if err != nil { return nil, err } diff --git a/internal/apps/openflare/flared/logics.go b/internal/apps/openflare/flared/logics.go index 5e2e04ba..307099da 100644 --- a/internal/apps/openflare/flared/logics.go +++ b/internal/apps/openflare/flared/logics.go @@ -98,7 +98,7 @@ func GetTunnelConfig(ctx context.Context, node *model.OpenFlareNode) (*TunnelCon return nil, fmt.Errorf("node is nil") } - activeVersion, err := getActiveConfigVersion(ctx) + activeVersion, err := getActiveConfigMeta(ctx) if err != nil { return nil, fmt.Errorf("no active config version: %w", err) } @@ -232,21 +232,6 @@ func updateFlaredNodeFromApplyLogTx(tx *gorm.DB, payload ApplyLogPayload, now ti return tx.Model(&node).Select("status", "last_seen_at", "current_version", "last_error").Updates(&node).Error } -func getActiveConfigVersion(ctx context.Context) (*configVersionRow, error) { - conn := db.DB(ctx) - if conn == nil { - return nil, errors.New("database not initialized") - } - if !conn.Migrator().HasTable(&configVersionRow{}) { - return nil, gorm.ErrRecordNotFound - } - var version configVersionRow - if err := conn.Where("is_active = ?", true).Order("id desc").First(&version).Error; err != nil { - return nil, err - } - return &version, nil -} - func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload { payload.NodeID = strings.TrimSpace(payload.NodeID) payload.Version = strings.TrimSpace(payload.Version) diff --git a/internal/apps/openflare/flared/observability_test.go b/internal/apps/openflare/flared/observability_test.go index 94836bfe..bbdb5625 100644 --- a/internal/apps/openflare/flared/observability_test.go +++ b/internal/apps/openflare/flared/observability_test.go @@ -27,6 +27,7 @@ func setupFlaredObservabilityTestDB(t *testing.T) func() { &model.OpenFlareNode{}, &model.OpenFlareHealthEvent{}, &model.SystemConfig{}, + &model.ConfigVersion{}, )) db.SetDB(sqliteDB) diff --git a/internal/apps/openflare/integration/agent_protocol_test.go b/internal/apps/openflare/integration/agent_protocol_test.go index 3aa8ccf5..0e83d634 100644 --- a/internal/apps/openflare/integration/agent_protocol_test.go +++ b/internal/apps/openflare/integration/agent_protocol_test.go @@ -20,19 +20,6 @@ import ( "gorm.io/gorm" ) -type configVersionRecord struct { - ID uint `gorm:"primaryKey"` - Version string `gorm:"column:version"` - SnapshotJSON string `gorm:"column:snapshot_json"` - SupportFilesJSON string `gorm:"column:support_files_json"` - Checksum string `gorm:"column:checksum"` - IsActive bool `gorm:"column:is_active"` -} - -func (configVersionRecord) TableName() string { - return "of_config_versions" -} - func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) { t.Helper() @@ -46,7 +33,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) { &model.OpenFlareApplyLog{}, &model.OpenFlareNodeSystemProfile{}, &model.OpenFlareHealthEvent{}, - &configVersionRecord{}, + &model.ConfigVersion{}, )) db.SetDB(sqliteDB)