perf: 全栈查询优化、前端缓存去重与页面可见性优化

后端:
- SQL count 查询统一改用 func.count() 子查询替代 query.count()
- Dashboard/Audit 等页面多次独立查询合并为单次聚合查询
- Provider summary 列表改为批量查询消除 N+1 问题
- DailyStats 逐天循环查询改为 CASE 分桶单次查询
- 使用 load_only() 减少不必要的列加载
- cache_decorator 支持嵌套属性路径解析(dotted vary_by)
- 多个管理/公共端点新增 @cache_result 缓存装饰

前端:
- cache.ts 新增 in-flight 请求复用、dedupedRequest、buildCacheKey
- 大量 API 调用添加前端缓存或去重
- 多个页面定时器在标签页隐藏时暂停、可见时恢复
- Auth 检查从 setInterval 改为 storage + visibilitychange 事件驱动
- 请求竞态防护(requestId 模式)

数据库:
- Usage 表新增 idx_usage_status_user_created 复合索引
This commit is contained in:
fawney19
2026-03-03 22:04:40 +08:00
parent 0a60492146
commit 97b0146ce9
66 changed files with 2306 additions and 657 deletions
@@ -52,6 +52,8 @@ export function useUsageData(options: UseUsageDataOptions) {
// 当前的日期范围(用于分页请求)
const currentDateRange = ref<DateRangeParams | undefined>(undefined)
let loadStatsRequestId = 0
let loadRecordsRequestId = 0
// 可用的筛选选项(从统计数据获取,而不是从记录中)
const availableModels = ref<string[]>([])
@@ -69,6 +71,7 @@ export function useUsageData(options: UseUsageDataOptions) {
// 加载统计数据(不加载记录)
async function loadStats(dateRange?: DateRangeParams) {
const requestId = ++loadStatsRequestId
isLoadingStats.value = true
currentDateRange.value = dateRange
@@ -82,6 +85,10 @@ export function useUsageData(options: UseUsageDataOptions) {
usageApi.getUsageByApiFormat(dateRange)
])
if (requestId !== loadStatsRequestId) {
return
}
// statsData may contain additional fields not declared in UsageStats
const statsRaw = statsData as Record<string, unknown>
stats.value = {
@@ -138,6 +145,9 @@ export function useUsageData(options: UseUsageDataOptions) {
} else {
// 用户页面
const userData = await meApi.getUsage(dateRange)
if (requestId !== loadStatsRequestId) {
return
}
stats.value = {
total_requests: userData.total_requests || 0,
@@ -227,6 +237,9 @@ export function useUsageData(options: UseUsageDataOptions) {
.sort((a, b) => b.request_count - a.request_count)
}
} catch (error: unknown) {
if (requestId !== loadStatsRequestId) {
return
}
if (getErrorStatus(error) !== 403) {
log.error('加载统计数据失败:', error)
}
@@ -234,7 +247,9 @@ export function useUsageData(options: UseUsageDataOptions) {
modelStats.value = []
currentRecords.value = []
} finally {
isLoadingStats.value = false
if (requestId === loadStatsRequestId) {
isLoadingStats.value = false
}
}
}
@@ -243,6 +258,7 @@ export function useUsageData(options: UseUsageDataOptions) {
pagination: PaginationParams,
filters?: FilterParams
): Promise<void> {
const requestId = ++loadRecordsRequestId
isLoadingRecords.value = true
try {
@@ -279,22 +295,33 @@ export function useUsageData(options: UseUsageDataOptions) {
}
const response = await usageApi.getAllUsageRecords(params)
if (requestId !== loadRecordsRequestId) {
return
}
const nextRecords = (response.records || []) as UsageRecord[]
currentRecords.value = mergeRecordStatus(currentRecords.value, nextRecords)
totalRecords.value = response.total || 0
} else {
// 用户页面:使用用户 API
const userData = await meApi.getUsage(params)
if (requestId !== loadRecordsRequestId) {
return
}
const nextRecords = (userData.records || []) as UsageRecord[]
currentRecords.value = mergeRecordStatus(currentRecords.value, nextRecords)
totalRecords.value = userData.pagination?.total || currentRecords.value.length
}
} catch (error) {
if (requestId !== loadRecordsRequestId) {
return
}
log.error('加载记录失败:', error)
currentRecords.value = []
totalRecords.value = 0
} finally {
isLoadingRecords.value = false
if (requestId === loadRecordsRequestId) {
isLoadingRecords.value = false
}
}
}