#!/usr/bin/env python3 # -*- coding: utf-8 -*- """存量用户默认头像同步脚本。 给「头像为空」的存量用户随机分配一个默认头像(来自 avatar_pool.json 池)。 已有真实头像(非空、非默认池前缀)的用户不会被覆盖。 用法: python scripts/sync_default_avatars.py # 实际执行 python scripts/sync_default_avatars.py --dry-run # 只预览不写库 """ from __future__ import annotations import argparse import asyncio from pathlib import Path from dotenv import load_dotenv load_dotenv(Path(__file__).resolve().parent.parent / ".env") from sqlalchemy import text # noqa: E402 from app.infrastructure.db import AsyncSessionLocal # noqa: E402 from app.infrastructure.repositories import utcnow_iso # noqa: E402 from app.services.avatar_pool import random_default_avatar # noqa: E402 DEFAULT_POOL_PREFIX = "/oss/avatar/images/images/" async def main(dry_run: bool) -> int: async with AsyncSessionLocal() as session: # 仅处理头像为空(或 NULL)的存量用户;已设置真实头像的不动 rows = ( await session.execute( text( "SELECT id, nickname, username FROM users " "WHERE avatar IS NULL OR avatar = ''" ) ) ).all() total = len(rows) print(f"待同步用户数(头像为空): {total}") if total == 0: print("无需同步。") return 0 updated = 0 for row in rows: avatar = random_default_avatar() if not avatar: print("警告:默认头像池为空,跳过(请检查 serverdata/avatar_pool.json)") return 1 print( f" {'[DRY] ' if dry_run else '[set] '}{row.id} " f"({row.nickname or row.username or ''}) -> {avatar}" ) if not dry_run: await session.execute( text("UPDATE users SET avatar = :av, updated_at = :now WHERE id = :uid"), {"av": avatar, "uid": row.id, "now": utcnow_iso()}, ) updated += 1 if not dry_run: await session.commit() print(f"完成:{'(dry-run 未写库)' if dry_run else ''}计划/已更新 {updated} 人。") return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description="存量用户默认头像同步") parser.add_argument("--dry-run", action="store_true", help="只预览不写库") args = parser.parse_args() raise SystemExit(asyncio.run(main(args.dry_run)))