Files
video-gen/video-gen-api/app/services/upload_resource/delete_service.py
T

137 lines
5.1 KiB
Python

from __future__ import annotations
from datetime import datetime, timezone
from typing import Iterable
from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.enums.upload_resource import UploadResourceEventEnum, UploadResourceFileDeleteStatusEnum
from app.models.upload_resource import UploadResource
from app.models.user import User
from app.schemas.upload_resource import (
UploadResourceCleanupOut,
UploadResourceHistoryBatchDeleteOut,
)
from app.services.upload_resource.accounting_service import release_upload_resource_capacity
from app.services.upload_resource.file_cleanup_service import cleanup_upload_resource_files_after_commit
from app.services.upload_resource.history_service import assert_upload_resource_history_ids_visible
from app.services.upload_resource.log_service import log_upload_resource_event
MAX_UPLOAD_RESOURCE_BATCH_DELETE_COUNT = 30
def normalize_upload_resource_ids(values: Iterable[str | None]) -> list[str]:
ids = [str(value).strip() for value in values if str(value or "").strip()]
if not ids:
raise HTTPException(status_code=400, detail="resource_ids 不能为空")
if len(ids) > MAX_UPLOAD_RESOURCE_BATCH_DELETE_COUNT:
raise HTTPException(status_code=400, detail=f"单次最多删除 {MAX_UPLOAD_RESOURCE_BATCH_DELETE_COUNT} 条上传素材")
if len(ids) != len(set(ids)):
raise HTTPException(status_code=400, detail="resource_ids 不允许重复")
return ids
def _cleanup_out(stats: dict[str, int] | None) -> UploadResourceCleanupOut:
stats = stats or {}
return UploadResourceCleanupOut(
matched=int(stats.get("matched", 0) or 0),
deleted=int(stats.get("deleted", 0) or 0),
missing=int(stats.get("missing", 0) or 0),
failed=int(stats.get("failed", 0) or 0),
legacy_deleted=int(stats.get("legacy_deleted", 0) or 0),
legacy_missing=int(stats.get("legacy_missing", 0) or 0),
legacy_failed=int(stats.get("legacy_failed", 0) or 0),
)
async def mark_upload_resource_history_deleted(
db: AsyncSession,
*,
current_user: User,
resource_ids: Iterable[str | None],
) -> UploadResourceHistoryBatchDeleteOut:
"""主事务内软删上传历史素材并释放容量,不删除真实文件。"""
ids = normalize_upload_resource_ids(resource_ids)
user_id = str(current_user.id)
log_upload_resource_event(
event_type=UploadResourceEventEnum.DELETE_BATCH_START.value,
user_id=user_id,
detail={"requested_ids": ids, "requested_count": len(ids)},
)
resources = await assert_upload_resource_history_ids_visible(db, user_id=user_id, resource_ids=ids)
now = datetime.now(timezone.utc)
# 先抽 primitive 快照,commit/rollback 后日志不再碰 ORM,规避懒加载失效风险。
snapshots = [
{
"id": resource.id,
"user_id": resource.user_id,
"module": resource.module,
"resource_type": resource.resource_type,
"resource_url": resource.resource_url,
"storage_path": resource.storage_path,
"file_size_bytes": int(resource.file_size_bytes or 0),
}
for resource in resources
]
released_size = 0
released_ids: list[str] = []
for resource in resources:
resource.deleted_at = now
resource.file_delete_status = UploadResourceFileDeleteStatusEnum.PENDING_DELETE.value
resource.file_delete_error = None
released = await release_upload_resource_capacity(db, resource, released_at=now)
if released:
released_size += int(resource.file_size_bytes or 0)
released_ids.append(resource.id)
await db.flush()
log_upload_resource_event(
event_type=UploadResourceEventEnum.DELETE_BATCH_MARKED_PENDING.value,
user_id=user_id,
detail={
"requested_ids": ids,
"deleted_ids": ids,
"released_ids": released_ids,
"released_size_bytes": released_size,
"snapshots": snapshots,
"file_delete_status": UploadResourceFileDeleteStatusEnum.PENDING_DELETE.value,
},
)
return UploadResourceHistoryBatchDeleteOut(
message="删除成功",
requested_count=len(ids),
deleted_count=len(ids),
requested_ids=ids,
deleted_ids=ids,
released_size_bytes=released_size,
cleanup=UploadResourceCleanupOut(),
)
async def cleanup_upload_resource_history_files(
db: AsyncSession,
*,
result: UploadResourceHistoryBatchDeleteOut,
current_user: User,
) -> UploadResourceHistoryBatchDeleteOut:
"""主事务 commit 成功后清理真实文件,失败不回滚主删除。"""
stats = await cleanup_upload_resource_files_after_commit(db, resource_ids=result.deleted_ids)
result.cleanup = _cleanup_out(stats)
log_upload_resource_event(
event_type=UploadResourceEventEnum.DELETE_BATCH_CLEANUP_SUCCESS.value,
user_id=str(current_user.id),
detail={
"requested_ids": result.requested_ids,
"deleted_ids": result.deleted_ids,
"cleanup": result.cleanup.model_dump(),
},
)
return result