Files
BYW-VoiceHub/server/utils/open-api-cache.ts
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

246 lines
7.0 KiB
TypeScript

import { cache } from './cache-helpers'
import crypto from 'crypto'
import { CACHE_CONSTANTS } from '../config/constants'
/**
* 开放API缓存管理器
* 复用现有的缓存管理机制,提供统一的缓存策略
*/
export class OpenApiCacheManager {
private static readonly CACHE_PREFIX = 'open:'
/**
* 生成缓存键
* @param endpoint API端点名称
* @param params 查询参数
* @returns 缓存键
*/
static generateCacheKey(endpoint: string, params: Record<string, any>): string {
// 过滤掉空值和undefined
const filteredParams = Object.fromEntries(
Object.entries(params).filter(
([_, value]) => value !== '' && value !== null && value !== undefined
)
)
// 对参数进行排序以确保一致性
const sortedParams = Object.keys(filteredParams)
.sort()
.reduce(
(result, key) => {
result[key] = filteredParams[key]
return result
},
{} as Record<string, any>
)
const queryHash = crypto.createHash('md5').update(JSON.stringify(sortedParams)).digest('hex')
return `${this.CACHE_PREFIX}${endpoint}:${queryHash}`
}
/**
* 获取缓存数据
* @param cacheKey 缓存键
* @returns 缓存的数据或null
*/
static async get<T>(cacheKey: string): Promise<T | null> {
try {
const cachedData = await cache.get<T>(cacheKey)
if (cachedData !== null) {
console.log(`[Open API Cache] 缓存命中: ${cacheKey}`)
return cachedData
}
console.log(`[Open API Cache] 缓存未命中: ${cacheKey}`)
return null
} catch (error) {
console.error(`[Open API Cache] 获取缓存失败 ${cacheKey}:`, error)
return null
}
}
/**
* 设置缓存数据
* @param cacheKey 缓存键
* @param data 要缓存的数据
* @param ttl 缓存时间(秒),默认使用配置的默认值
* @returns 是否设置成功
*/
static async set<T>(cacheKey: string, data: T, ttl?: number): Promise<boolean> {
try {
const cacheTtl = ttl || CACHE_CONSTANTS.DEFAULT_TTL
const success = await cache.set(cacheKey, data, cacheTtl)
if (success) {
console.log(`[Open API Cache] 设置缓存成功: ${cacheKey}, TTL: ${cacheTtl}s`)
} else {
console.warn(`[Open API Cache] 设置缓存失败: ${cacheKey}`)
}
return success
} catch (error) {
console.error(`[Open API Cache] 设置缓存异常 ${cacheKey}:`, error)
return false
}
}
/**
* 删除特定缓存
* @param cacheKey 缓存键
* @returns 是否删除成功
*/
static async delete(cacheKey: string): Promise<boolean> {
try {
const success = await cache.delete(cacheKey)
if (success) {
console.log(`[Open API Cache] 删除缓存成功: ${cacheKey}`)
}
return success
} catch (error) {
console.error(`[Open API Cache] 删除缓存失败 ${cacheKey}:`, error)
return false
}
}
/**
* 清除所有开放API缓存
* @returns 是否清除成功
*/
static async clearAll(): Promise<boolean> {
try {
const pattern = `${this.CACHE_PREFIX}*`
const success = await cache.deletePattern(pattern)
if (success) {
console.log(`[Open API Cache] 清除所有缓存成功: ${pattern}`)
}
return success
} catch (error) {
console.error(`[Open API Cache] 清除所有缓存失败:`, error)
return false
}
}
/**
* 清除特定端点的所有缓存
* @param endpoint 端点名称
* @returns 是否清除成功
*/
static async clearEndpoint(endpoint: string): Promise<boolean> {
try {
const pattern = `${this.CACHE_PREFIX}${endpoint}:*`
const success = await cache.deletePattern(pattern)
if (success) {
console.log(`[Open API Cache] 清除端点缓存成功: ${pattern}`)
}
return success
} catch (error) {
console.error(`[Open API Cache] 清除端点缓存失败 ${endpoint}:`, error)
return false
}
}
/**
* 获取或设置缓存(缓存穿透保护)
* @param cacheKey 缓存键
* @param loader 数据加载函数
* @param ttl 缓存时间(秒)
* @returns 数据
*/
static async getOrSet<T>(
cacheKey: string,
loader: () => Promise<T>,
ttl?: number
): Promise<T | null> {
try {
// 先尝试获取缓存
const cached = await this.get<T>(cacheKey)
if (cached !== null) {
return cached
}
// 缓存未命中,执行loader函数
console.log(`[Open API Cache] 执行数据加载: ${cacheKey}`)
const data = await loader()
if (data !== null && data !== undefined) {
// 异步设置缓存,不等待结果
this.set(cacheKey, data, ttl).catch((error) => {
console.error(`[Open API Cache] 异步设置缓存失败 ${cacheKey}:`, error)
})
}
return data
} catch (error) {
console.error(`[Open API Cache] getOrSet操作失败 ${cacheKey}:`, error)
return null
}
}
/**
* 智能响应体截断
* 对大响应体进行智能截断,保留重要信息
* @param responseBody 响应体
* @param maxLength 最大长度
* @returns 截断后的响应体
*/
static truncateResponseBody(
responseBody: any,
maxLength: number = CACHE_CONSTANTS.MAX_RESPONSE_BODY_LENGTH
): string | null {
if (!responseBody) return null
try {
const jsonString = JSON.stringify(responseBody)
if (jsonString.length <= maxLength) {
return jsonString
}
// 智能截断:保留开头和结尾的重要信息
const halfLength = Math.floor(maxLength / 2) - 50 // 预留空间给省略标记
const start = jsonString.substring(0, halfLength)
const end = jsonString.substring(jsonString.length - halfLength)
return `${start}... [截断 ${jsonString.length - maxLength} 字符] ...${end}`
} catch (error) {
console.error('[Open API Cache] 响应体截断失败:', error)
return String(responseBody).substring(0, maxLength)
}
}
/**
* 获取缓存统计信息
* @returns 缓存统计
*/
static async getStats(): Promise<{
totalKeys: number
endpoints: string[]
}> {
try {
// 这里可以扩展实现更详细的统计信息
// 目前返回基本信息
return {
totalKeys: 0, // 需要Redis支持来获取准确数量
endpoints: ['songs', 'schedules'] // 当前支持的端点
}
} catch (error) {
console.error('[Open API Cache] 获取统计信息失败:', error)
return {
totalKeys: 0,
endpoints: []
}
}
}
}
// 导出便捷方法
export const openApiCache = {
generateKey: OpenApiCacheManager.generateCacheKey,
get: OpenApiCacheManager.get,
set: OpenApiCacheManager.set,
delete: OpenApiCacheManager.delete,
clearAll: OpenApiCacheManager.clearAll,
clearEndpoint: OpenApiCacheManager.clearEndpoint,
getOrSet: OpenApiCacheManager.getOrSet,
truncateResponseBody: OpenApiCacheManager.truncateResponseBody,
getStats: OpenApiCacheManager.getStats
}