You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/docs/superpowers/specs/2026-07-04-config-to-mysql-...

190 lines
4.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
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 文件与数据库不一致 | 写入时双写;读取时优先数据库 |