Files
vf_react/src/utils/stock/klineDataCache.js
zdl 334a4b7e50 fix: 修复 Community 目录迁移后的导入路径错误
修复 7 处导入路径问题:
- EventHeaderInfo.js: StockChangeIndicators 和 EventFollowButton 路径
- klineDataCache.js: stockService 和 logger 路径别名
- EventDescriptionSection.js: professionalTheme 路径别名
- CollapsibleSection.js: professionalTheme 路径别名
- RelatedConceptsSection/index.js: logger 路径别名
- CompactMetaBar.js: EventFollowButton 路径
- EventDetailScrollPanel.js: DynamicNewsDetailPanel 路径

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 13:24:35 +08:00

289 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/utils/stock/klineDataCache.js
import dayjs from 'dayjs';
import { stockService } from '@services/eventService';
import { logger } from '@utils/logger';
// ================= 全局缓存和请求管理 =================
export const klineDataCache = new Map(); // 缓存K线数据: key = `${code}|${date}|${chartType}` -> data
export const pendingRequests = new Map(); // 正在进行的请求: key = `${code}|${date}|${chartType}` -> Promise
export const lastRequestTime = new Map(); // 最后请求时间: key = `${code}|${date}|${chartType}` -> timestamp
export const batchPendingRequests = new Map(); // 批量请求的 Promise: key = `${eventTime}|${chartType}` -> Promise
// 请求间隔限制(毫秒)
const REQUEST_INTERVAL = 30000; // 30秒内不重复请求同一只股票的数据
/**
* 获取缓存键
* @param {string} stockCode - 股票代码
* @param {string} eventTime - 事件时间
* @param {string} chartType - 图表类型timeline/daily
* @returns {string} 缓存键
*/
export const getCacheKey = (stockCode, eventTime, chartType = 'timeline') => {
const date = eventTime ? dayjs(eventTime).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
return `${stockCode}|${date}|${chartType}`;
};
/**
* 检查是否需要刷新数据
* @param {string} cacheKey - 缓存键
* @returns {boolean} 是否需要刷新
*/
export const shouldRefreshData = (cacheKey) => {
const lastTime = lastRequestTime.get(cacheKey);
if (!lastTime) return true;
const now = Date.now();
const elapsed = now - lastTime;
// 如果是今天的数据且交易时间内,允许更频繁的更新
const today = dayjs().format('YYYY-MM-DD');
const isToday = cacheKey.includes(today);
const currentHour = new Date().getHours();
const isTradingHours = currentHour >= 9 && currentHour < 16;
if (isToday && isTradingHours) {
return elapsed > REQUEST_INTERVAL;
}
// 历史数据不需要频繁更新
return elapsed > 3600000; // 1小时
};
/**
* 获取K线数据带缓存和防重复请求
* @param {string} stockCode - 股票代码
* @param {string} eventTime - 事件时间
* @param {string} chartType - 图表类型timeline/daily
* @returns {Promise<Array>} K线数据
*/
export const fetchKlineData = async (stockCode, eventTime, chartType = 'timeline') => {
const cacheKey = getCacheKey(stockCode, eventTime, chartType);
// 1. 检查缓存
if (klineDataCache.has(cacheKey)) {
// 检查是否需要刷新
if (!shouldRefreshData(cacheKey)) {
logger.debug('klineDataCache', '使用缓存数据', { cacheKey });
return klineDataCache.get(cacheKey);
}
}
// 2. 检查是否有正在进行的请求
if (pendingRequests.has(cacheKey)) {
logger.debug('klineDataCache', '等待进行中的请求', { cacheKey });
return pendingRequests.get(cacheKey);
}
// 3. 发起新请求
logger.debug('klineDataCache', '发起新K线数据请求', { cacheKey, chartType });
const normalizedEventTime = eventTime ? dayjs(eventTime).format('YYYY-MM-DD HH:mm') : undefined;
const requestPromise = stockService
.getKlineData(stockCode, chartType, normalizedEventTime)
.then((res) => {
const data = Array.isArray(res?.data) ? res.data : [];
// 更新缓存
klineDataCache.set(cacheKey, data);
lastRequestTime.set(cacheKey, Date.now());
// 清除pending状态
pendingRequests.delete(cacheKey);
logger.debug('klineDataCache', 'K线数据请求完成并缓存', {
cacheKey,
chartType,
dataPoints: data.length
});
return data;
})
.catch((error) => {
logger.error('klineDataCache', 'fetchKlineData', error, { stockCode, chartType, cacheKey });
// 清除pending状态
pendingRequests.delete(cacheKey);
// 如果有旧缓存,返回旧数据
if (klineDataCache.has(cacheKey)) {
return klineDataCache.get(cacheKey);
}
return [];
});
// 保存pending请求
pendingRequests.set(cacheKey, requestPromise);
return requestPromise;
};
/**
* 清除指定股票的缓存
* @param {string} stockCode - 股票代码
* @param {string} eventTime - 事件时间(可选)
*/
export const clearCache = (stockCode, eventTime = null) => {
if (eventTime) {
const cacheKey = getCacheKey(stockCode, eventTime);
klineDataCache.delete(cacheKey);
lastRequestTime.delete(cacheKey);
pendingRequests.delete(cacheKey);
logger.debug('klineDataCache', '清除缓存', { cacheKey });
} else {
// 清除该股票的所有缓存
const prefix = `${stockCode}|`;
for (const key of klineDataCache.keys()) {
if (key.startsWith(prefix)) {
klineDataCache.delete(key);
lastRequestTime.delete(key);
pendingRequests.delete(key);
}
}
logger.debug('klineDataCache', '清除股票所有缓存', { stockCode });
}
};
/**
* 清除所有缓存
*/
export const clearAllCache = () => {
klineDataCache.clear();
lastRequestTime.clear();
pendingRequests.clear();
logger.debug('klineDataCache', '清除所有缓存');
};
/**
* 获取缓存统计信息
* @returns {Object} 缓存统计
*/
export const getCacheStats = () => {
return {
totalCached: klineDataCache.size,
pendingRequests: pendingRequests.size,
cacheKeys: Array.from(klineDataCache.keys())
};
};
/**
* 批量获取多只股票的K线数据一次API请求
* @param {string[]} stockCodes - 股票代码数组
* @param {string} eventTime - 事件时间
* @param {string} chartType - 图表类型timeline/daily
* @returns {Promise<Object>} 股票代码到K线数据的映射 { [stockCode]: data[] }
*/
export const fetchBatchKlineData = async (stockCodes, eventTime, chartType = 'timeline') => {
if (!stockCodes || stockCodes.length === 0) {
return {};
}
const normalizedEventTime = eventTime ? dayjs(eventTime).format('YYYY-MM-DD HH:mm') : undefined;
const batchKey = `${normalizedEventTime || 'today'}|${chartType}`;
// 过滤出未缓存的股票
const uncachedCodes = stockCodes.filter(code => {
const cacheKey = getCacheKey(code, eventTime, chartType);
return !klineDataCache.has(cacheKey) || shouldRefreshData(cacheKey);
});
logger.debug('klineDataCache', '批量请求分析', {
totalCodes: stockCodes.length,
uncachedCodes: uncachedCodes.length,
cachedCodes: stockCodes.length - uncachedCodes.length
});
// 如果所有股票都有缓存,直接返回缓存数据
if (uncachedCodes.length === 0) {
const result = {};
stockCodes.forEach(code => {
const cacheKey = getCacheKey(code, eventTime, chartType);
result[code] = klineDataCache.get(cacheKey) || [];
});
logger.debug('klineDataCache', '所有股票数据来自缓存', { stockCount: stockCodes.length });
return result;
}
// 检查是否有正在进行的批量请求
if (batchPendingRequests.has(batchKey)) {
logger.debug('klineDataCache', '等待进行中的批量请求', { batchKey });
return batchPendingRequests.get(batchKey);
}
// 发起批量请求
logger.debug('klineDataCache', '发起批量K线数据请求', {
batchKey,
stockCount: uncachedCodes.length,
chartType
});
const requestPromise = stockService
.getBatchKlineData(uncachedCodes, chartType, normalizedEventTime)
.then((response) => {
const batchData = response?.data || {};
const now = Date.now();
// 将批量数据存入缓存
Object.entries(batchData).forEach(([code, stockData]) => {
const data = Array.isArray(stockData?.data) ? stockData.data : [];
const cacheKey = getCacheKey(code, eventTime, chartType);
klineDataCache.set(cacheKey, data);
lastRequestTime.set(cacheKey, now);
});
// 对于请求中没有返回数据的股票,设置空数组
uncachedCodes.forEach(code => {
if (!batchData[code]) {
const cacheKey = getCacheKey(code, eventTime, chartType);
if (!klineDataCache.has(cacheKey)) {
klineDataCache.set(cacheKey, []);
lastRequestTime.set(cacheKey, now);
}
}
});
// 清除批量请求状态
batchPendingRequests.delete(batchKey);
logger.debug('klineDataCache', '批量K线数据请求完成', {
batchKey,
stockCount: Object.keys(batchData).length
});
// 返回所有请求股票的数据(包括之前缓存的)
const result = {};
stockCodes.forEach(code => {
const cacheKey = getCacheKey(code, eventTime, chartType);
result[code] = klineDataCache.get(cacheKey) || [];
});
return result;
})
.catch((error) => {
logger.error('klineDataCache', 'fetchBatchKlineData', error, {
stockCount: uncachedCodes.length,
chartType
});
// 清除批量请求状态
batchPendingRequests.delete(batchKey);
// 返回已缓存的数据
const result = {};
stockCodes.forEach(code => {
const cacheKey = getCacheKey(code, eventTime, chartType);
result[code] = klineDataCache.get(cacheKey) || [];
});
return result;
});
// 保存批量请求
batchPendingRequests.set(batchKey, requestPromise);
return requestPromise;
};
/**
* 预加载多只股票的K线数据后台执行不阻塞UI
* @param {string[]} stockCodes - 股票代码数组
* @param {string} eventTime - 事件时间
* @param {string} chartType - 图表类型timeline/daily
*/
export const preloadBatchKlineData = (stockCodes, eventTime, chartType = 'timeline') => {
// 异步执行不返回Promise不阻塞调用方
fetchBatchKlineData(stockCodes, eventTime, chartType).catch(() => {
// 静默处理错误,预加载失败不影响用户体验
});
};