|
|
|
|
|
"""
|
|
|
|
|
|
期货智析数据库迁移 - SQLite → MySQL
|
|
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
|
|
|
|
|
|
|
|
from app.analysis_db import analysis_engine, AnalysisBase
|
|
|
|
|
|
from app.analysis_models import (
|
|
|
|
|
|
FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings,
|
|
|
|
|
|
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
|
|
|
|
|
|
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
|
|
|
|
|
|
TradeRecord, TradeImportBatch,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.mysql_database import mysql_engine
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
# 所有 analysis 模型类,按依赖顺序排列
|
|
|
|
|
|
ANALYSIS_MODELS = [
|
|
|
|
|
|
FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings,
|
|
|
|
|
|
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
|
|
|
|
|
|
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
|
|
|
|
|
|
TradeRecord, TradeImportBatch,
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def migrate_analysis_sqlite_to_mysql():
|
|
|
|
|
|
"""
|
|
|
|
|
|
将 SQLite futures_analysis.db 中的数据迁移到 MySQL。
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 迁移是否成功(跳过也视为成功)
|
|
|
|
|
|
"""
|
|
|
|
|
|
if mysql_engine is None:
|
|
|
|
|
|
logger.warning("MySQL 未初始化,跳过 analysis 数据迁移")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
mysql_count = _count_mysql_analysis_records()
|
|
|
|
|
|
if mysql_count > 0:
|
|
|
|
|
|
logger.info(f"MySQL analysis 表已有数据 ({mysql_count} 条),跳过迁移")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("开始将 SQLite analysis 数据迁移到 MySQL...")
|
|
|
|
|
|
data = _read_sqlite_analysis_data()
|
|
|
|
|
|
if not data:
|
|
|
|
|
|
logger.info("SQLite analysis 数据库为空,无需迁移")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
if _write_mysql_analysis_data(data):
|
|
|
|
|
|
total = sum(len(records) for records in data.values())
|
|
|
|
|
|
logger.info(f"Analysis 数据迁移完成,共迁移 {total} 条记录")
|
|
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.error("Analysis 数据迁移失败")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Analysis 数据迁移异常: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _count_mysql_analysis_records():
|
|
|
|
|
|
"""统计 MySQL 中 analysis 表的总记录数。"""
|
|
|
|
|
|
SessionLocal = sessionmaker(bind=mysql_engine)
|
|
|
|
|
|
total = 0
|
|
|
|
|
|
with SessionLocal() as db:
|
|
|
|
|
|
for model in ANALYSIS_MODELS:
|
|
|
|
|
|
try:
|
|
|
|
|
|
total += db.query(model).count()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"统计 MySQL {model.__tablename__} 记录数失败: {e}")
|
|
|
|
|
|
return total
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _read_sqlite_analysis_data():
|
|
|
|
|
|
"""从 SQLite 读取所有 analysis 数据。"""
|
|
|
|
|
|
SessionLocal = sessionmaker(bind=analysis_engine)
|
|
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
|
|
|
|
with SessionLocal() as db:
|
|
|
|
|
|
for model in ANALYSIS_MODELS:
|
|
|
|
|
|
try:
|
|
|
|
|
|
records = db.query(model).all()
|
|
|
|
|
|
if records:
|
|
|
|
|
|
data[model.__tablename__] = records
|
|
|
|
|
|
logger.info(f"从 SQLite 读取 {model.__tablename__}: {len(records)} 条")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"读取 SQLite {model.__tablename__} 失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _write_mysql_analysis_data(data):
|
|
|
|
|
|
"""将 analysis 数据写入 MySQL。"""
|
|
|
|
|
|
SessionLocal = sessionmaker(bind=mysql_engine)
|
|
|
|
|
|
|
|
|
|
|
|
with SessionLocal() as db:
|
|
|
|
|
|
try:
|
|
|
|
|
|
for model in ANALYSIS_MODELS:
|
|
|
|
|
|
records = data.get(model.__tablename__, [])
|
|
|
|
|
|
if not records:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
for record in records:
|
|
|
|
|
|
# 创建新对象,避免绑定到 SQLite session
|
|
|
|
|
|
db.expunge(record)
|
|
|
|
|
|
db.add(record)
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
logger.error(f"写入 MySQL analysis 数据失败: {e}")
|
|
|
|
|
|
return False
|