perf: 并行化 admin 聚合路由并完善前端缓存预取

- gateway: usage detail / provider summary / pool overview / users list 改为 tokio join 并行拉取依赖数据
- usage: interval timeline 支持自动刷新并按查询区间动态展示,取消服务端 120 分钟过滤并在 ScatterChart 统一封顶
- frontend: 新增管理端导航预取工具及 SidebarNav/MainLayout 触发,admin 读接口统一走 cachedRequest 的短期缓存
- dashboard: request detail 支持短 TTL 缓存并在 UsageRecordsTable mousedown 时预取
- data: migrate 测试在 wait_for_postgres 失败时清理子进程,避免遗留
This commit is contained in:
fawney19
2026-04-19 15:17:25 +08:00
parent 97cd877ce5
commit 41b51f10a9
30 changed files with 619 additions and 243 deletions

View File

@@ -1,5 +1,5 @@
import client from '../client'
import { dedupedRequest, buildCacheKey } from '@/utils/cache'
import { buildCacheKey, cachedRequest, dedupedRequest } from '@/utils/cache'
import type {
GlobalModelCreate,
GlobalModelUpdate,
@@ -22,17 +22,26 @@ export type {
/**
* 获取 GlobalModel 列表
*/
interface GlobalModelListOptions {
cacheTtlMs?: number
}
export async function getGlobalModels(params?: {
skip?: number
limit?: number
is_active?: boolean
search?: string
}): Promise<GlobalModelListResponse> {
}, options: GlobalModelListOptions = {}): Promise<GlobalModelListResponse> {
const cacheTtlMs = options.cacheTtlMs ?? 0
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
})
return cachedRequest(
key,
async () => {
const response = await client.get('/api/admin/models/global', { params })
return response.data
},
cacheTtlMs,
)
}
/**

View File

@@ -1,5 +1,5 @@
import client from '../client'
import { dedupedRequest } from '@/utils/cache'
import { buildCacheKey, cachedRequest } from '@/utils/cache'
import type {
AllowedModels,
OAuthOrganizationInfo,
@@ -230,33 +230,59 @@ export interface PoolBatchAction {
payload?: Record<string, unknown> | null
}
export async function getPoolOverview(): Promise<PoolOverviewResponse> {
return dedupedRequest('pool:overview', async () => {
const response = await client.get<PoolOverviewResponse>('/api/admin/pool/overview')
return response.data
})
interface PoolReadOptions {
cacheTtlMs?: number
}
export async function getPoolSchedulingPresets(): Promise<PoolPresetMeta[]> {
return dedupedRequest('pool:scheduling-presets', async () => {
const response = await client.get<PoolPresetMeta[]>('/api/admin/pool/scheduling-presets')
return response.data
})
export async function getPoolOverview(
options: PoolReadOptions = {},
): Promise<PoolOverviewResponse> {
const cacheTtlMs = options.cacheTtlMs ?? 0
return cachedRequest(
'pool:overview',
async () => {
const response = await client.get<PoolOverviewResponse>('/api/admin/pool/overview')
return response.data
},
cacheTtlMs,
)
}
export async function getPoolSchedulingPresets(
options: PoolReadOptions = {},
): Promise<PoolPresetMeta[]> {
const cacheTtlMs = options.cacheTtlMs ?? 0
return cachedRequest(
'pool:scheduling-presets',
async () => {
const response = await client.get<PoolPresetMeta[]>('/api/admin/pool/scheduling-presets')
return response.data
},
cacheTtlMs,
)
}
export async function listPoolKeys(
providerId: string,
params: PoolKeysQuery = {},
options: PoolReadOptions = {},
): Promise<PoolKeysPageResponse> {
const normalizedParams = {
...params,
quick_selectors: params.quick_selectors?.length ? params.quick_selectors.join(',') : undefined,
}
const key = `pool:keys:${providerId}|${normalizedParams.page ?? ''}|${normalizedParams.page_size ?? ''}|${normalizedParams.search ?? ''}|${normalizedParams.status ?? ''}|${normalizedParams.quick_selectors ?? ''}|${normalizedParams.search_scope ?? ''}`
return dedupedRequest(key, async () => {
const response = await client.get<PoolKeysPageResponse>(`/api/admin/pool/${providerId}/keys`, { params: normalizedParams })
return response.data
})
const cacheKey = buildCacheKey(
`pool:keys:${providerId}`,
normalizedParams as Record<string, unknown>,
)
return cachedRequest(
cacheKey,
async () => {
const response = await client.get<PoolKeysPageResponse>(`/api/admin/pool/${providerId}/keys`, { params: normalizedParams })
return response.data
},
options.cacheTtlMs ?? 0,
)
}
export async function resolvePoolKeySelection(

View File

@@ -1,5 +1,5 @@
import client from '../client'
import { dedupedRequest } from '@/utils/cache'
import { buildCacheKey, cachedRequest, dedupedRequest } from '@/utils/cache'
import type {
ClaudeCodeAdvancedConfig,
FailoverRulesConfig,
@@ -13,6 +13,11 @@ interface ProviderRequestOptions {
timeout?: number
}
interface ProviderReadOptions {
timeout?: number
cacheTtlMs?: number
}
/**
* 获取 Providers 摘要(分页)
*/
@@ -43,15 +48,27 @@ function normalizeProviderSummary(
export async function getProvidersSummary(
params: ProviderSummaryQuery = {},
options: ProviderReadOptions = {},
): Promise<ProviderSummaryPageResponse> {
const response = await client.get<ProviderSummaryPageResponse>(
'/api/admin/providers/summary',
{ params },
const cacheTtlMs = options.cacheTtlMs ?? 0
const cacheKey = buildCacheKey('providers:summary', params as Record<string, unknown>)
return cachedRequest(
cacheKey,
async () => {
const response = await client.get<ProviderSummaryPageResponse>(
'/api/admin/providers/summary',
{
params,
timeout: options.timeout,
},
)
return {
...response.data,
items: response.data.items.map(normalizeProviderSummary),
}
},
cacheTtlMs,
)
return {
...response.data,
items: response.data.items.map(normalizeProviderSummary),
}
}
/**