mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(usage): session touch 独立提交避免行锁阻塞 & 管理员页面顺序加载降低并发压力
后端: 将 session touch 的 commit 从请求事务中分离,防止管理员 usage 页面的长查询持有 user_sessions 行锁阻塞后续请求。touch_session 改为 返回 bool 以支持按需提交。 前端: 管理员 Usage 页面将并行 API 调用改为顺序加载,优先显示记录表格, 统计面板在后台异步刷新,避免瞬时并发打满后端 worker。loadRecords 支持 传入 dateRange 参数确保时间范围一致性。
This commit is contained in:
@@ -77,13 +77,8 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
|
||||
try {
|
||||
if (isAdminPage.value) {
|
||||
// 管理员页面,并行加载统计数据
|
||||
const [statsData, modelData, providerData, apiFormatData] = await Promise.all([
|
||||
usageApi.getUsageStats(dateRange),
|
||||
usageApi.getUsageByModel(dateRange),
|
||||
usageApi.getUsageByProvider(dateRange),
|
||||
usageApi.getUsageByApiFormat(dateRange)
|
||||
])
|
||||
// 管理员页面顺序加载统计数据,避免刷新使用记录时瞬时打满后端 worker。
|
||||
const statsData = await usageApi.getUsageStats(dateRange)
|
||||
|
||||
if (requestId !== loadStatsRequestId) {
|
||||
return
|
||||
@@ -104,6 +99,11 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
period_end: '',
|
||||
}
|
||||
|
||||
const modelData = await usageApi.getUsageByModel(dateRange)
|
||||
if (requestId !== loadStatsRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
modelStats.value = modelData.map(item => {
|
||||
const raw = item as Record<string, unknown>
|
||||
return {
|
||||
@@ -120,6 +120,11 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
}
|
||||
})
|
||||
|
||||
const providerData = await usageApi.getUsageByProvider(dateRange)
|
||||
if (requestId !== loadStatsRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
providerStats.value = providerData.map(item => ({
|
||||
provider: item.provider,
|
||||
requests: item.request_count,
|
||||
@@ -137,6 +142,11 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
: '-'
|
||||
}))
|
||||
|
||||
const apiFormatData = await usageApi.getUsageByApiFormat(dateRange)
|
||||
if (requestId !== loadStatsRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
apiFormatStats.value = apiFormatData.map(item => ({
|
||||
api_format: item.api_format,
|
||||
request_count: item.request_count || 0,
|
||||
@@ -255,19 +265,24 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
// 加载记录(真正的后端分页)
|
||||
async function loadRecords(
|
||||
pagination: PaginationParams,
|
||||
filters?: FilterParams
|
||||
filters?: FilterParams,
|
||||
dateRange?: DateRangeParams
|
||||
): Promise<void> {
|
||||
const requestId = ++loadRecordsRequestId
|
||||
isLoadingRecords.value = true
|
||||
|
||||
try {
|
||||
const offset = (pagination.page - 1) * pagination.pageSize
|
||||
const effectiveDateRange = dateRange ?? currentDateRange.value
|
||||
if (dateRange) {
|
||||
currentDateRange.value = dateRange
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
const params: Record<string, unknown> = {
|
||||
limit: pagination.pageSize,
|
||||
offset,
|
||||
...currentDateRange.value
|
||||
...effectiveDateRange
|
||||
}
|
||||
|
||||
// 添加筛选条件
|
||||
|
||||
@@ -196,6 +196,9 @@ const {
|
||||
const activityHeatmapData = ref<ActivityHeatmap | null>(null)
|
||||
const isLoadingHeatmap = ref(false)
|
||||
const heatmapError = ref(false)
|
||||
const ADMIN_ANALYTICS_REFRESH_INTERVAL = 60000
|
||||
let adminAnalyticsRefreshInFlight: Promise<void> | null = null
|
||||
let lastAdminAnalyticsRefreshAt = 0
|
||||
|
||||
// 加载热力图数据
|
||||
async function loadHeatmapData() {
|
||||
@@ -215,6 +218,57 @@ async function loadHeatmapData() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAdminUsers() {
|
||||
try {
|
||||
const users = await usersApi.getAllUsers()
|
||||
availableUsers.value = users.map(u => ({ id: u.id, username: u.username, email: u.email }))
|
||||
} catch (error) {
|
||||
log.error('加载用户列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAdminAnalytics(options: { force?: boolean } = {}) {
|
||||
if (!isAdminPage.value) return
|
||||
if (!options.force && !isPageVisible.value) return
|
||||
|
||||
const now = Date.now()
|
||||
if (!options.force && now - lastAdminAnalyticsRefreshAt < ADMIN_ANALYTICS_REFRESH_INTERVAL) {
|
||||
return
|
||||
}
|
||||
if (adminAnalyticsRefreshInFlight) {
|
||||
return adminAnalyticsRefreshInFlight
|
||||
}
|
||||
|
||||
adminAnalyticsRefreshInFlight = (async () => {
|
||||
let hasSuccessfulRefresh = false
|
||||
|
||||
try {
|
||||
await loadStats(timeRange.value)
|
||||
hasSuccessfulRefresh = true
|
||||
} catch (error) {
|
||||
log.error('加载统计数据失败:', error)
|
||||
warning('统计数据加载失败,请刷新重试')
|
||||
}
|
||||
|
||||
try {
|
||||
await loadHeatmapData()
|
||||
hasSuccessfulRefresh = true
|
||||
} catch (error) {
|
||||
log.error('加载热力图数据失败:', error)
|
||||
}
|
||||
|
||||
if (hasSuccessfulRefresh) {
|
||||
lastAdminAnalyticsRefreshAt = Date.now()
|
||||
}
|
||||
})()
|
||||
|
||||
try {
|
||||
await adminAnalyticsRefreshInFlight
|
||||
} finally {
|
||||
adminAnalyticsRefreshInFlight = null
|
||||
}
|
||||
}
|
||||
|
||||
// 用户页面需要前端筛选
|
||||
const filteredRecords = computed(() => {
|
||||
if (!isAdminPage.value) {
|
||||
@@ -488,32 +542,29 @@ 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)
|
||||
warning('统计数据加载失败,请刷新重试')
|
||||
})
|
||||
const heatmapTask = loadHeatmapData().catch(err => {
|
||||
log.error('加载热力图数据失败:', err)
|
||||
})
|
||||
|
||||
const tasks: Promise<unknown>[] = [statsTask, heatmapTask]
|
||||
|
||||
if (isAdminPage.value) {
|
||||
// 管理员页面:stats 和 records 分开加载(后端分页)
|
||||
tasks.push(loadRecords(
|
||||
// 管理员页面优先加载记录,统计面板在后台顺序刷新,避免瞬时并发打满后端。
|
||||
await loadRecords(
|
||||
{ page: currentPage.value, pageSize: pageSize.value },
|
||||
getCurrentFilters()
|
||||
))
|
||||
tasks.push(
|
||||
usersApi.getAllUsers().then(users => {
|
||||
availableUsers.value = users.map(u => ({ id: u.id, username: u.username, email: u.email }))
|
||||
})
|
||||
getCurrentFilters(),
|
||||
timeRange.value
|
||||
)
|
||||
void (async () => {
|
||||
await refreshAdminAnalytics({ force: true })
|
||||
await loadAdminUsers()
|
||||
})()
|
||||
} else {
|
||||
// 用户页面:loadStats 已包含记录加载,不需要单独调用 loadRecords
|
||||
await Promise.allSettled([
|
||||
loadStats(timeRange.value).catch(err => {
|
||||
log.error('加载统计数据失败:', err)
|
||||
warning('统计数据加载失败,请刷新重试')
|
||||
}),
|
||||
loadHeatmapData().catch(err => {
|
||||
log.error('加载热力图数据失败:', err)
|
||||
})
|
||||
])
|
||||
}
|
||||
// 用户页面:loadStats 已包含记录加载,不需要单独调用 loadRecords
|
||||
|
||||
await Promise.allSettled(tasks)
|
||||
|
||||
if (globalAutoRefresh.value && isPageVisible.value) {
|
||||
startGlobalAutoRefresh()
|
||||
@@ -524,10 +575,12 @@ onMounted(async () => {
|
||||
async function handleTimeRangeChange(value: DateRangeParams) {
|
||||
timeRange.value = value
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
await loadStats(timeRange.value)
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
await refreshAdminAnalytics({ force: true })
|
||||
return
|
||||
}
|
||||
await loadStats(timeRange.value)
|
||||
// 用户页面:loadStats 已包含记录加载
|
||||
}
|
||||
|
||||
@@ -535,7 +588,7 @@ async function handleTimeRangeChange(value: DateRangeParams) {
|
||||
async function handlePageChange(page: number) {
|
||||
currentPage.value = page
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
// 用户页面使用前端分页,无需重新请求
|
||||
}
|
||||
@@ -545,7 +598,7 @@ async function handlePageSizeChange(size: number) {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: size }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: size }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
// 用户页面使用前端分页,无需重新请求
|
||||
}
|
||||
@@ -568,7 +621,7 @@ async function handleFilterSearchChange(value: string) {
|
||||
currentPage.value = 1
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
// 用户页面:search 需要重新从后端拉取数据(后端支持 search 参数)
|
||||
// 但通过 filteredRecords 做前端过滤已覆盖,无需额外请求
|
||||
@@ -579,7 +632,7 @@ async function handleFilterUserChange(value: string) {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,7 +641,7 @@ async function handleFilterModelChange(value: string) {
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +650,7 @@ async function handleFilterProviderChange(value: string) {
|
||||
currentPage.value = 1
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,7 +659,7 @@ async function handleFilterApiFormatChange(value: string) {
|
||||
currentPage.value = 1
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,7 +668,7 @@ async function handleFilterStatusChange(value: string) {
|
||||
currentPage.value = 1
|
||||
|
||||
if (isAdminPage.value) {
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters())
|
||||
await loadRecords({ page: 1, pageSize: pageSize.value }, getCurrentFilters(), timeRange.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,11 +679,12 @@ async function refreshData() {
|
||||
|
||||
refreshInFlight = (async () => {
|
||||
if (isAdminPage.value) {
|
||||
// loadStats 会同步更新 currentDateRange,随后 loadRecords 复用同一时间范围
|
||||
await Promise.all([
|
||||
loadStats(timeRange.value),
|
||||
loadRecords({ page: currentPage.value, pageSize: pageSize.value }, getCurrentFilters())
|
||||
])
|
||||
await loadRecords(
|
||||
{ page: currentPage.value, pageSize: pageSize.value },
|
||||
getCurrentFilters(),
|
||||
timeRange.value
|
||||
)
|
||||
void refreshAdminAnalytics()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user