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
+49 -6
View File
@@ -247,14 +247,17 @@ const hasActiveRequests = computed(() => activeRequestIds.value.length > 0)
// 自动刷新定时器
let autoRefreshTimer: ReturnType<typeof setInterval> | null = null
let globalAutoRefreshTimer: ReturnType<typeof setInterval> | null = null
let refreshInFlight: Promise<void> | null = null
const AUTO_REFRESH_INTERVAL = 1000 // 1秒刷新一次(用于活跃请求)
const GLOBAL_AUTO_REFRESH_INTERVAL = 5000 // 5秒刷新一次(全局自动刷新)
const globalAutoRefresh = ref(false) // 全局自动刷新开关
const isPageVisible = ref(typeof document === 'undefined' ? true : !document.hidden)
// 轮询活跃请求状态(轻量级,只更新状态变化的记录)
let pollInFlight = false
async function pollActiveRequests() {
if (!isPageVisible.value) return
if (!hasActiveRequests.value) return
if (pollInFlight) return
pollInFlight = true
@@ -338,6 +341,7 @@ async function pollActiveRequests() {
// 启动自动刷新
function startAutoRefresh() {
if (!isPageVisible.value) return
if (autoRefreshTimer) return
autoRefreshTimer = setInterval(pollActiveRequests, AUTO_REFRESH_INTERVAL)
}
@@ -353,7 +357,7 @@ function stopAutoRefresh() {
// 监听活跃请求状态,自动启动/停止刷新
// 1秒轮询始终用于活跃请求的实时更新,不受全局刷新影响
watch(hasActiveRequests, (hasActive) => {
if (hasActive) {
if (hasActive && isPageVisible.value) {
startAutoRefresh()
} else {
stopAutoRefresh()
@@ -362,6 +366,7 @@ watch(hasActiveRequests, (hasActive) => {
// 启动全局自动刷新
function startGlobalAutoRefresh() {
if (!isPageVisible.value) return
if (globalAutoRefreshTimer) return
globalAutoRefreshTimer = setInterval(refreshData, GLOBAL_AUTO_REFRESH_INTERVAL)
}
@@ -378,15 +383,34 @@ function stopGlobalAutoRefresh() {
function handleAutoRefreshChange(value: boolean) {
globalAutoRefresh.value = value
if (value) {
refreshData() // 立即刷新一次
if (isPageVisible.value) {
refreshData() // 立即刷新一次
}
startGlobalAutoRefresh()
} else {
stopGlobalAutoRefresh()
}
}
function handleVisibilityChange() {
isPageVisible.value = !document.hidden
if (!isPageVisible.value) {
stopAutoRefresh()
stopGlobalAutoRefresh()
return
}
if (hasActiveRequests.value) {
startAutoRefresh()
}
if (globalAutoRefresh.value) {
refreshData()
startGlobalAutoRefresh()
}
}
// 组件卸载时清理定时器
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
stopAutoRefresh()
stopGlobalAutoRefresh()
})
@@ -419,6 +443,8 @@ const selectedRequestId = ref<string | null>(null)
// 初始化加载
onMounted(async () => {
document.addEventListener('visibilitychange', handleVisibilityChange)
// 所有数据源并行加载(stats/heatmap/records/users 之间没有数据依赖)
const statsTask = loadStats(timeRange.value).catch(err => {
log.error('加载统计数据失败:', err)
@@ -548,11 +574,28 @@ async function handleFilterStatusChange(value: string) {
// 刷新数据
async function refreshData() {
await loadStats(timeRange.value)
if (isAdminPage.value) {
await loadRecords({ page: currentPage.value, pageSize: pageSize.value }, getCurrentFilters())
if (!isPageVisible.value) return
if (refreshInFlight) return refreshInFlight
refreshInFlight = (async () => {
if (isAdminPage.value) {
// loadStats 会同步更新 currentDateRange,随后 loadRecords 复用同一时间范围
await Promise.all([
loadStats(timeRange.value),
loadRecords({ page: currentPage.value, pageSize: pageSize.value }, getCurrentFilters())
])
return
}
await loadStats(timeRange.value)
// 用户页面:loadStats 已包含记录加载
})()
try {
await refreshInFlight
} finally {
refreshInFlight = null
}
// 用户页面:loadStats 已包含记录加载
}
// 显示请求详情