From 491a744481b51bedc9f74cc93247edf56a4774c7 Mon Sep 17 00:00:00 2001 From: shaw Date: Fri, 6 Mar 2026 10:23:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=A6=96=E6=AC=A1=E5=8A=A0=E8=BD=BD=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E8=B4=B9=E7=94=A8=E6=98=BE=E7=A4=BA=20$0.00?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lite 模式下从快照缓存读取窗口费用,非 lite 模式查询后写入缓存 --- .../internal/handler/admin/account_handler.go | 64 ++++++++++++------- .../admin/account_window_cost_cache.go | 25 ++++++++ 2 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 backend/internal/handler/admin/account_window_cost_cache.go diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 23ebc8e6..f42159a8 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -288,32 +288,48 @@ func (h *AccountHandler) List(c *gin.Context) { } } - // 仅非 lite 模式获取窗口费用(PostgreSQL 聚合查询,高开销) - if !lite && len(windowCostAccountIDs) > 0 { - windowCosts = make(map[int64]float64) - var mu sync.Mutex - g, gctx := errgroup.WithContext(c.Request.Context()) - g.SetLimit(10) // 限制并发数 - - for i := range accounts { - acc := &accounts[i] - if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 { - continue - } - accCopy := acc // 闭包捕获 - g.Go(func() error { - // 使用统一的窗口开始时间计算逻辑(考虑窗口过期情况) - startTime := accCopy.GetCurrentWindowStartTime() - stats, err := h.accountUsageService.GetAccountWindowStats(gctx, accCopy.ID, startTime) - if err == nil && stats != nil { - mu.Lock() - windowCosts[accCopy.ID] = stats.StandardCost // 使用标准费用 - mu.Unlock() + // 窗口费用获取:lite 模式从快照缓存读取,非 lite 模式执行 PostgreSQL 查询后写入缓存 + if len(windowCostAccountIDs) > 0 { + if lite { + // lite 模式:尝试从快照缓存读取 + cacheKey := buildWindowCostCacheKey(windowCostAccountIDs) + if cached, ok := accountWindowCostCache.Get(cacheKey); ok { + if costs, ok := cached.Payload.(map[int64]float64); ok { + windowCosts = costs } - return nil // 不返回错误,允许部分失败 - }) + } + // 缓存未命中则 windowCosts 保持 nil(仅发生在服务刚启动时) + } else { + // 非 lite 模式:执行 PostgreSQL 聚合查询(高开销) + windowCosts = make(map[int64]float64) + var mu sync.Mutex + g, gctx := errgroup.WithContext(c.Request.Context()) + g.SetLimit(10) // 限制并发数 + + for i := range accounts { + acc := &accounts[i] + if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 { + continue + } + accCopy := acc // 闭包捕获 + g.Go(func() error { + // 使用统一的窗口开始时间计算逻辑(考虑窗口过期情况) + startTime := accCopy.GetCurrentWindowStartTime() + stats, err := h.accountUsageService.GetAccountWindowStats(gctx, accCopy.ID, startTime) + if err == nil && stats != nil { + mu.Lock() + windowCosts[accCopy.ID] = stats.StandardCost // 使用标准费用 + mu.Unlock() + } + return nil // 不返回错误,允许部分失败 + }) + } + _ = g.Wait() + + // 查询完毕后写入快照缓存,供 lite 模式使用 + cacheKey := buildWindowCostCacheKey(windowCostAccountIDs) + accountWindowCostCache.Set(cacheKey, windowCosts) } - _ = g.Wait() } // Build response with concurrency info diff --git a/backend/internal/handler/admin/account_window_cost_cache.go b/backend/internal/handler/admin/account_window_cost_cache.go new file mode 100644 index 00000000..3271b630 --- /dev/null +++ b/backend/internal/handler/admin/account_window_cost_cache.go @@ -0,0 +1,25 @@ +package admin + +import ( + "strconv" + "strings" + "time" +) + +var accountWindowCostCache = newSnapshotCache(30 * time.Second) + +func buildWindowCostCacheKey(accountIDs []int64) string { + if len(accountIDs) == 0 { + return "accounts_window_cost_empty" + } + var b strings.Builder + b.Grow(len(accountIDs) * 6) + _, _ = b.WriteString("accounts_window_cost:") + for i, id := range accountIDs { + if i > 0 { + _ = b.WriteByte(',') + } + _, _ = b.WriteString(strconv.FormatInt(id, 10)) + } + return b.String() +}