上传素材管控-历史素材success
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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
|
||||
Reference in New Issue
Block a user