Merge pull request #813 from FizzlyCode/fix/account-usage-display

fix: 修复账号列表五小时用量显示为 $0.00 的问题
This commit is contained in:
Wesley Liddick
2026-03-06 17:25:03 +08:00
committed by GitHub
3 changed files with 28 additions and 63 deletions

View File

@@ -288,48 +288,32 @@ func (h *AccountHandler) List(c *gin.Context) {
} }
} }
// 窗口费用获取lite 模式从快照缓存读取,非 lite 模式执行 PostgreSQL 查询后写入缓存 // 始终获取窗口费用(PostgreSQL 聚合查询)
if len(windowCostAccountIDs) > 0 { if len(windowCostAccountIDs) > 0 {
if lite { windowCosts = make(map[int64]float64)
// lite 模式:尝试从快照缓存读取 var mu sync.Mutex
cacheKey := buildWindowCostCacheKey(windowCostAccountIDs) g, gctx := errgroup.WithContext(c.Request.Context())
if cached, ok := accountWindowCostCache.Get(cacheKey); ok { g.SetLimit(10) // 限制并发数
if costs, ok := cached.Payload.(map[int64]float64); ok {
windowCosts = costs
}
}
// 缓存未命中则 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 { for i := range accounts {
acc := &accounts[i] acc := &accounts[i]
if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 { if !acc.IsAnthropicOAuthOrSetupToken() || acc.GetWindowCostLimit() <= 0 {
continue 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() accCopy := acc // 闭包捕获
g.Go(func() error {
// 查询完毕后写入快照缓存,供 lite 模式使用 // 使用统一的窗口开始时间计算逻辑(考虑窗口过期情况)
cacheKey := buildWindowCostCacheKey(windowCostAccountIDs) startTime := accCopy.GetCurrentWindowStartTime()
accountWindowCostCache.Set(cacheKey, windowCosts) 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()
} }
// Build response with concurrency info // Build response with concurrency info

View File

@@ -1,25 +0,0 @@
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()
}

View File

@@ -2640,6 +2640,12 @@ export default {
allProtocols: '全部协议', allProtocols: '全部协议',
allStatus: '全部状态', allStatus: '全部状态',
searchProxies: '搜索代理...', searchProxies: '搜索代理...',
protocols: {
http: 'HTTP',
https: 'HTTPS',
socks5: 'SOCKS5',
socks5h: 'SOCKS5H (远程 DNS)',
},
name: '名称', name: '名称',
protocol: '协议', protocol: '协议',
host: '主机', host: '主机',