Files
video-gen/video-gen-api/app/cmd/backfill_upload_resources.py
T

89 lines
3.8 KiB
Python

from __future__ import annotations
import argparse
import asyncio
import json
import sys
from app.models.base import async_session
from app.enums.upload_resource import UploadResourceEventEnum
from app.services.upload_resource.backfill_service import BackfillOptions, run_upload_resource_backfill
from app.services.upload_resource.log_service import log_upload_resource_exception, safe_rollback_with_log
VALID_MODULES = {"common", "hot_opening_replicate", "shot_replicate"}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="补录 storage/uploads 下的上传资源账本,并可重绑模块归属。")
parser.add_argument("--root", default="storage/uploads", help="扫描根目录,默认 storage/uploads")
parser.add_argument("--batch-size", type=int, default=500, help="每批处理数量,默认 500")
parser.add_argument("--dry-run", action="store_true", help="试跑,不写数据库")
parser.add_argument("--commit", action="store_true", help="真实执行写入")
parser.add_argument("--include-legacy", action="store_true", help="补录 gen_{user_id}_* 等旧版散落文件")
parser.add_argument("--rebind-modules", action="store_true", help="根据 hot/shot 业务表中保存的 URL 重新绑定模块归属")
parser.add_argument("--rebuild-stats", action="store_true", help="补录完成后按资源账本重算容量统计")
parser.add_argument("--only-user-id", default=None, help="只处理指定用户ID")
parser.add_argument("--only-module", choices=sorted(VALID_MODULES), default=None, help="只处理指定模块")
parser.add_argument("--cleanup-pending-files", action="store_true", help="清理已软删但真实文件待删除/删除失败的 UploadResource 文件")
parser.add_argument("--cleanup-limit", type=int, default=500, help="每次最多清理待删除真实文件数量,默认 500")
return parser
async def amain(argv: list[str]) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.dry_run and args.commit:
parser.error("--dry-run 和 --commit 不能同时使用")
if not args.dry_run and not args.commit:
args.dry_run = True
if args.batch_size <= 0:
parser.error("--batch-size 必须大于 0")
options = BackfillOptions(
root=args.root,
batch_size=args.batch_size,
dry_run=not args.commit,
include_legacy=args.include_legacy,
rebind_modules=args.rebind_modules,
rebuild_stats=args.rebuild_stats,
only_user_id=args.only_user_id,
only_module=args.only_module,
cleanup_pending_files=args.cleanup_pending_files,
cleanup_limit=args.cleanup_limit,
)
async with async_session() as db:
try:
result = await run_upload_resource_backfill(db, options)
if options.dry_run:
await db.rollback()
else:
await db.commit()
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.UPLOAD_RESOURCE_BACKFILL_CLI_ROLLBACK_FAILED.value,
message="UploadResource 补录/清理 CLI 回滚失败",
detail={"argv": argv, "options": vars(args)},
original_exc=exc,
)
log_upload_resource_exception(
event_type=UploadResourceEventEnum.UPLOAD_RESOURCE_BACKFILL_CLI_FAILED.value,
message=f"UploadResource 补录/清理 CLI 执行失败: {exc}",
detail={"argv": argv, "options": vars(args)},
exc=exc,
)
raise
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2, default=str))
return 0
def main() -> int:
return asyncio.run(amain(sys.argv[1:]))
if __name__ == "__main__":
raise SystemExit(main())