|
|
|
|
|
---
|
|
|
|
|
|
comet_change: analysis-storage-refactor
|
|
|
|
|
|
role: technical-design
|
|
|
|
|
|
canonical_spec: openspec
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Analysis Storage Refactor - Technical Design
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 架构概览
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 目标架构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 应用层 │
|
|
|
|
|
|
│ app/api/futures_analysis.py ←→ app/services/cache.py │
|
|
|
|
|
|
│ ↓ │
|
|
|
|
|
|
│ StorageManager │
|
|
|
|
|
|
│ (storage_manager.py) │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
↓
|
|
|
|
|
|
┌─────────────────────┼─────────────────────┐
|
|
|
|
|
|
↓ ↓ ↓
|
|
|
|
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
|
|
|
|
│ Redis │ │ MySQL │ │ SQLite │
|
|
|
|
|
|
│ (缓存) │ │(持久化) │ │ (兜底) │
|
|
|
|
|
|
└─────────┘ └─────────┘ └─────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 核心组件
|
|
|
|
|
|
|
|
|
|
|
|
| 组件 | 职责 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `StorageManager` | 提供通用 Redis 缓存方法(`cache_get`、`cache_set`、`cache_delete`) |
|
|
|
|
|
|
| `analysis_db.py` | Analysis 数据库连接管理,根据后端可用性返回 MySQL 或 SQLite Session |
|
|
|
|
|
|
| `analysis_models.py` | ORM 模型定义(不变) |
|
|
|
|
|
|
| `analysis_migration.py` | SQLite → MySQL 数据迁移 |
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 表清单
|
|
|
|
|
|
|
|
|
|
|
|
| 表名 | 用途 | 缓存 TTL |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| `futures_analysis` | 期货分析报告 | 5 分钟 |
|
|
|
|
|
|
| `watched_symbols` | 用户关注品种 | 10 分钟 |
|
|
|
|
|
|
| `ai_model_configs` | AI 模型配置 | 10 分钟 |
|
|
|
|
|
|
| `analysis_settings` | 分析设置 | 10 分钟 |
|
|
|
|
|
|
| `ai_analysis_cache` | AI 分析缓存 | 5 分钟 |
|
|
|
|
|
|
| `review_dates` | 复盘日期 | 1 小时 |
|
|
|
|
|
|
| `symbol_rankings` | 品种排名 | 1 小时 |
|
|
|
|
|
|
| `trading_plans` | 交易计划 | 1 小时 |
|
|
|
|
|
|
| `symbol_scores_v2` | V2 品种评分 | 1 小时 |
|
|
|
|
|
|
| `trading_plans_v2` | V2 交易计划 | 1 小时 |
|
|
|
|
|
|
| `sector_heat` | 板块热度 | 1 小时 |
|
|
|
|
|
|
| `review_plans_v2` | V2 复盘计划 | 1 小时 |
|
|
|
|
|
|
| `trade_records` | 交易记录 | 不缓存 |
|
|
|
|
|
|
| `trade_import_batches` | 交易导入批次 | 不缓存 |
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 降级策略
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 数据库 Session 切换
|
|
|
|
|
|
|
|
|
|
|
|
`analysis_db.py` 中的 `get_analysis_db()` 根据后端可用性返回不同 Session:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
业务代码 → get_analysis_db()
|
|
|
|
|
|
↓
|
|
|
|
|
|
MySQL 可用?
|
|
|
|
|
|
↓ 是 ↓ 否
|
|
|
|
|
|
MySQL Session SQLite Session
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 Redis 缓存层(可选)
|
|
|
|
|
|
|
|
|
|
|
|
对于高频读取场景,业务代码可通过 `StorageManager.cache_get/cache_set/cache_delete` 使用 Redis:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
读取请求
|
|
|
|
|
|
↓
|
|
|
|
|
|
StorageManager.cache_get(key)
|
|
|
|
|
|
↓ 命中
|
|
|
|
|
|
返回数据
|
|
|
|
|
|
↓ 未命中
|
|
|
|
|
|
ORM 查询 MySQL/SQLite → StorageManager.cache_set(key, value) → 返回
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 降级状态检测
|
|
|
|
|
|
|
|
|
|
|
|
- 复用 `StorageManager.check_mysql()` 惰性检测 MySQL 可用性
|
|
|
|
|
|
- MySQL 不可用时,`get_analysis_db()` 返回 SQLite Session
|
|
|
|
|
|
- 保留 SQLite 文件 `data/futures_analysis.db` 作为兜底
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 数据迁移
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 迁移时机
|
|
|
|
|
|
|
|
|
|
|
|
应用启动时(`lifespan`),在 MySQL 表结构初始化后执行。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 迁移逻辑
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
def migrate_analysis_sqlite_to_mysql():
|
|
|
|
|
|
"""将 SQLite analysis 数据迁移到 MySQL"""
|
|
|
|
|
|
# 1. 检测 MySQL 表是否已有数据
|
|
|
|
|
|
mysql_count = count_mysql_analysis_tables()
|
|
|
|
|
|
if mysql_count > 0:
|
|
|
|
|
|
logger.info("MySQL analysis 表已有数据,跳过迁移")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 从 SQLite 读取数据
|
|
|
|
|
|
sqlite_data = read_sqlite_analysis_data()
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 批量写入 MySQL
|
|
|
|
|
|
write_mysql_analysis_data(sqlite_data)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("Analysis 数据迁移完成")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 幂等性
|
|
|
|
|
|
|
|
|
|
|
|
- 检测 MySQL 表是否已有数据
|
|
|
|
|
|
- 有数据则跳过迁移
|
|
|
|
|
|
- 迁移失败不影响应用启动
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 缓存策略
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 通用缓存方法
|
|
|
|
|
|
|
|
|
|
|
|
`StorageManager` 提供三个通用方法:
|
|
|
|
|
|
|
|
|
|
|
|
- `cache_get(key)`: 读取 Redis 缓存
|
|
|
|
|
|
- `cache_set(key, value, ttl)`: 写入 Redis 缓存
|
|
|
|
|
|
- `cache_delete(key)`: 删除 Redis 缓存
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 缓存键设计
|
|
|
|
|
|
|
|
|
|
|
|
由业务代码决定缓存键,推荐格式:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
analysis:{table_name}:{id}
|
|
|
|
|
|
analysis:{table_name}:list:{query_hash}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 缓存失效
|
|
|
|
|
|
|
|
|
|
|
|
- 写入时删除相关缓存
|
|
|
|
|
|
- TTL 过期自动失效
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 缓存回填
|
|
|
|
|
|
|
|
|
|
|
|
- 读取时未命中,查询 MySQL/SQLite 后回填
|
|
|
|
|
|
- TTL 与表类型对应
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 兼容性
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 ORM 模型
|
|
|
|
|
|
|
|
|
|
|
|
- 保持不变
|
|
|
|
|
|
- 使用 `AnalysisBase` 作为基类
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 API 接口
|
|
|
|
|
|
|
|
|
|
|
|
- 保持不变
|
|
|
|
|
|
- 通过 `get_analysis_db()` 依赖注入
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 SQLite 文件
|
|
|
|
|
|
|
|
|
|
|
|
- 保留 `data/futures_analysis.db`
|
|
|
|
|
|
- 降级时使用
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 测试策略
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 单元测试
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 测试方法 |
|
|
|
|
|
|
|------|----------|
|
|
|
|
|
|
| Redis 缓存命中 | Mock Redis,验证不查询 MySQL |
|
|
|
|
|
|
| Redis 缓存未命中 | Mock Redis 返回 None,验证查询 MySQL |
|
|
|
|
|
|
| MySQL 降级到 SQLite | Mock MySQL 不可用,验证查询 SQLite |
|
|
|
|
|
|
| 数据迁移 | 验证 SQLite 数据完整迁移到 MySQL |
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 集成测试
|
|
|
|
|
|
|
|
|
|
|
|
- 启动应用,验证 MySQL 表创建
|
|
|
|
|
|
- 执行迁移,验证数据完整性
|
|
|
|
|
|
- 调用 API,验证功能正常
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 风险与缓解
|
|
|
|
|
|
|
|
|
|
|
|
| 风险 | 缓解措施 |
|
|
|
|
|
|
|------|----------|
|
|
|
|
|
|
| 数据迁移失败 | 保留 SQLite 作为降级方案,迁移失败时自动降级 |
|
|
|
|
|
|
| Redis 缓存不一致 | 写入时先删缓存,再写 MySQL,最后回填缓存 |
|
|
|
|
|
|
| 表结构不兼容 | 复用现有 ORM 模型,MySQL 自动创建表结构 |
|
|
|
|
|
|
| 性能下降 | Redis 缓存减少 MySQL 查询压力 |
|