From d7fe4ef8be8eb8a6b2145ac9bc01de990621b6e1 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jun 2026 17:12:39 +0800 Subject: [PATCH] migrate --- Wavelet/internal/apps/openflare/node/errs.go | 1 + .../internal/apps/openflare/node/logics.go | 22 +++++++++--- .../apps/openflare/node/logics_test.go | 34 ++++++++++++++++++- docs/changelog/index.md | 1 + 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Wavelet/internal/apps/openflare/node/errs.go b/Wavelet/internal/apps/openflare/node/errs.go index 1715423a..512ce4c9 100644 --- a/Wavelet/internal/apps/openflare/node/errs.go +++ b/Wavelet/internal/apps/openflare/node/errs.go @@ -15,6 +15,7 @@ const ( errNodeIDConflict = "节点标识生成冲突,请重试" errNodeNotFound = "节点不存在" errNodeForceSyncFailed = "节点不在线或通过 WebSocket 发送同步指令失败" + errNoActiveConfigVersion = "当前没有激活版本" errAgentPreviewTagInvalid = "指定版本不是 preview 发布" errAgentStableTagInvalid = "正式版更新不能选择 preview 发布" ) diff --git a/Wavelet/internal/apps/openflare/node/logics.go b/Wavelet/internal/apps/openflare/node/logics.go index 552da996..96a614a0 100644 --- a/Wavelet/internal/apps/openflare/node/logics.go +++ b/Wavelet/internal/apps/openflare/node/logics.go @@ -12,7 +12,9 @@ import ( "github.com/Rain-kl/Wavelet/internal/apps/openflare/observability" "github.com/Rain-kl/Wavelet/internal/apps/openflare/option" + ofws "github.com/Rain-kl/Wavelet/internal/apps/openflare/websocket" "github.com/Rain-kl/Wavelet/internal/model" + "gorm.io/gorm" ) // Input is the create/update node payload. @@ -326,13 +328,23 @@ func RequestOpenrestyRestart(ctx context.Context, id uint) (*View, error) { return buildNodeView(node), nil } -// RequestForceSync requests a force sync via websocket (stub until T-AGENT). +// RequestForceSync pushes force_sync_config to a connected agent websocket. func RequestForceSync(ctx context.Context, id uint) (*View, error) { node, err := model.GetOpenFlareNodeByID(ctx, id) if err != nil { return nil, err } - if !sendAgentWSForceSyncConfig(node.NodeID) { + activeConfig, err := model.GetActiveConfigVersion(ctx) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("无法获取当前激活的配置版本:%s", errNoActiveConfigVersion) + } + return nil, fmt.Errorf("无法获取当前激活的配置版本:%v", err) + } + if !ofws.SendForceSyncConfig(node.NodeID, forceSyncConfigPayload{ + Version: activeConfig.Version, + Checksum: activeConfig.Checksum, + }) { return nil, errors.New(errNodeForceSyncFailed) } return buildNodeView(node), nil @@ -371,9 +383,9 @@ func ensureGlobalDiscoveryToken(ctx context.Context) (string, error) { return token, nil } -func sendAgentWSForceSyncConfig(nodeID string) bool { - _ = nodeID - return false +type forceSyncConfigPayload struct { + Version string `json:"version"` + Checksum string `json:"checksum"` } // ValidateDiscoveryToken validates the global discovery token. diff --git a/Wavelet/internal/apps/openflare/node/logics_test.go b/Wavelet/internal/apps/openflare/node/logics_test.go index dab0dfa4..db647b7f 100644 --- a/Wavelet/internal/apps/openflare/node/logics_test.go +++ b/Wavelet/internal/apps/openflare/node/logics_test.go @@ -244,10 +244,26 @@ func TestRequestOpenrestyRestart(t *testing.T) { assert.True(t, updated.RestartOpenrestyRequested) } -func TestRequestForceSyncStub(t *testing.T) { +func seedActiveConfigVersion(t *testing.T, ctx context.Context) { + t.Helper() + conn := db.DB(ctx) + require.NotNil(t, conn) + require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{})) + require.NoError(t, conn.Create(&model.ConfigVersion{ + Version: "20260618-001", + SnapshotJSON: `{}`, + RenderedConfig: `server {}`, + Checksum: "abc123", + IsActive: true, + CreatedBy: "test", + }).Error) +} + +func TestRequestForceSyncRequiresWebSocket(t *testing.T) { cleanup := setupNodeTestDB(t) defer cleanup() ctx := context.Background() + seedActiveConfigVersion(t, ctx) created, err := CreateNode(ctx, Input{Name: "edge-sync"}) require.NoError(t, err) @@ -257,6 +273,22 @@ func TestRequestForceSyncStub(t *testing.T) { assert.Equal(t, errNodeForceSyncFailed, err.Error()) } +func TestRequestForceSyncRequiresActiveConfig(t *testing.T) { + cleanup := setupNodeTestDB(t) + defer cleanup() + ctx := context.Background() + conn := db.DB(ctx) + require.NotNil(t, conn) + require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{})) + + created, err := CreateNode(ctx, Input{Name: "edge-sync-active"}) + require.NoError(t, err) + + _, err = RequestForceSync(ctx, created.ID) + require.Error(t, err) + assert.Contains(t, err.Error(), errNoActiveConfigVersion) +} + func TestGetObservabilityStub(t *testing.T) { cleanup := setupNodeTestDB(t) defer cleanup() diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 86caeea1..6195adf0 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -37,6 +37,7 @@ sidebar: false - 新增 `internal/apps/openflare/tasks/` 集中承载 OpenFlare 定时/后台任务(主进程 cron,非 Asynq),含数据库可观测性自动清理、WAF IP 组周期同步、UptimeKuma 同步、ACME 证书自动续期。 - 实装数据库可观测性手动/自动清理、WAF IP 组订阅/自动同步与测试接口、UptimeKuma 监控同步、TLS ACME 申请/续期(lego DNS-01)。 - 修复 Wavelet Agent WebSocket 未处理 `status` 消息导致 WS 模式下 `last_seen_at` 停止更新、节点超时显示离线的问题;列表「最近心跳」恢复显示「WS 已连接」。 +- 修复节点「强制同步」仍为 stub 导致始终返回「节点不在线或通过 WebSocket 发送同步指令失败」的问题。 ## [v2.3.4] - 2026-06-17