mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
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:
@@ -1,4 +1,5 @@
|
||||
import apiClient from './client'
|
||||
import { cachedRequest, buildCacheKey } from '@/utils/cache'
|
||||
|
||||
// LDAP 配置导出结构
|
||||
export interface LDAPConfigExport {
|
||||
@@ -743,10 +744,17 @@ export const adminApi = {
|
||||
include_inactive?: boolean
|
||||
exclude_admin?: boolean
|
||||
}): Promise<LeaderboardResponse> {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/users', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:leaderboard:users', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/users', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getLeaderboardApiKeys(params?: {
|
||||
@@ -764,10 +772,17 @@ export const adminApi = {
|
||||
include_inactive?: boolean
|
||||
exclude_admin?: boolean
|
||||
}): Promise<LeaderboardResponse> {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/api-keys', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:leaderboard:api-keys', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/api-keys', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getLeaderboardModels(params?: {
|
||||
@@ -783,10 +798,17 @@ export const adminApi = {
|
||||
provider_name?: string
|
||||
model?: string
|
||||
}): Promise<LeaderboardResponse> {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/models', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:leaderboard:models', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<LeaderboardResponse>('/api/admin/stats/leaderboard/models', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getCostForecast(params?: {
|
||||
@@ -798,10 +820,17 @@ export const adminApi = {
|
||||
days?: number
|
||||
forecast_days?: number
|
||||
}): Promise<CostForecastResponse> {
|
||||
const response = await apiClient.get<CostForecastResponse>('/api/admin/stats/cost/forecast', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:cost:forecast', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<CostForecastResponse>('/api/admin/stats/cost/forecast', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
30 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getCostSavings(params?: {
|
||||
@@ -813,15 +842,28 @@ export const adminApi = {
|
||||
provider_name?: string
|
||||
model?: string
|
||||
}): Promise<CostSavingsResponse> {
|
||||
const response = await apiClient.get<CostSavingsResponse>('/api/admin/stats/cost/savings', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:cost:savings', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<CostSavingsResponse>('/api/admin/stats/cost/savings', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
30 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getQuotaUsage(): Promise<QuotaUsageResponse> {
|
||||
const response = await apiClient.get<QuotaUsageResponse>('/api/admin/stats/providers/quota-usage')
|
||||
return response.data
|
||||
return cachedRequest(
|
||||
'admin:stats:providers:quota-usage',
|
||||
async () => {
|
||||
const response = await apiClient.get<QuotaUsageResponse>('/api/admin/stats/providers/quota-usage')
|
||||
return response.data
|
||||
},
|
||||
30 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getPercentiles(params?: {
|
||||
@@ -831,10 +873,17 @@ export const adminApi = {
|
||||
timezone?: string
|
||||
tz_offset_minutes?: number
|
||||
}): Promise<PercentileItem[]> {
|
||||
const response = await apiClient.get<PercentileItem[]>('/api/admin/stats/performance/percentiles', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:performance:percentiles', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<PercentileItem[]>('/api/admin/stats/performance/percentiles', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getErrorDistribution(params?: {
|
||||
@@ -844,10 +893,17 @@ export const adminApi = {
|
||||
timezone?: string
|
||||
tz_offset_minutes?: number
|
||||
}): Promise<ErrorDistributionResponse> {
|
||||
const response = await apiClient.get<ErrorDistributionResponse>('/api/admin/stats/errors/distribution', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:errors:distribution', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<ErrorDistributionResponse>('/api/admin/stats/errors/distribution', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
async getComparison(params: {
|
||||
@@ -882,8 +938,15 @@ export const adminApi = {
|
||||
model?: string
|
||||
provider_name?: string
|
||||
}): Promise<Array<Record<string, unknown>>> {
|
||||
const response = await apiClient.get<Array<Record<string, unknown>>>('/api/admin/stats/time-series', { params })
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('admin:stats:time-series', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<Array<Record<string, unknown>>>('/api/admin/stats/time-series', { params })
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import api from './client'
|
||||
import { cachedRequest, buildCacheKey } from '@/utils/cache'
|
||||
|
||||
export interface CacheStats {
|
||||
scheduler: string
|
||||
@@ -314,8 +315,15 @@ export const cacheAnalysisApi = {
|
||||
user_id?: string
|
||||
include_user_info?: boolean
|
||||
}): Promise<IntervalTimelineResponse> {
|
||||
const response = await api.get('/api/admin/usage/cache-affinity/interval-timeline', { params })
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('cache-affinity:interval-timeline', params as Record<string, unknown> | undefined)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await api.get('/api/admin/usage/cache-affinity/interval-timeline', { params })
|
||||
return response.data
|
||||
},
|
||||
30000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import apiClient from './client'
|
||||
import { cachedRequest, buildCacheKey } from '@/utils/cache'
|
||||
|
||||
export interface DashboardStat {
|
||||
name: string
|
||||
@@ -289,8 +290,15 @@ export interface TimeRangeParams {
|
||||
export const dashboardApi = {
|
||||
// 获取仪表盘统计数据
|
||||
async getStats(params?: TimeRangeParams): Promise<DashboardStatsResponse> {
|
||||
const response = await apiClient.get<DashboardStatsResponse>('/api/dashboard/stats', { params })
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('dashboard:stats', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<DashboardStatsResponse>('/api/dashboard/stats', { params })
|
||||
return response.data
|
||||
},
|
||||
10 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
// 获取最近的请求记录
|
||||
@@ -303,8 +311,14 @@ export const dashboardApi = {
|
||||
|
||||
// 获取提供商状态
|
||||
async getProviderStatus(): Promise<ProviderStatus[]> {
|
||||
const response = await apiClient.get<ProviderStatusResponse>('/api/dashboard/provider-status')
|
||||
return response.data.providers
|
||||
return cachedRequest(
|
||||
'dashboard:provider-status',
|
||||
async () => {
|
||||
const response = await apiClient.get<ProviderStatusResponse>('/api/dashboard/provider-status')
|
||||
return response.data.providers
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
// 获取请求详情
|
||||
@@ -316,10 +330,17 @@ export const dashboardApi = {
|
||||
|
||||
// 获取每日统计数据
|
||||
async getDailyStats(params?: TimeRangeParams & { days?: number }): Promise<DailyStatsResponse> {
|
||||
const response = await apiClient.get<DailyStatsResponse>('/api/dashboard/daily-stats', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('dashboard:daily-stats', params)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<DailyStatsResponse>('/api/dashboard/daily-stats', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
20 * 1000
|
||||
)
|
||||
},
|
||||
|
||||
// 获取 cURL 命令数据(含明文 API Key)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import client from '../client'
|
||||
import { dedupedRequest, buildCacheKey } from '@/utils/cache'
|
||||
import type {
|
||||
GlobalModelCreate,
|
||||
GlobalModelUpdate,
|
||||
@@ -27,16 +28,21 @@ export async function getGlobalModels(params?: {
|
||||
is_active?: boolean
|
||||
search?: string
|
||||
}): Promise<GlobalModelListResponse> {
|
||||
const response = await client.get('/api/admin/models/global', { params })
|
||||
return response.data
|
||||
const key = buildCacheKey('global-models:list', params as Record<string, unknown> | undefined)
|
||||
return dedupedRequest(key, async () => {
|
||||
const response = await client.get('/api/admin/models/global', { params })
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个 GlobalModel 详情
|
||||
*/
|
||||
export async function getGlobalModel(id: string): Promise<GlobalModelWithStats> {
|
||||
const response = await client.get(`/api/admin/models/global/${id}`)
|
||||
return response.data
|
||||
return dedupedRequest(`global-models:detail:${id}`, async () => {
|
||||
const response = await client.get(`/api/admin/models/global/${id}`)
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,10 +118,12 @@ export async function getGlobalModelProviders(globalModelId: string): Promise<{
|
||||
providers: ModelCatalogProviderDetail[]
|
||||
total: number
|
||||
}> {
|
||||
const response = await client.get(
|
||||
`/api/admin/models/global/${globalModelId}/providers`
|
||||
)
|
||||
return response.data
|
||||
return dedupedRequest(`global-models:providers:${globalModelId}`, async () => {
|
||||
const response = await client.get(
|
||||
`/api/admin/models/global/${globalModelId}/providers`
|
||||
)
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import client from '../client'
|
||||
import { dedupedRequest } from '@/utils/cache'
|
||||
import type { AllowedModels, ProxyConfig } from './types/provider'
|
||||
|
||||
export interface PoolKeyStatus {
|
||||
@@ -175,16 +176,21 @@ export interface PoolBatchAction {
|
||||
}
|
||||
|
||||
export async function getPoolOverview(): Promise<PoolOverviewResponse> {
|
||||
const response = await client.get('/api/admin/pool/overview')
|
||||
return response.data
|
||||
return dedupedRequest('pool:overview', async () => {
|
||||
const response = await client.get<PoolOverviewResponse>('/api/admin/pool/overview')
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
export async function listPoolKeys(
|
||||
providerId: string,
|
||||
params: PoolKeysQuery = {},
|
||||
): Promise<PoolKeysPageResponse> {
|
||||
const response = await client.get(`/api/admin/pool/${providerId}/keys`, { params })
|
||||
return response.data
|
||||
const key = `pool:keys:${providerId}|${params.page ?? ''}|${params.page_size ?? ''}|${params.search ?? ''}|${params.status ?? ''}`
|
||||
return dedupedRequest(key, async () => {
|
||||
const response = await client.get<PoolKeysPageResponse>(`/api/admin/pool/${providerId}/keys`, { params })
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
export async function batchActionPoolKeys(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import client from '../client'
|
||||
import { dedupedRequest } from '@/utils/cache'
|
||||
import type {
|
||||
ClaudeCodeAdvancedConfig,
|
||||
FailoverRulesConfig,
|
||||
@@ -11,16 +12,20 @@ import type {
|
||||
* 获取 Providers 摘要(包含 Endpoints 统计)
|
||||
*/
|
||||
export async function getProvidersSummary(): Promise<ProviderWithEndpointsSummary[]> {
|
||||
const response = await client.get('/api/admin/providers/summary')
|
||||
return response.data
|
||||
return dedupedRequest('providers:summary', async () => {
|
||||
const response = await client.get<ProviderWithEndpointsSummary[]>('/api/admin/providers/summary')
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个 Provider 的详细信息
|
||||
*/
|
||||
export async function getProvider(providerId: string): Promise<ProviderWithEndpointsSummary> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/summary`)
|
||||
return response.data
|
||||
return dedupedRequest(`providers:detail:${providerId}`, async () => {
|
||||
const response = await client.get<ProviderWithEndpointsSummary>(`/api/admin/providers/${providerId}/summary`)
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,6 +219,8 @@ export interface ProviderMappingPreviewResponse {
|
||||
export async function getProviderMappingPreview(
|
||||
providerId: string
|
||||
): Promise<ProviderMappingPreviewResponse> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/mapping-preview`)
|
||||
return response.data
|
||||
return dedupedRequest(`providers:mapping-preview:${providerId}`, async () => {
|
||||
const response = await client.get<ProviderMappingPreviewResponse>(`/api/admin/providers/${providerId}/mapping-preview`)
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import apiClient from './client'
|
||||
import type { ActivityHeatmap } from '@/types/activity'
|
||||
import type { TieredPricingConfig } from './endpoints/types'
|
||||
import { cachedRequest, buildCacheKey } from '@/utils/cache'
|
||||
|
||||
export interface Profile {
|
||||
id: string // UUID
|
||||
@@ -328,8 +329,15 @@ export const meApi = {
|
||||
points: Array<{ x: string; y: number; model?: string }>
|
||||
models?: string[]
|
||||
}> {
|
||||
const response = await apiClient.get('/api/users/me/usage/interval-timeline', { params })
|
||||
return response.data
|
||||
const cacheKey = buildCacheKey('me:interval-timeline', params as Record<string, unknown> | undefined)
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get('/api/users/me/usage/interval-timeline', { params })
|
||||
return response.data
|
||||
},
|
||||
30000
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -337,7 +345,13 @@ export const meApi = {
|
||||
* 后端已缓存5分钟
|
||||
*/
|
||||
async getActivityHeatmap(): Promise<ActivityHeatmap> {
|
||||
const response = await apiClient.get<ActivityHeatmap>('/api/users/me/usage/heatmap')
|
||||
return response.data
|
||||
return cachedRequest(
|
||||
'me-activity-heatmap',
|
||||
async () => {
|
||||
const response = await apiClient.get<ActivityHeatmap>('/api/users/me/usage/heatmap')
|
||||
return response.data
|
||||
},
|
||||
60000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import apiClient from './client'
|
||||
import { cachedRequest } from '@/utils/cache'
|
||||
import { cachedRequest, dedupedRequest, buildCacheKey } from '@/utils/cache'
|
||||
import type { ActivityHeatmap } from '@/types/activity'
|
||||
|
||||
export interface UsageRecord {
|
||||
@@ -187,8 +187,11 @@ export const usageApi = {
|
||||
limit: number
|
||||
offset: number
|
||||
}> {
|
||||
const response = await apiClient.get('/api/admin/usage/records', { params })
|
||||
return response.data
|
||||
const key = buildCacheKey('usage:records', params as Record<string, unknown> | undefined)
|
||||
return dedupedRequest(key, async () => {
|
||||
const response = await apiClient.get('/api/admin/usage/records', { params })
|
||||
return response.data
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -226,7 +229,13 @@ export const usageApi = {
|
||||
* 后端已缓存5分钟
|
||||
*/
|
||||
async getActivityHeatmap(): Promise<ActivityHeatmap> {
|
||||
const response = await apiClient.get<ActivityHeatmap>('/api/admin/usage/heatmap')
|
||||
return response.data
|
||||
return cachedRequest(
|
||||
'admin-usage-activity-heatmap',
|
||||
async () => {
|
||||
const response = await apiClient.get<ActivityHeatmap>('/api/admin/usage/heatmap')
|
||||
return response.data
|
||||
},
|
||||
60000
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user