|
|
---
|
|
|
comet_change: config-to-mysql
|
|
|
role: technical-design
|
|
|
canonical_spec: openspec
|
|
|
archived-with: config-to-mysql
|
|
|
status: final
|
|
|
---
|
|
|
|
|
|
# Config to MySQL - Technical Design
|
|
|
|
|
|
## 1. 架构概览
|
|
|
|
|
|
### 1.1 目标架构
|
|
|
|
|
|
```
|
|
|
业务代码 / API
|
|
|
↓
|
|
|
app/config_store.py (统一入口)
|
|
|
↓
|
|
|
MySQL 可用?
|
|
|
↓ 是 ↓ 否
|
|
|
读/写 app_config 读/写 JSON 文件
|
|
|
```
|
|
|
|
|
|
### 1.2 核心组件
|
|
|
|
|
|
| 组件 | 职责 |
|
|
|
|------|------|
|
|
|
| `AppConfig` 模型 | MySQL 配置表 ORM 映射 |
|
|
|
| `ConfigStore` | 统一配置读写入口,封装降级逻辑 |
|
|
|
| `config_migration.py` | JSON → MySQL 一次性迁移 |
|
|
|
| JSON 文件 | MySQL 不可用时降级使用 |
|
|
|
|
|
|
## 2. 数据模型
|
|
|
|
|
|
### 2.1 AppConfig 表
|
|
|
|
|
|
```python
|
|
|
class AppConfig(Base):
|
|
|
__tablename__ = "app_config"
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
|
config_key = Column(String(64), unique=True, nullable=False, index=True)
|
|
|
config_value = Column(JSON, nullable=False)
|
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
```
|
|
|
|
|
|
### 2.2 配置键
|
|
|
|
|
|
| config_key | 对应文件 | 默认值 |
|
|
|
|------------|----------|--------|
|
|
|
| `symbols` | `config/symbols_config.json` | `{"futures": {}, "stock": {}}` |
|
|
|
| `ai` | `config/ai_config.json` | `{"models": [], "active_model": None, "analysis_settings": {...}}` |
|
|
|
|
|
|
## 3. 降级策略
|
|
|
|
|
|
### 3.1 读取流程
|
|
|
|
|
|
1. 检查 MySQL 可用性(复用 `StorageManager.check_mysql()`)
|
|
|
2. MySQL 可用:
|
|
|
- 查询 `app_config`
|
|
|
- 命中则返回
|
|
|
- 未命中则读取 JSON 文件并回填数据库
|
|
|
3. MySQL 不可用:
|
|
|
- 直接读取 JSON 文件
|
|
|
|
|
|
### 3.2 写入流程
|
|
|
|
|
|
1. 检查 MySQL 可用性
|
|
|
2. MySQL 可用时先写入 `app_config`;失败时保留 JSON 旧值并返回 False
|
|
|
3. MySQL 写入成功后,再原子写入 JSON 文件
|
|
|
4. MySQL 不可用时只写入 JSON 文件
|
|
|
|
|
|
### 3.3 降级检测
|
|
|
|
|
|
复用 `StorageManager.check_mysql()` 惰性检测,避免每次配置读写都尝试连接数据库。
|
|
|
|
|
|
## 4. ConfigStore 接口
|
|
|
|
|
|
```python
|
|
|
class ConfigStore:
|
|
|
def get_config(self, key: str, fallback: Optional[dict] = None) -> dict: ...
|
|
|
def set_config(self, key: str, value: dict) -> bool: ...
|
|
|
def load_from_json(self, key: str, fallback: Optional[dict] = None) -> dict: ...
|
|
|
def save_to_json(self, key: str, value: dict) -> None: ...
|
|
|
```
|
|
|
|
|
|
### 4.1 单例获取
|
|
|
|
|
|
```python
|
|
|
_config_store = None
|
|
|
|
|
|
def get_config_store() -> ConfigStore:
|
|
|
global _config_store
|
|
|
if _config_store is None:
|
|
|
_config_store = ConfigStore()
|
|
|
return _config_store
|
|
|
```
|
|
|
|
|
|
## 5. 数据迁移
|
|
|
|
|
|
### 5.1 迁移时机
|
|
|
|
|
|
应用启动时(`lifespan`),在 MySQL 表结构初始化后执行。
|
|
|
|
|
|
### 5.2 迁移逻辑
|
|
|
|
|
|
```python
|
|
|
def migrate_configs_to_mysql():
|
|
|
for key, file_path, default in CONFIGS:
|
|
|
if _config_exists_in_mysql(key):
|
|
|
continue
|
|
|
value = _load_json(file_path, default)
|
|
|
_save_to_mysql(key, value)
|
|
|
```
|
|
|
|
|
|
### 5.3 幂等性
|
|
|
|
|
|
- 检测 `app_config` 是否已有对应 key
|
|
|
- 有则跳过
|
|
|
|
|
|
## 6. 使用方修改
|
|
|
|
|
|
### 6.1 品种配置读取
|
|
|
|
|
|
替换以下文件中的 `_load_symbols_config()` / `load_symbols_config()`:
|
|
|
|
|
|
- `app/api/config.py`
|
|
|
- `app/api/futures_analysis.py`
|
|
|
- `app/api/trade_review.py`
|
|
|
- `app/services/trade_parser.py`
|
|
|
- `app/services/plan_generator.py`
|
|
|
|
|
|
统一使用:
|
|
|
|
|
|
```python
|
|
|
from app.config_store import get_config_store
|
|
|
|
|
|
symbols = get_config_store().get_config("symbols", {"futures": {}, "stock": {}})
|
|
|
```
|
|
|
|
|
|
### 6.2 AI 配置读取
|
|
|
|
|
|
替换 `app/api/ai_config.py` 和 `app/services/ai_analysis.py` 中的 `_load_ai_config()`:
|
|
|
|
|
|
```python
|
|
|
ai_config = get_config_store().get_config("ai", {...default...})
|
|
|
```
|
|
|
|
|
|
### 6.3 写入接口
|
|
|
|
|
|
`app/api/config.py` 和 `app/api/ai_config.py` 的保存接口改为:
|
|
|
|
|
|
```python
|
|
|
get_config_store().set_config("symbols", data)
|
|
|
```
|
|
|
|
|
|
## 7. 兼容性
|
|
|
|
|
|
- 保持 JSON 文件路径和 schema 不变
|
|
|
- API 接口签名不变
|
|
|
- 默认值与现有 JSON 默认值一致
|
|
|
|
|
|
## 8. 测试策略
|
|
|
|
|
|
### 8.1 单元测试
|
|
|
|
|
|
| 场景 | 测试 |
|
|
|
|------|------|
|
|
|
| MySQL 命中 | `get_config` 从数据库返回值 |
|
|
|
| MySQL 未命中 | `get_config` 读取 JSON 并回填 |
|
|
|
| MySQL 不可用 | `get_config` 读取 JSON |
|
|
|
| 写入 | `set_config` 双写数据库和文件 |
|
|
|
| 迁移幂等 | 数据库已有数据时跳过 |
|
|
|
|
|
|
### 8.2 集成测试
|
|
|
|
|
|
- 启动应用验证配置表创建
|
|
|
- 验证 API 读写正常
|
|
|
- 运行全部测试套件确保无回归
|
|
|
|
|
|
## 9. 风险与缓解
|
|
|
|
|
|
| 风险 | 缓解措施 |
|
|
|
|------|----------|
|
|
|
| MySQL 不可用时配置无法写入数据库 | 始终同步写入 JSON 文件 |
|
|
|
| 多处修改引入回归 | 统一 `ConfigStore` 接口 + 全面测试 |
|
|
|
| JSON 文件与数据库不一致 | 写入时双写;读取时优先数据库 |
|