Files
xiaoxia-saas/scripts/recount_asset_counts.py
xiaoxia 5678779232
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 12s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 12s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m58s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m1s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m2s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 2m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m56s
CI/CD Pipeline / Build Staging API Image (push) Successful in 59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m9s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 13s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m5s
AI Code Review / AI Code Review (pull_request) Successful in 6m29s
CI/CD Pipeline / Validate - Security (push) Successful in 6m24s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m53s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 8m1s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m40s
CI/CD Pipeline / Unit Tests (push) Failing after 10m13s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
feat: #1776 素材库 asset_count 自动同步维护 (#1787)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 10:30:21 +08:00

160 lines
5.1 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""素材库计数重算脚本(Issue #1776)。
用法:
# Dry-run: 输出差异清单,不执行修改
python scripts/recount_asset_counts.py --dry-run
# 执行修正
python scripts/recount_asset_counts.py
# 只处理指定项目
python scripts/recount_asset_counts.py --project-id <project_id>
# 只处理指定素材库
python scripts/recount_asset_counts.py --library-id <library_id>
"""
import argparse
import sys
from pathlib import Path
# 添加项目根目录到 path
sys.path.insert(0, str(Path(__file__).parent.parent))
from sqlalchemy import create_engine, func
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import AssetLibraryModel, AssetModel
def get_db_session() -> Session:
"""创建数据库 session。"""
import os
database_url = os.getenv("DATABASE_URL")
if not database_url:
print("ERROR: DATABASE_URL environment variable not set")
sys.exit(1)
engine = create_engine(database_url)
return Session(engine)
def check_discrepancies(session: Session, project_id: str | None = None, library_id: str | None = None) -> list[dict]:
"""检查素材库计数差异。
返回列表,每项包含:
- library_id: 素材库 ID
- library_name: 素材库名称
- recorded_count: 记录的计数
- actual_count: 实际计数
- delta: 差异 (actual - recorded)
"""
query = session.query(AssetLibraryModel)
if project_id:
query = query.filter(AssetLibraryModel.project_id == project_id)
if library_id:
query = query.filter(AssetLibraryModel.id == library_id)
libraries = query.all()
discrepancies = []
for lib in libraries:
# 查询实际计数(排除 deleted)
actual_count = (
session.query(func.count(AssetModel.id))
.filter(
AssetModel.asset_library_id == lib.id,
AssetModel.status != "deleted",
)
.scalar()
or 0
)
actual_size = (
session.query(func.coalesce(func.sum(AssetModel.file_size), 0))
.filter(
AssetModel.asset_library_id == lib.id,
AssetModel.status != "deleted",
)
.scalar()
or 0
)
recorded_count = int(lib.asset_count or 0)
recorded_size = int(lib.total_size or 0)
if actual_count != recorded_count or actual_size != recorded_size:
discrepancies.append(
{
"library_id": lib.id,
"library_name": lib.name,
"project_id": lib.project_id,
"kind": lib.kind,
"recorded_count": recorded_count,
"actual_count": actual_count,
"count_delta": actual_count - recorded_count,
"recorded_size": recorded_size,
"actual_size": actual_size,
"size_delta": actual_size - recorded_size,
}
)
return discrepancies
def fix_discrepancies(session: Session, discrepancies: list[dict]) -> int:
"""修正素材库计数。返回修正数量。"""
fixed = 0
for d in discrepancies:
session.query(AssetLibraryModel).filter(AssetLibraryModel.id == d["library_id"]).update(
{
AssetLibraryModel.asset_count: d["actual_count"],
AssetLibraryModel.total_size: d["actual_size"],
}
)
fixed += 1
session.commit()
return fixed
def main():
parser = argparse.ArgumentParser(description="素材库计数重算脚本(Issue #1776")
parser.add_argument("--dry-run", action="store_true", help="只输出差异清单,不执行修正")
parser.add_argument("--project-id", type=str, help="只处理指定项目")
parser.add_argument("--library-id", type=str, help="只处理指定素材库")
args = parser.parse_args()
session = get_db_session()
try:
discrepancies = check_discrepancies(session, args.project_id, args.library_id)
if not discrepancies:
print("✅ 所有素材库计数一致,无需修正")
return
# 输出差异清单
print(f"发现 {len(discrepancies)} 个素材库计数不一致:\n")
print(f"{'Library ID':<40} {'Name':<20} {'Recorded':<10} {'Actual':<10} {'Delta':<10}")
print("-" * 90)
for d in discrepancies:
print(
f"{d['library_id']:<40} {d['library_name'][:20]:<20} {d['recorded_count']:<10} {d['actual_count']:<10} {d['count_delta']:+<10}"
)
total_delta = sum(d["count_delta"] for d in discrepancies)
print(f"\n总计差异: {total_delta:+d}")
if args.dry_run:
print("\n[DRY-RUN] 未执行修正。移除 --dry-run 参数以执行修正。")
else:
print("\n正在执行修正...")
fixed = fix_discrepancies(session, discrepancies)
print(f"✅ 已修正 {fixed} 个素材库计数")
finally:
session.close()
if __name__ == "__main__":
main()