| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- """
- 迁移脚本:给老用户补齐默认 /chatbot 场景(幂等)
- 默认规则:
- - 幕布:fresh_green_wallpaper
- - slot_04:stump_stool
- - slot_03:french_window
- 并确保用户拥有:
- - fresh_green_wallpaper
- - stump_stool
- - french_window
- """
- import asyncio
- import sys
- from pathlib import Path
- from typing import Any
- import aiomysql
- backend_dir = Path(__file__).resolve().parents[1]
- if str(backend_dir) not in sys.path:
- sys.path.insert(0, str(backend_dir))
- from config import settings
- from shop_item_image_utils import scene_image_name_from_shop_row
- REQUIRED_TABLES = [
- "users",
- "shop_items",
- "user_assets",
- "user_chatbot_backdrops",
- "user_chatbot_slot_placements",
- ]
- ITEM_CODES = {
- "backdrop": "fresh_green_wallpaper",
- "seat": "stump_stool",
- "window": "french_window",
- }
- async def _table_exists(conn: aiomysql.Connection, table_name: str) -> bool:
- async with conn.cursor() as cursor:
- await cursor.execute(
- """
- SELECT COUNT(*) FROM information_schema.tables
- WHERE table_schema = %s AND table_name = %s
- """,
- (settings.MYSQL_DATABASE, table_name),
- )
- row: Any = await cursor.fetchone()
- return bool(row and row[0] > 0)
- async def _column_exists(conn: aiomysql.Connection, table: str, col: str) -> bool:
- async with conn.cursor() as cursor:
- await cursor.execute(
- """
- SELECT COUNT(*) FROM information_schema.columns
- WHERE table_schema=%s AND table_name=%s AND column_name=%s
- """,
- (settings.MYSQL_DATABASE, table, col),
- )
- row: Any = await cursor.fetchone()
- return bool(row and row[0] > 0)
- async def _col_is_bigint(conn: aiomysql.Connection, table: str, col: str) -> bool:
- async with conn.cursor() as cursor:
- await cursor.execute(
- """
- SELECT DATA_TYPE FROM information_schema.columns
- WHERE table_schema=%s AND table_name=%s AND column_name=%s
- """,
- (settings.MYSQL_DATABASE, table, col),
- )
- row: Any = await cursor.fetchone()
- return bool(row and "bigint" in str(row[0]).lower())
- async def migrate() -> bool:
- conn: aiomysql.Connection | None = None
- try:
- conn = await aiomysql.connect(
- host=settings.MYSQL_HOST,
- port=settings.MYSQL_PORT,
- user=settings.MYSQL_USER,
- password=settings.MYSQL_PASSWORD,
- db=settings.MYSQL_DATABASE,
- charset="utf8mb4",
- autocommit=False,
- )
- missing = [t for t in REQUIRED_TABLES if not await _table_exists(conn, t)]
- if missing:
- print(f"❌ 缺少必要表: {missing}")
- return False
- ua_bigint = await _col_is_bigint(conn, "user_assets", "user_id")
- ub_bigint = await _col_is_bigint(conn, "user_chatbot_backdrops", "user_id")
- up_bigint = await _col_is_bigint(conn, "user_chatbot_slot_placements", "user_id")
- ua_uid_expr = "u.id" if ua_bigint else "u.user_id"
- ub_uid_expr = "u.id" if ub_bigint else "u.user_id"
- up_uid_expr = "u.id" if up_bigint else "u.user_id"
- has_render_col = await _column_exists(conn, "shop_items", "render_image_name")
- async with conn.cursor() as cursor:
- if has_render_col:
- await cursor.execute(
- """
- SELECT item_code, id, image_name, render_image_name, default_z_index
- FROM shop_items
- WHERE item_code IN (%s,%s,%s)
- """,
- (ITEM_CODES["backdrop"], ITEM_CODES["seat"], ITEM_CODES["window"]),
- )
- rows = await cursor.fetchall()
- by_code = {
- r[0]: {
- "id": int(r[1]),
- "image_name": str(r[2]),
- "render_image_name": r[3],
- "z": int(r[4] or 0),
- }
- for r in rows
- }
- else:
- await cursor.execute(
- """
- SELECT item_code, id, image_name, default_z_index
- FROM shop_items
- WHERE item_code IN (%s,%s,%s)
- """,
- (ITEM_CODES["backdrop"], ITEM_CODES["seat"], ITEM_CODES["window"]),
- )
- rows = await cursor.fetchall()
- by_code = {
- r[0]: {
- "id": int(r[1]),
- "image_name": str(r[2]),
- "render_image_name": None,
- "z": int(r[3] or 0),
- }
- for r in rows
- }
- for k, code in ITEM_CODES.items():
- if code not in by_code:
- print(f"❌ shop_items 缺少 {code}")
- return False
- seat = by_code[ITEM_CODES["seat"]]
- window = by_code[ITEM_CODES["window"]]
- backdrop = by_code[ITEM_CODES["backdrop"]]
- # 1) 补 user_assets(拥有默认三件,used_qty 先不抬高)
- for role in ("seat", "window", "backdrop"):
- item_id = by_code[ITEM_CODES[role]]["id"]
- await cursor.execute(
- f"""
- INSERT INTO user_assets
- (user_id, item_id, owned_qty, used_qty, last_acquired_at, created_at, updated_at)
- SELECT {ua_uid_expr}, %s, 1, 0, NOW(), NOW(), NOW()
- FROM users u
- WHERE NOT EXISTS (
- SELECT 1 FROM user_assets ua
- WHERE ua.user_id = {ua_uid_expr} AND ua.item_id = %s
- )
- """,
- (item_id, item_id),
- )
- print("✅ user_assets 默认三件补齐完成")
- # 2) 补默认幕布
- await cursor.execute(
- f"""
- INSERT INTO user_chatbot_backdrops
- (user_id, backdrop_item_id, z_index, created_at, updated_at)
- SELECT {ub_uid_expr}, %s, 0, NOW(), NOW()
- FROM users u
- WHERE NOT EXISTS (
- SELECT 1 FROM user_chatbot_backdrops ub
- WHERE ub.user_id = {ub_uid_expr}
- )
- """,
- (backdrop["id"],),
- )
- print("✅ user_chatbot_backdrops 默认幕布补齐完成")
- # 3) 槽位摆放:slot_04 放 seat;slot_03 放 window
- # 先通过 user_assets 取每个用户对应 user_asset_id
- await cursor.execute(
- f"""
- INSERT INTO user_chatbot_slot_placements
- (user_id, backdrop_item_id, slot_code, user_asset_id, item_id, z_index, image_name_snapshot, placed_at, updated_at)
- SELECT
- {up_uid_expr},
- %s,
- 'slot_04_row4_col2',
- ua.id,
- %s,
- %s,
- %s,
- NOW(),
- NOW()
- FROM users u
- JOIN user_assets ua
- ON ua.user_id = {ua_uid_expr}
- AND ua.item_id = %s
- ON DUPLICATE KEY UPDATE
- user_asset_id=VALUES(user_asset_id),
- item_id=VALUES(item_id),
- z_index=VALUES(z_index),
- image_name_snapshot=VALUES(image_name_snapshot),
- updated_at=NOW()
- """,
- (
- backdrop["id"],
- seat["id"],
- seat["z"],
- scene_image_name_from_shop_row(seat),
- seat["id"],
- ),
- )
- await cursor.execute(
- f"""
- INSERT INTO user_chatbot_slot_placements
- (user_id, backdrop_item_id, slot_code, user_asset_id, item_id, z_index, image_name_snapshot, placed_at, updated_at)
- SELECT
- {up_uid_expr},
- %s,
- 'slot_03_center_2x3',
- ua.id,
- %s,
- %s,
- %s,
- NOW(),
- NOW()
- FROM users u
- JOIN user_assets ua
- ON ua.user_id = {ua_uid_expr}
- AND ua.item_id = %s
- ON DUPLICATE KEY UPDATE
- user_asset_id=VALUES(user_asset_id),
- item_id=VALUES(item_id),
- z_index=VALUES(z_index),
- image_name_snapshot=VALUES(image_name_snapshot),
- updated_at=NOW()
- """,
- (
- backdrop["id"],
- window["id"],
- window["z"],
- scene_image_name_from_shop_row(window),
- window["id"],
- ),
- )
- print("✅ user_chatbot_slot_placements 默认槽位补齐完成")
- # 4) used_qty 与默认摆放同步(seat/window 标记已使用1个)
- await cursor.execute(
- f"""
- UPDATE user_assets ua
- JOIN users u ON ua.user_id = {ua_uid_expr}
- SET ua.used_qty = GREATEST(ua.used_qty, 1), ua.updated_at = NOW()
- WHERE ua.item_id IN (%s,%s)
- """,
- (seat["id"], window["id"]),
- )
- await conn.commit()
- print("🎉 migrate_backfill_default_chatbot_scene 完成")
- return True
- except Exception as exc: # noqa: BLE001
- print(f"❌ 迁移失败: {exc}")
- if conn is not None:
- try:
- await conn.rollback()
- except Exception:
- pass
- return False
- finally:
- if conn is not None:
- conn.close()
- if __name__ == "__main__":
- if len(sys.argv) > 1 and sys.argv[1] == "--force":
- confirm = "y"
- else:
- confirm = input("\n是否继续为老用户回填默认/chatbot场景? (y/n): ").strip().lower()
- if confirm != "y":
- sys.exit(0)
- ok = asyncio.run(migrate())
- sys.exit(0 if ok else 1)
|