76 lines
2.6 KiB
Python
76 lines
2.6 KiB
Python
#!/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)))
|