mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +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:
@@ -11,6 +11,7 @@ interface CacheItem<T> {
|
||||
|
||||
class MemoryCache {
|
||||
private cache: Map<string, CacheItem<unknown>> = new Map()
|
||||
private inFlight: Map<string, Promise<unknown>> = new Map()
|
||||
private defaultTTL = 60000 // 默认缓存60秒
|
||||
|
||||
/**
|
||||
@@ -61,6 +62,7 @@ class MemoryCache {
|
||||
*/
|
||||
clear(): void {
|
||||
this.cache.clear()
|
||||
this.inFlight.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +83,27 @@ class MemoryCache {
|
||||
size(): number {
|
||||
return this.cache.size
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进行中的请求
|
||||
*/
|
||||
getInFlight<T>(key: string): Promise<T> | null {
|
||||
return (this.inFlight.get(key) as Promise<T> | undefined) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记进行中的请求
|
||||
*/
|
||||
setInFlight<T>(key: string, promise: Promise<T>): void {
|
||||
this.inFlight.set(key, promise as Promise<unknown>)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除进行中的请求
|
||||
*/
|
||||
deleteInFlight(key: string): void {
|
||||
this.inFlight.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局缓存实例
|
||||
@@ -103,18 +126,62 @@ export async function cachedRequest<T>(
|
||||
ttl?: number
|
||||
): Promise<T> {
|
||||
// 尝试从缓存获取
|
||||
const cached = cache.get<T>(key)
|
||||
if (cached !== null) {
|
||||
return cached
|
||||
if (ttl !== 0) {
|
||||
const cached = cache.get<T>(key)
|
||||
if (cached !== null) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存未命中,执行请求
|
||||
const data = await fetcher()
|
||||
// 命中进行中的同 key 请求,直接复用
|
||||
const inFlight = cache.getInFlight<T>(key)
|
||||
if (inFlight) {
|
||||
return inFlight
|
||||
}
|
||||
|
||||
// 存入缓存
|
||||
cache.set(key, data, ttl)
|
||||
// 缓存未命中,执行请求并登记为 in-flight
|
||||
const request = (async () => {
|
||||
try {
|
||||
const data = await fetcher()
|
||||
if (ttl !== 0) {
|
||||
cache.set(key, data, ttl)
|
||||
}
|
||||
return data
|
||||
} finally {
|
||||
cache.deleteInFlight(key)
|
||||
}
|
||||
})()
|
||||
|
||||
return data
|
||||
cache.setInFlight(key, request)
|
||||
return request
|
||||
}
|
||||
|
||||
export default cache
|
||||
/**
|
||||
* 仅做请求去重(不缓存结果)
|
||||
* 相同 key 的并发请求会复用同一个 Promise
|
||||
*/
|
||||
export function dedupedRequest<T>(
|
||||
key: string,
|
||||
fetcher: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
return cachedRequest(key, fetcher, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建归一化的缓存 key
|
||||
* 将 params 的 key 排序并过滤 undefined 值,确保相同参数生成相同 key
|
||||
*/
|
||||
export function buildCacheKey(prefix: string, params?: Record<string, unknown>): string {
|
||||
if (!params) {
|
||||
return prefix
|
||||
}
|
||||
const normalizedParams = Object.entries(params)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
if (normalizedParams.length === 0) {
|
||||
return prefix
|
||||
}
|
||||
return `${prefix}:${JSON.stringify(normalizedParams)}`
|
||||
}
|
||||
|
||||
export default cache
|
||||
|
||||
Reference in New Issue
Block a user