From 0964d9014b8aa86f9e9b59da114dd8072fb95f6d Mon Sep 17 00:00:00 2001 From: qian Date: Fri, 28 Aug 2026 00:29:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(update):=E4=BF=AE=E5=A4=8D=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF=E6=9B=B4=E6=96=B0=E6=A3=80=E6=B5=8B=E5=90=8E=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E9=97=AE=E9=A2=98=E5=AF=BC=E8=87=B4=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logic/admin/update.go | 4 +++- internal/logic/update/checker.go | 20 ++++++++++++++++++-- internal/logic/update/checker_test.go | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/internal/logic/admin/update.go b/internal/logic/admin/update.go index d488e55..9d6c6cc 100644 --- a/internal/logic/admin/update.go +++ b/internal/logic/admin/update.go @@ -115,7 +115,9 @@ func (s *sAdmin) UpdateExecute(ctx context.Context, req *v1.UpdateExecuteReq) (* // Get check result to find download URL cached := update.GetManager().GetCheckResult() - if cached == nil { + // 缓存里有更新但没匹配到平台安装包时强制重查,而不是直接报「没有可用的更新包」: + // 典型场景是发行版刚发布、CI 尚未上传资产窗口期的检查结果被缓存,资产几分钟后就位 + if cached == nil || update.HasUpdateButNoAsset(cached) { // Force a check first result, err := update.CheckForUpdate(ctx, true) if err != nil { diff --git a/internal/logic/update/checker.go b/internal/logic/update/checker.go index be0756b..97af3b8 100644 --- a/internal/logic/update/checker.go +++ b/internal/logic/update/checker.go @@ -114,8 +114,11 @@ func CheckForUpdate(ctx context.Context, force bool) (*CheckResult, error) { // Store ETag for conditional requests manager.lastETag.Store(etag) - // Cache result - setCheckCache(ctx, result) + // Cache result。「有更新但没匹配到平台安装包」的结果不写 Redis:TTL 24h 太长, + // 会挡住发布窗口期之后的检查拿到完整资产列表(内存结果照常存,供状态展示) + if !HasUpdateButNoAsset(result) { + setCheckCache(ctx, result) + } manager.checkResult.Store(result) return result, nil @@ -185,6 +188,14 @@ func fetchLatestRelease(ctx context.Context, conditional bool) (*GitHubRelease, return &release, etag, nil } +// HasUpdateButNoAsset 判断检查结果是否为「有更新但当前平台没匹配到安装包」。 +// 典型成因:发行版刚发布、CI 尚未上传资产的窗口期拉到的结果(资产几分钟后才就位)。 +// 这类结果不可信:不能写缓存,已入库的脏缓存读到后按无效处理, +// 执行更新时则强制重查,而不是直接报「没有可用的更新包」。 +func HasUpdateButNoAsset(r *CheckResult) bool { + return r != nil && r.HasUpdate && r.DownloadURL == "" +} + // getCheckCache reads cached check result from Redis. // 缓存是纯优化(TTL 24h),Redis 未配置/不可用时应降级为无缓存、走实时检查, // 绝不能因 g.Redis() 客户端创建失败而 panic(见 goframe-conventions.md 已修复记录)。 @@ -205,6 +216,11 @@ func getCheckCache(ctx context.Context) (out *CheckResult) { if err := json.Unmarshal(val.Bytes(), &result); err != nil { return nil } + // 「有更新但没匹配到平台安装包」的脏缓存(发布窗口期写入,含旧版本遗留数据)按无效处理, + // 让调用方 fallthrough 到实时检查拿完整资产列表 + if HasUpdateButNoAsset(&result) { + return nil + } return &result } diff --git a/internal/logic/update/checker_test.go b/internal/logic/update/checker_test.go index 4619cfd..249e414 100644 --- a/internal/logic/update/checker_test.go +++ b/internal/logic/update/checker_test.go @@ -46,6 +46,27 @@ func TestCompareVersions(t *testing.T) { } } +func TestHasUpdateButNoAsset(t *testing.T) { + cases := []struct { + name string + result *CheckResult + want bool + }{ + {"nil 结果", nil, false}, + {"有更新且有安装包", &CheckResult{HasUpdate: true, DownloadURL: "https://example.com/team-api-0.2.14-linux-amd64.tar.gz"}, false}, + {"无更新且无安装包", &CheckResult{HasUpdate: false, DownloadURL: ""}, false}, + {"有更新但缺安装包(发布窗口期脏数据)", &CheckResult{HasUpdate: true, DownloadURL: ""}, true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := HasUpdateButNoAsset(c.result); got != c.want { + t.Errorf("HasUpdateButNoAsset(%+v) = %v, want %v", c.result, got, c.want) + } + }) + } +} + func TestNormalizeSemver(t *testing.T) { cases := []struct{ in, want string }{ {"0.2.0_39", "0.2.0+39"}, // Makefile git describe 版本:下划线转构建元数据