perf(frontend): add api cache layer and reduce report polling pressure
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
export async function fetchBrowserPoolStats() {
|
||||
const browserPoolStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/browser_pool/stats')
|
||||
return data
|
||||
}
|
||||
}, 4_000)
|
||||
|
||||
export async function fetchBrowserPoolStats(options = {}) {
|
||||
return browserPoolStatsGetter.run(options)
|
||||
}
|
||||
|
||||
46
admin-frontend/src/api/cache.js
Normal file
46
admin-frontend/src/api/cache.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export function createCachedGetter(fetcher, ttlMs = 0) {
|
||||
let hasValue = false
|
||||
let cachedValue = null
|
||||
let expiresAt = 0
|
||||
let inflight = null
|
||||
|
||||
async function run(options = {}) {
|
||||
const force = Boolean(options?.force)
|
||||
const now = Date.now()
|
||||
|
||||
if (!force && hasValue && now < expiresAt) {
|
||||
return cachedValue
|
||||
}
|
||||
|
||||
if (!force && inflight) {
|
||||
return inflight
|
||||
}
|
||||
|
||||
inflight = Promise.resolve()
|
||||
.then(() => fetcher())
|
||||
.then((data) => {
|
||||
cachedValue = data
|
||||
hasValue = true
|
||||
const ttl = Math.max(0, Number(ttlMs) || 0)
|
||||
expiresAt = Date.now() + ttl
|
||||
return data
|
||||
})
|
||||
.finally(() => {
|
||||
inflight = null
|
||||
})
|
||||
|
||||
return inflight
|
||||
}
|
||||
|
||||
function clear() {
|
||||
hasValue = false
|
||||
cachedValue = null
|
||||
expiresAt = 0
|
||||
inflight = null
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
clear,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
const emailStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/email/stats')
|
||||
return data
|
||||
}, 10_000)
|
||||
|
||||
export async function fetchEmailSettings() {
|
||||
const { data } = await api.get('/email/settings')
|
||||
@@ -7,12 +13,12 @@ export async function fetchEmailSettings() {
|
||||
|
||||
export async function updateEmailSettings(payload) {
|
||||
const { data } = await api.post('/email/settings', payload)
|
||||
emailStatsGetter.clear()
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchEmailStats() {
|
||||
const { data } = await api.get('/email/stats')
|
||||
return data
|
||||
export async function fetchEmailStats(options = {}) {
|
||||
return emailStatsGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchEmailLogs(params) {
|
||||
@@ -22,6 +28,6 @@ export async function fetchEmailLogs(params) {
|
||||
|
||||
export async function cleanupEmailLogs(days) {
|
||||
const { data } = await api.post('/email/logs/cleanup', { days })
|
||||
emailStatsGetter.clear()
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
let _feedbackStatsInflight = null
|
||||
const FEEDBACK_STATS_TTL_MS = 10_000
|
||||
|
||||
const feedbackStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/feedbacks', { params: { limit: 1, offset: 0 } })
|
||||
return data?.stats
|
||||
}, FEEDBACK_STATS_TTL_MS)
|
||||
|
||||
export async function fetchFeedbacks(status = '') {
|
||||
const { data } = await api.get('/feedbacks', { params: status ? { status } : {} })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchFeedbackStats() {
|
||||
if (_feedbackStatsInflight) return _feedbackStatsInflight
|
||||
export async function fetchFeedbackStats(options = {}) {
|
||||
return feedbackStatsGetter.run(options)
|
||||
}
|
||||
|
||||
_feedbackStatsInflight = api
|
||||
.get('/feedbacks', { params: { limit: 1, offset: 0 } })
|
||||
.then(({ data }) => data?.stats)
|
||||
.finally(() => {
|
||||
_feedbackStatsInflight = null
|
||||
})
|
||||
|
||||
return _feedbackStatsInflight
|
||||
export function clearFeedbackStatsCache() {
|
||||
feedbackStatsGetter.clear()
|
||||
}
|
||||
|
||||
export async function replyFeedback(feedbackId, reply) {
|
||||
const { data } = await api.post(`/feedbacks/${feedbackId}/reply`, { reply })
|
||||
clearFeedbackStatsCache()
|
||||
return data
|
||||
}
|
||||
|
||||
export async function closeFeedback(feedbackId) {
|
||||
const { data } = await api.post(`/feedbacks/${feedbackId}/close`)
|
||||
clearFeedbackStatsCache()
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteFeedback(feedbackId) {
|
||||
const { data } = await api.delete(`/feedbacks/${feedbackId}`)
|
||||
clearFeedbackStatsCache()
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
const _statsCache = {
|
||||
value: null,
|
||||
expiresAt: 0,
|
||||
inflight: null,
|
||||
}
|
||||
const SYSTEM_STATS_TTL_MS = 15_000
|
||||
|
||||
const SYSTEM_STATS_TTL_MS = 2000
|
||||
const systemStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/stats')
|
||||
return data
|
||||
}, SYSTEM_STATS_TTL_MS)
|
||||
|
||||
export async function fetchSystemStats(options = {}) {
|
||||
const force = Boolean(options?.force)
|
||||
const now = Date.now()
|
||||
|
||||
if (!force && _statsCache.value && now < _statsCache.expiresAt) {
|
||||
return _statsCache.value
|
||||
}
|
||||
|
||||
if (!force && _statsCache.inflight) {
|
||||
return _statsCache.inflight
|
||||
}
|
||||
|
||||
const request = api
|
||||
.get('/stats')
|
||||
.then(({ data }) => {
|
||||
_statsCache.value = data
|
||||
_statsCache.expiresAt = Date.now() + SYSTEM_STATS_TTL_MS
|
||||
return data
|
||||
})
|
||||
.finally(() => {
|
||||
_statsCache.inflight = null
|
||||
})
|
||||
|
||||
_statsCache.inflight = request
|
||||
return request
|
||||
return systemStatsGetter.run(options)
|
||||
}
|
||||
|
||||
export function clearSystemStatsCache() {
|
||||
_statsCache.value = null
|
||||
_statsCache.expiresAt = 0
|
||||
_statsCache.inflight = null
|
||||
systemStatsGetter.clear()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
export async function fetchSystemConfig() {
|
||||
const systemConfigGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/system/config')
|
||||
return data
|
||||
}, 15_000)
|
||||
|
||||
export async function fetchSystemConfig(options = {}) {
|
||||
return systemConfigGetter.run(options)
|
||||
}
|
||||
|
||||
export async function updateSystemConfig(payload) {
|
||||
const { data } = await api.post('/system/config', payload)
|
||||
systemConfigGetter.clear()
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -14,4 +20,3 @@ export async function executeScheduleNow() {
|
||||
const { data } = await api.post('/schedule/execute', {})
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,58 @@
|
||||
import { api } from './client'
|
||||
import { createCachedGetter } from './cache'
|
||||
|
||||
export async function fetchServerInfo() {
|
||||
const serverInfoGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/server/info')
|
||||
return data
|
||||
}
|
||||
}, 30_000)
|
||||
|
||||
export async function fetchDockerStats() {
|
||||
const dockerStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/docker_stats')
|
||||
return data
|
||||
}
|
||||
}, 8_000)
|
||||
|
||||
export async function fetchRequestMetrics() {
|
||||
const requestMetricsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/request_metrics')
|
||||
return data
|
||||
}
|
||||
}, 10_000)
|
||||
|
||||
export async function fetchSlowSqlMetrics() {
|
||||
const slowSqlMetricsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/slow_sql_metrics')
|
||||
return data
|
||||
}
|
||||
}, 10_000)
|
||||
|
||||
export async function fetchTaskStats() {
|
||||
const taskStatsGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/task/stats')
|
||||
return data
|
||||
}
|
||||
}, 4_000)
|
||||
|
||||
export async function fetchRunningTasks() {
|
||||
const runningTasksGetter = createCachedGetter(async () => {
|
||||
const { data } = await api.get('/task/running')
|
||||
return data
|
||||
}, 2_000)
|
||||
|
||||
export async function fetchServerInfo(options = {}) {
|
||||
return serverInfoGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchDockerStats(options = {}) {
|
||||
return dockerStatsGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchRequestMetrics(options = {}) {
|
||||
return requestMetricsGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchSlowSqlMetrics(options = {}) {
|
||||
return slowSqlMetricsGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchTaskStats(options = {}) {
|
||||
return taskStatsGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchRunningTasks(options = {}) {
|
||||
return runningTasksGetter.run(options)
|
||||
}
|
||||
|
||||
export async function fetchTaskLogs(params) {
|
||||
@@ -37,5 +62,7 @@ export async function fetchTaskLogs(params) {
|
||||
|
||||
export async function clearOldTaskLogs(days) {
|
||||
const { data } = await api.post('/task/logs/clear', { days })
|
||||
taskStatsGetter.clear()
|
||||
runningTasksGetter.clear()
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -25,11 +25,8 @@ const stats = ref({})
|
||||
|
||||
const adminUsername = computed(() => stats.value?.admin_username || '')
|
||||
|
||||
async function refreshStats() {
|
||||
try {
|
||||
stats.value = await fetchSystemStats()
|
||||
} finally {
|
||||
}
|
||||
async function refreshStats(options = {}) {
|
||||
stats.value = await fetchSystemStats(options)
|
||||
}
|
||||
|
||||
const loadingBadges = ref(false)
|
||||
|
||||
@@ -550,9 +550,17 @@ const mobileModules = computed(() => [
|
||||
},
|
||||
])
|
||||
|
||||
const REPORT_SYNC_STATS_EVERY = 3
|
||||
let reportRefreshTick = 0
|
||||
|
||||
async function refreshAll(options = {}) {
|
||||
const showLoading = options.showLoading ?? true
|
||||
if (refreshing.value) return
|
||||
|
||||
const forceStatsSync = Boolean(options.forceStatsSync)
|
||||
const shouldSyncStats = forceStatsSync || reportRefreshTick % REPORT_SYNC_STATS_EVERY === 0
|
||||
reportRefreshTick += 1
|
||||
|
||||
refreshing.value = true
|
||||
if (showLoading) {
|
||||
loading.value = true
|
||||
@@ -593,7 +601,9 @@ async function refreshAll(options = {}) {
|
||||
if (slowSqlResult.status === 'fulfilled') slowSqlMetrics.value = slowSqlResult.value
|
||||
if (configResult.status === 'fulfilled') systemConfig.value = configResult.value
|
||||
|
||||
await refreshStats?.()
|
||||
if (shouldSyncStats) {
|
||||
await refreshStats?.({ force: forceStatsSync })
|
||||
}
|
||||
recordUpdatedAt()
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
@@ -637,7 +647,7 @@ function onVisibilityChange() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshAll({ showLoading: false })
|
||||
refreshAll({ showLoading: false, forceStatsSync: true })
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
scheduleRefreshLoop()
|
||||
|
||||
@@ -72,7 +72,7 @@ async function onEnableUser(row) {
|
||||
await approveUser(row.id)
|
||||
ElMessage.success('用户已启用')
|
||||
await loadUsers()
|
||||
await refreshStats?.()
|
||||
await refreshStats?.({ force: true })
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
}
|
||||
@@ -93,7 +93,7 @@ async function onDisableUser(row) {
|
||||
await rejectUser(row.id)
|
||||
ElMessage.success('用户已禁用')
|
||||
await loadUsers()
|
||||
await refreshStats?.()
|
||||
await refreshStats?.({ force: true })
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
}
|
||||
@@ -114,7 +114,7 @@ async function onDelete(row) {
|
||||
await deleteUser(row.id)
|
||||
ElMessage.success('用户已删除')
|
||||
await loadUsers()
|
||||
await refreshStats?.()
|
||||
await refreshStats?.({ force: true })
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
}
|
||||
@@ -136,7 +136,7 @@ async function onSetVip(row, days) {
|
||||
const res = await setUserVip(row.id, days)
|
||||
ElMessage.success(res?.message || 'VIP设置成功')
|
||||
await loadUsers()
|
||||
await refreshStats?.()
|
||||
await refreshStats?.({ force: true })
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
}
|
||||
@@ -157,7 +157,7 @@ async function onRemoveVip(row) {
|
||||
const res = await removeUserVip(row.id)
|
||||
ElMessage.success(res?.message || 'VIP已移除')
|
||||
await loadUsers()
|
||||
await refreshStats?.()
|
||||
await refreshStats?.({ force: true })
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user