|
|
|
|
|
# 数据缓存功能开发文档
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
本文档描述了在 AdminConfig 管理配置页面中新增的数据缓存功能,包括浏览器本地缓存和数据库持久化缓存两大部分。
|
|
|
|
|
|
|
|
|
|
|
|
## 功能清单
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 浏览器本地缓存(原有功能增强)
|
|
|
|
|
|
- **一键获取数据并缓存**:获取市场概览、风险预警、热门品种详情和K线数据到浏览器 localStorage
|
|
|
|
|
|
- **缓存统计**:显示有效缓存、过期缓存、缓存总数和上次更新时间
|
|
|
|
|
|
- **清除所有缓存**:一键清除浏览器本地缓存
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 数据库持久化缓存(新增功能)
|
|
|
|
|
|
- **一键缓存所有合约到数据库**:批量获取所有合约详情和K线数据,存入 MySQL 和 Redis
|
|
|
|
|
|
- **缓存指定合约到数据库**:针对单个合约进行强制刷新缓存
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 前端实现
|
|
|
|
|
|
|
|
|
|
|
|
### 文件位置
|
|
|
|
|
|
- `src/pages/admin/AdminConfig.jsx`
|
|
|
|
|
|
|
|
|
|
|
|
### 新增状态变量
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 数据缓存相关状态
|
|
|
|
|
|
const [cacheLoading, setCacheLoading] = useState(false);
|
|
|
|
|
|
const [cacheStats, setCacheStats] = useState({ total: 0, valid: 0, expired: 0 });
|
|
|
|
|
|
const [cacheProgress, setCacheProgress] = useState({ current: 0, total: 0, name: '' });
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库缓存相关状态
|
|
|
|
|
|
const [dbCacheLoading, setDbCacheLoading] = useState(false);
|
|
|
|
|
|
const [dbCacheProgress, setDbCacheProgress] = useState({ current: 0, total: 0, name: '' });
|
|
|
|
|
|
const [symbolInput, setSymbolInput] = useState('');
|
|
|
|
|
|
const [singleSymbolLoading, setSingleSymbolLoading] = useState(false);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 新增图标导入
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
import {
|
|
|
|
|
|
DatabaseOutlined,
|
|
|
|
|
|
KeyOutlined,
|
|
|
|
|
|
SettingOutlined,
|
|
|
|
|
|
SaveOutlined,
|
|
|
|
|
|
ToolOutlined,
|
|
|
|
|
|
RobotOutlined,
|
|
|
|
|
|
EditOutlined,
|
|
|
|
|
|
CloudDownloadOutlined,
|
|
|
|
|
|
ClearOutlined,
|
|
|
|
|
|
ThunderboltOutlined,
|
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
|
CodeOutlined
|
|
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 核心功能函数
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. 浏览器本地缓存 - 一键获取数据
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const fetchAllDataForCache = async () => {
|
|
|
|
|
|
setCacheLoading(true);
|
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
|
const cacheResults = [];
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取市场概览数据
|
|
|
|
|
|
setCacheProgress({ current: 1, total: 4, name: '市场概览数据' });
|
|
|
|
|
|
const overviewResponse = await fetch(`${API_BASE_URL}/market/overview`);
|
|
|
|
|
|
if (overviewResponse.ok) {
|
|
|
|
|
|
const overviewData = await overviewResponse.json();
|
|
|
|
|
|
localStorage.setItem('cached_overview', JSON.stringify({
|
|
|
|
|
|
data: overviewData.data,
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟过期
|
|
|
|
|
|
}));
|
|
|
|
|
|
cacheResults.push({ name: '市场概览', status: 'success' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取风险预警数据
|
|
|
|
|
|
setCacheProgress({ current: 2, total: 4, name: '风险预警数据' });
|
|
|
|
|
|
// ... 类似处理
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取热门品种详情(前5个)
|
|
|
|
|
|
setCacheProgress({ current: 3, total: 4, name: '热门品种详情' });
|
|
|
|
|
|
// ... 处理详情缓存
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取K线数据(主要周期)
|
|
|
|
|
|
setCacheProgress({ current: 4, total: 4, name: 'K线数据' });
|
|
|
|
|
|
// ... 处理K线缓存
|
|
|
|
|
|
|
|
|
|
|
|
// 更新缓存统计
|
|
|
|
|
|
updateCacheStats();
|
|
|
|
|
|
messageApi.success(`数据缓存完成!`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
messageApi.error('缓存数据失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setCacheLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**缓存数据结构:**
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
{
|
|
|
|
|
|
data: {...}, // 实际数据
|
|
|
|
|
|
timestamp: 1234567890, // 缓存时间戳
|
|
|
|
|
|
expiresAt: 1234567890 // 过期时间戳
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**缓存内容:**
|
|
|
|
|
|
- 市场概览数据(`cached_overview`):5分钟过期
|
|
|
|
|
|
- 风险预警数据(`cached_alerts`):3分钟过期
|
|
|
|
|
|
- 品种详情数据(`cached_detail_{code}`):10分钟过期
|
|
|
|
|
|
- K线数据(`cached_kline_{code}_{period}`):30分钟过期
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. 数据库缓存 - 一键缓存所有合约
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const cacheAllContractsToDB = async () => {
|
|
|
|
|
|
setDbCacheLoading(true);
|
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
messageApi.info('开始批量缓存所有合约到数据库...');
|
|
|
|
|
|
|
|
|
|
|
|
// 调用后端批量缓存API
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/cache-all`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
const { success, total, klineCached } = result.data;
|
|
|
|
|
|
messageApi.success(`数据库缓存完成!成功: ${success}/${total},K线: ${klineCached}条`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
messageApi.error('缓存到数据库失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setDbCacheLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. 数据库缓存 - 缓存指定合约
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const cacheSymbolToDB = async () => {
|
|
|
|
|
|
if (!symbolInput.trim()) {
|
|
|
|
|
|
messageApi.warning('请输入合约代码');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setSingleSymbolLoading(true);
|
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
|
const symbol = symbolInput.trim().toUpperCase();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 调用后端单合约缓存API(强制刷新)
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/cache/${symbol}`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
periods: ['1H', '4H', '1D']
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
const klineSuccess = result.data.klines.filter((k) => k.success).length;
|
|
|
|
|
|
messageApi.success(`合约 ${symbol} 数据缓存完成!详情: ✓,K线: ${klineSuccess}/${result.data.klines.length}`);
|
|
|
|
|
|
setSymbolInput('');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
messageApi.error('缓存合约到数据库失败: ' + error.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSingleSymbolLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### UI 布局
|
|
|
|
|
|
|
|
|
|
|
|
数据缓存页签(`key: 'cache'`)包含以下卡片:
|
|
|
|
|
|
|
|
|
|
|
|
1. **缓存统计卡片**
|
|
|
|
|
|
- 有效缓存数(绿色)
|
|
|
|
|
|
- 过期缓存数(橙色)
|
|
|
|
|
|
- 缓存总数(蓝色)
|
|
|
|
|
|
- 上次更新时间(紫色)
|
|
|
|
|
|
|
|
|
|
|
|
2. **一键获取数据并缓存卡片**
|
|
|
|
|
|
- 主要按钮:"一键获取数据"
|
|
|
|
|
|
- 危险按钮:"清除所有缓存"
|
|
|
|
|
|
- 进度条显示
|
|
|
|
|
|
- 缓存内容说明
|
|
|
|
|
|
|
|
|
|
|
|
3. **一键缓存所有合约到数据库卡片**
|
|
|
|
|
|
- 警告类型说明 Alert
|
|
|
|
|
|
- 危险主按钮:"缓存所有合约到数据库"
|
|
|
|
|
|
- 进度条显示
|
|
|
|
|
|
- 数据存储位置说明
|
|
|
|
|
|
|
|
|
|
|
|
4. **缓存指定合约到数据库卡片**
|
|
|
|
|
|
- 输入框:合约代码(带快捷选择按钮)
|
|
|
|
|
|
- 主按钮:"缓存指定合约"
|
|
|
|
|
|
- 常用合约代码快捷按钮(AU、CU、RB等)
|
|
|
|
|
|
- 缓存内容说明
|
|
|
|
|
|
|
|
|
|
|
|
5. **缓存管理卡片**
|
|
|
|
|
|
- 缓存存储位置说明
|
|
|
|
|
|
- 缓存策略说明
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 后端实现
|
|
|
|
|
|
|
|
|
|
|
|
### 文件位置
|
|
|
|
|
|
- `backend/src/api/market.ts` - API 路由
|
|
|
|
|
|
- `backend/src/services/cacheService.ts` - 缓存服务
|
|
|
|
|
|
|
|
|
|
|
|
### CacheService 新增方法
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. 直接保存数据到缓存
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 直接保存数据到缓存(用于批量预热缓存)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async saveDirect(symbol: string, type: string, data: any, options: CacheOptions = {}): Promise<void> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const key = `market:${type}:${symbol}`;
|
|
|
|
|
|
// 保存到Redis
|
|
|
|
|
|
await this.set(key, data, options);
|
|
|
|
|
|
// 保存到MySQL
|
|
|
|
|
|
await this.saveToMySQL(symbol, type, data);
|
|
|
|
|
|
logger.log(`直接保存缓存成功: ${symbol}, ${type}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error(`直接保存缓存失败: ${symbol}, ${type}`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. 获取数据库缓存统计
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取数据库缓存统计
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getDBStats(): Promise<{ total: number; byType: Record<string, number> }> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [results]: any = await mysqlConnection.query(
|
|
|
|
|
|
'SELECT type, COUNT(*) as count FROM market_data GROUP BY type'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const byType: Record<string, number> = {};
|
|
|
|
|
|
let total = 0;
|
|
|
|
|
|
|
|
|
|
|
|
results.forEach((row: any) => {
|
|
|
|
|
|
byType[row.type] = row.count;
|
|
|
|
|
|
total += row.count;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return { total, byType };
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('获取数据库缓存统计失败:', error);
|
|
|
|
|
|
return { total: 0, byType: {} };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 新增 API 接口
|
|
|
|
|
|
|
|
|
|
|
|
#### 1. 批量缓存所有合约
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// POST /api/market/cache-all
|
|
|
|
|
|
router.post('/cache-all', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
logger.info('start 批量缓存所有合约到数据库');
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 获取所有合约列表
|
|
|
|
|
|
const overview = await fetchMarketOverview();
|
|
|
|
|
|
const contracts = overview || [];
|
|
|
|
|
|
|
|
|
|
|
|
const results = {
|
|
|
|
|
|
total: contracts.length,
|
|
|
|
|
|
success: 0,
|
|
|
|
|
|
failed: 0,
|
|
|
|
|
|
details: [] as { code: string; status: string; error?: string }[]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 批量获取并缓存每个合约的详情
|
|
|
|
|
|
for (let i = 0; i < contracts.length; i++) {
|
|
|
|
|
|
const contract = contracts[i];
|
|
|
|
|
|
try {
|
|
|
|
|
|
await fetchMarketDetail(contract.code);
|
|
|
|
|
|
results.success++;
|
|
|
|
|
|
results.details.push({ code: contract.code, status: 'success' });
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
results.failed++;
|
|
|
|
|
|
results.details.push({ code: contract.code, status: 'error', error: error.message });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 每10个合约延迟100ms,避免请求过快
|
|
|
|
|
|
if ((i + 1) % 10 === 0) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 缓存热门合约的K线数据(前10个)
|
|
|
|
|
|
const topContracts = contracts.slice(0, 10);
|
|
|
|
|
|
const periods = ['1H', '1D'];
|
|
|
|
|
|
let klineCached = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (const contract of topContracts) {
|
|
|
|
|
|
for (const period of periods) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await fetchKlineData(contract.code, period);
|
|
|
|
|
|
klineCached++;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error(`缓存K线数据失败: ${contract.code} ${period}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: `批量缓存完成,成功: ${results.success}/${results.total}`,
|
|
|
|
|
|
data: { ...results, klineCached }
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('批量缓存所有合约失败:', error);
|
|
|
|
|
|
res.status(500).json({ success: false, message: '批量缓存失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2. 缓存指定合约(强制刷新)
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// POST /api/market/cache/:symbol
|
|
|
|
|
|
router.post('/cache/:symbol', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { symbol } = req.params;
|
|
|
|
|
|
const { periods = ['1H', '4H', '1D'] } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`start 缓存合约 ${symbol} 到数据库`);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 清除现有缓存(强制刷新)
|
|
|
|
|
|
await cacheService.clearByType(symbol, 'detail');
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 重新获取并缓存合约详情
|
|
|
|
|
|
const detail = await fetchMarketDetail(symbol);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 缓存K线数据
|
|
|
|
|
|
const klineResults = [];
|
|
|
|
|
|
for (const period of periods) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await cacheService.clearByType(symbol, `kline:${period}`);
|
|
|
|
|
|
const kline = await fetchKlineData(symbol, period);
|
|
|
|
|
|
klineResults.push({ period, success: true });
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
klineResults.push({ period, success: false, error: error.message });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: `合约 ${symbol} 缓存成功`,
|
|
|
|
|
|
data: { symbol, detail: !!detail, klines: klineResults }
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
logger.error(`缓存合约 ${req.params.symbol} 失败:`, error);
|
|
|
|
|
|
res.status(500).json({ success: false, message: error.message || '缓存失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3. 获取缓存统计
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// GET /api/market/cache-stats
|
|
|
|
|
|
router.get('/cache-stats', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const stats = await cacheService.getDBStats();
|
|
|
|
|
|
res.status(200).json({ success: true, data: stats });
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logger.error('获取缓存统计失败:', error);
|
|
|
|
|
|
res.status(500).json({ success: false, message: '获取缓存统计失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 数据存储架构
|
|
|
|
|
|
|
|
|
|
|
|
### 浏览器本地缓存(LocalStorage)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Browser LocalStorage │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ cached_overview │ {data, timestamp, expiresAt} │
|
|
|
|
|
|
│ cached_alerts │ {data, timestamp, expiresAt} │
|
|
|
|
|
|
│ cached_detail_AU │ {data, timestamp, expiresAt} │
|
|
|
|
|
|
│ cached_kline_AU_1D │ {data, timestamp, expiresAt} │
|
|
|
|
|
|
│ dataCacheStats │ {total, valid, expired} │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库持久化缓存
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ MySQL - market_data │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ id | symbol | type | data (JSON) | updated_at │
|
|
|
|
|
|
├───┼─────────┼─────────┼─────────────────┼───────────────┤
|
|
|
|
|
|
│ 1 │ AU │ detail │ {...} │ 2024-01-01... │
|
|
|
|
|
|
│ 2 │ AU │ kline:1D│ {...} │ 2024-01-01... │
|
|
|
|
|
|
│ 3 │ CU │ detail │ {...} │ 2024-01-01... │
|
|
|
|
|
|
└───┴─────────┴─────────┴─────────────────┴───────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Redis Cache │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ Key │ Value │
|
|
|
|
|
|
├────────────────────────┼───────────────────────────────┤
|
|
|
|
|
|
│ market:detail:AU │ {...} │
|
|
|
|
|
|
│ market:kline:AU:1D │ {...} │
|
|
|
|
|
|
│ market:overview │ {...} │
|
|
|
|
|
|
└────────────────────────┴───────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 缓存策略
|
|
|
|
|
|
|
|
|
|
|
|
### 三级缓存架构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 数据请求流程 │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 1. 浏览器 LocalStorage │
|
|
|
|
|
|
│ - 最快,无网络开销 │
|
|
|
|
|
|
│ - 适用于高频访问的数据 │
|
|
|
|
|
|
│ - 需要手动管理过期时间 │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
│ 未命中
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 2. Redis 缓存 │
|
|
|
|
|
|
│ - 内存级速度 │
|
|
|
|
|
|
│ - 自动过期管理 │
|
|
|
|
|
|
│ - 分布式共享 │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
│ 未命中
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 3. MySQL 数据库 │
|
|
|
|
|
|
│ - 持久化存储 │
|
|
|
|
|
|
│ - 数据可靠性高 │
|
|
|
|
|
|
│ - 自动同步到 Redis │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
│ 未命中
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 4. 数据源(TQSDK/ServiceImplementation) │
|
|
|
|
|
|
│ - 实时数据 │
|
|
|
|
|
|
│ - 自动写入 MySQL + Redis │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 缓存有效期
|
|
|
|
|
|
|
|
|
|
|
|
| 数据类型 | LocalStorage | Redis | MySQL |
|
|
|
|
|
|
|---------|-------------|-------|-------|
|
|
|
|
|
|
| 市场概览 | 5分钟 | 5分钟 | 永久 |
|
|
|
|
|
|
| 风险预警 | 3分钟 | 3分钟 | 永久 |
|
|
|
|
|
|
| 品种详情 | 10分钟 | 5分钟 | 永久 |
|
|
|
|
|
|
| K线数据 | 30分钟 | 10分钟 | 永久 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 使用指南
|
|
|
|
|
|
|
|
|
|
|
|
### 浏览器本地缓存使用
|
|
|
|
|
|
|
|
|
|
|
|
1. 打开 AdminConfig 页面
|
|
|
|
|
|
2. 切换到"数据缓存"页签
|
|
|
|
|
|
3. 点击"一键获取数据"按钮
|
|
|
|
|
|
4. 等待进度完成
|
|
|
|
|
|
5. 查看缓存统计卡片确认缓存状态
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库缓存使用
|
|
|
|
|
|
|
|
|
|
|
|
#### 批量缓存所有合约
|
|
|
|
|
|
|
|
|
|
|
|
1. 在"数据缓存"页签中找到"一键缓存所有合约到数据库"卡片
|
|
|
|
|
|
2. 点击"缓存所有合约到数据库"按钮(红色按钮)
|
|
|
|
|
|
3. 等待批量处理完成(时间取决于合约数量)
|
|
|
|
|
|
4. 查看成功提示信息
|
|
|
|
|
|
|
|
|
|
|
|
#### 缓存指定合约
|
|
|
|
|
|
|
|
|
|
|
|
1. 在"缓存指定合约到数据库"卡片中
|
|
|
|
|
|
2. 输入合约代码(如 AU、CU、RB)或点击快捷按钮
|
|
|
|
|
|
3. 点击"缓存指定合约"按钮
|
|
|
|
|
|
4. 等待处理完成
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## API 列表
|
|
|
|
|
|
|
|
|
|
|
|
### 前端调用 API
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 端点 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| GET | `/api/market/overview` | 获取市场概览 |
|
|
|
|
|
|
| GET | `/api/market/detail/:symbol` | 获取品种详情 |
|
|
|
|
|
|
| GET | `/api/market/klines/:symbol?period=1D` | 获取K线数据 |
|
|
|
|
|
|
| POST | `/api/market/cache-all` | 批量缓存所有合约 |
|
|
|
|
|
|
| POST | `/api/market/cache/:symbol` | 缓存指定合约 |
|
|
|
|
|
|
| GET | `/api/market/cache-stats` | 获取缓存统计 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 注意事项
|
|
|
|
|
|
|
|
|
|
|
|
1. **性能考虑**
|
|
|
|
|
|
- 批量缓存操作可能需要较长时间,请耐心等待
|
|
|
|
|
|
- 后端已实现请求节流(每10个合约延迟100ms)
|
|
|
|
|
|
|
|
|
|
|
|
2. **错误处理**
|
|
|
|
|
|
- 单个合约缓存失败不会影响其他合约
|
|
|
|
|
|
- 网络错误会显示详细的错误信息
|
|
|
|
|
|
|
|
|
|
|
|
3. **数据一致性**
|
|
|
|
|
|
- 数据库缓存采用"强制刷新"策略
|
|
|
|
|
|
- 会先清除旧缓存再写入新数据
|
|
|
|
|
|
|
|
|
|
|
|
4. **存储限制**
|
|
|
|
|
|
- LocalStorage 有 5MB 限制
|
|
|
|
|
|
- 大量K线数据建议使用数据库缓存
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 后续优化建议
|
|
|
|
|
|
|
|
|
|
|
|
1. 添加缓存预热定时任务
|
|
|
|
|
|
2. 实现缓存失效自动重试机制
|
|
|
|
|
|
3. 添加缓存命中率监控
|
|
|
|
|
|
4. 支持批量指定合约缓存
|
|
|
|
|
|
5. 添加缓存导出/导入功能
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**文档版本**: 1.0
|
|
|
|
|
|
**最后更新**: 2026-03-02
|
|
|
|
|
|
**作者**: AI Assistant
|