162 lines
6.3 KiB
Python
162 lines
6.3 KiB
Python
"""迁移 019: 修复已有 schema 和数据问题。
|
||
|
||
修复内容:
|
||
1. 转换 slides_config_json 中的对象格式 {"slides": [...]} 为数组格式 [...]
|
||
2. 为 poster_records 表添加生成方式追踪字段
|
||
3. 为常用查询添加索引
|
||
"""
|
||
import json
|
||
import logging
|
||
from sqlalchemy import text, inspect
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def migrate():
|
||
"""执行迁移。"""
|
||
from insurance.db.compat import db
|
||
dialect = db.engine.dialect.name
|
||
|
||
_fix_slides_config_json(db)
|
||
_add_poster_record_fields(db, dialect)
|
||
_add_indexes(db, dialect)
|
||
db.session.commit()
|
||
|
||
|
||
def _column_exists(db, table_name: str, column_name: str) -> bool:
|
||
"""检查列是否存在。"""
|
||
dialect = db.engine.dialect.name
|
||
if dialect == "postgresql":
|
||
result = db.session.execute(text(
|
||
"SELECT COUNT(*) FROM information_schema.columns "
|
||
"WHERE table_name = :table AND column_name = :column"
|
||
), {"table": table_name, "column": column_name})
|
||
return result.scalar() > 0
|
||
else:
|
||
try:
|
||
result = db.session.execute(text(f"PRAGMA table_info({table_name})"))
|
||
return any(row[1] == column_name for row in result.fetchall())
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
def _fix_slides_config_json(db):
|
||
"""将对象格式 {"slides": [...]} 转换为数组格式 [...]。
|
||
|
||
历史数据中 migrate_017 写入的是 {"slides": [...]},
|
||
但渲染器和前端均期望数组格式。
|
||
"""
|
||
templates = db.session.execute(text(
|
||
"SELECT id, slides_config_json FROM insurance_ppt_templates "
|
||
"WHERE slides_config_json IS NOT NULL"
|
||
)).fetchall()
|
||
|
||
fixed_count = 0
|
||
for tpl_id, config_json in templates:
|
||
if not config_json or not config_json.strip():
|
||
continue
|
||
try:
|
||
config = json.loads(config_json)
|
||
except (json.JSONDecodeError, TypeError):
|
||
logger.warning(f"模板 {tpl_id} 的 slides_config_json 无法解析,跳过")
|
||
continue
|
||
|
||
# 如果是对象格式 {"slides": [...]},转换为数组
|
||
if isinstance(config, dict) and "slides" in config:
|
||
slides_array = config["slides"]
|
||
if isinstance(slides_array, list):
|
||
db.session.execute(text(
|
||
"UPDATE insurance_ppt_templates SET slides_config_json = :config WHERE id = :id"
|
||
), {"config": json.dumps(slides_array, ensure_ascii=False), "id": tpl_id})
|
||
fixed_count += 1
|
||
logger.info(f"模板 {tpl_id}: 对象格式已转换为数组格式")
|
||
else:
|
||
logger.warning(f"模板 {tpl_id}: slides 值不是数组,跳过")
|
||
elif isinstance(config, list):
|
||
pass # 已经是数组格式,无需处理
|
||
else:
|
||
logger.warning(f"模板 {tpl_id}: 未知格式 {type(config).__name__},跳过")
|
||
|
||
if fixed_count > 0:
|
||
logger.info(f"已修复 {fixed_count} 个模板的 slides_config_json 格式")
|
||
|
||
|
||
def _add_poster_record_fields(db, dialect):
|
||
"""为 poster_records 表添加生成方式追踪和任务状态字段。"""
|
||
new_columns = [
|
||
("generation_mode", "VARCHAR(20)", "生成方式: ai/fallback"),
|
||
("image_provider", "VARCHAR(50)", "图片供应商"),
|
||
("image_model", "VARCHAR(100)", "图片模型名称"),
|
||
("task_status", "VARCHAR(20)", "任务状态: pending/queued/generating/done/failed"),
|
||
("task_progress", "INTEGER", "任务进度 0-100"),
|
||
("task_error", "TEXT", "任务错误信息"),
|
||
("started_at", "TIMESTAMP", "任务开始时间"),
|
||
("finished_at", "TIMESTAMP", "任务完成时间"),
|
||
]
|
||
|
||
for col_name, col_type, comment in new_columns:
|
||
if _column_exists(db, "poster_records", col_name):
|
||
logger.debug(f"poster_records.{col_name} 已存在,跳过")
|
||
continue
|
||
|
||
try:
|
||
db.session.execute(text(
|
||
f"ALTER TABLE poster_records ADD COLUMN {col_name} {col_type}"
|
||
))
|
||
db.session.commit()
|
||
logger.info(f"已添加 poster_records.{col_name} ({comment})")
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
logger.warning(f"添加 poster_records.{col_name} 失败(已回滚,不影响其他字段): {e}")
|
||
|
||
|
||
def _add_indexes(db, dialect):
|
||
"""为常用查询添加索引。"""
|
||
indexes = [
|
||
("idx_poster_records_user_id", "poster_records", ["user_id"]),
|
||
("idx_poster_records_created_at", "poster_records", ["created_at"]),
|
||
("idx_poster_case_uploads_user_id", "poster_case_uploads", ["user_id"]),
|
||
("idx_ppt_history_user_id", "insurance_ppt_history", ["user_id"]),
|
||
("idx_ppt_history_created_at", "insurance_ppt_history", ["created_at"]),
|
||
("idx_system_settings_key", "system_settings", ["key"]),
|
||
]
|
||
|
||
for idx_name, table_name, columns in indexes:
|
||
try:
|
||
# 检查表是否存在
|
||
if dialect == "postgresql":
|
||
result = db.session.execute(text(
|
||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = :table"
|
||
), {"table": table_name})
|
||
if result.scalar() == 0:
|
||
continue
|
||
else:
|
||
try:
|
||
db.session.execute(text(f"SELECT 1 FROM {table_name} LIMIT 0"))
|
||
except Exception:
|
||
continue
|
||
|
||
# 检查索引是否已存在
|
||
if dialect == "postgresql":
|
||
result = db.session.execute(text(
|
||
"SELECT COUNT(*) FROM pg_indexes WHERE indexname = :idx"
|
||
), {"idx": idx_name})
|
||
if result.scalar() > 0:
|
||
continue
|
||
else:
|
||
try:
|
||
result = db.session.execute(text(
|
||
"SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name = :idx"
|
||
), {"idx": idx_name})
|
||
if result.scalar() > 0:
|
||
continue
|
||
except Exception:
|
||
continue
|
||
|
||
cols = ", ".join(columns)
|
||
db.session.execute(text(f"CREATE INDEX {idx_name} ON {table_name} ({cols})"))
|
||
logger.info(f"已创建索引 {idx_name}")
|
||
except Exception as e:
|
||
logger.warning(f"创建索引 {idx_name} 失败: {e}")
|
||
db.session.rollback()
|