上传素材管控-历史素材success

This commit is contained in:
2026-07-08 16:25:03 +08:00
parent 657505e63b
commit d085a6d2ec
47 changed files with 5424 additions and 447 deletions
+197 -21
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import datetime
from types import SimpleNamespace
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query
from fastapi import APIRouter, Body, Depends, File, HTTPException, Path, Query, UploadFile
from sqlalchemy import inspect as sa_inspect
from sqlalchemy.ext.asyncio import AsyncSession
@@ -35,6 +35,7 @@ from app.schemas.shot_replicate import (
ShotReplicateTaskDetailOut,
ShotReplicateVideoPromptSchemaUpdateRequest,
ShotSegmentDeleteOut,
ShotTaskSetDeleteOut,
ShotSegmentDetailOut,
ShotSegmentListOut,
ShotSegmentReplicationCreateRequest,
@@ -49,7 +50,6 @@ from app.schemas.shot_replicate import (
from app.services.shot_replicate_flow_service import (
_get_project_for_user,
create_shot_replicate_project_from_segment,
delete_shot_replicate_project,
generate_image_from_prompt,
generate_video_from_prompt,
mark_shot_replicate_step_dispatch_failed,
@@ -65,6 +65,7 @@ from app.services.shot_replicate_taskset_service import (
create_segments_by_ai,
create_task_set,
delete_segment,
delete_task_set,
get_segment_for_user,
list_segments,
list_task_sets,
@@ -83,6 +84,9 @@ from app.services.module_async_recovery_service import (
register_shot_task_set_analysis_task,
)
from app.tasks.celery_app import celery_app
from app.enums.upload_resource import UploadResourceEventEnum, UploadResourceModuleEnum, UploadResourceSourceModelEnum, UploadResourceTypeEnum
from app.services.upload_resource import upload_reference_file, bind_upload_resources, cleanup_upload_resource_files_after_commit
from app.services.upload_resource.log_service import log_upload_resource_exception, safe_rollback_with_log
MODULE = ModuleCodeEnum.SHOT_REPLICATE.value
@@ -245,6 +249,66 @@ async def get_spec():
return ShotReplicateSpecOut()
@router.post(
"/upload-image",
summary="上传拆镜复刻图片素材",
description="上传后先记录为 pending UploadResource;创建拆镜复刻项目后绑定业务记录。",
)
async def upload_shot_replicate_image(
file: UploadFile = File(...),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await upload_reference_file(
db,
file=file,
current_user=current_user,
module=UploadResourceModuleEnum.SHOT_REPLICATE.value,
resource_type=UploadResourceTypeEnum.IMAGE.value,
gen_type="video",
)
await db.commit()
return {
"url": result.url,
"filename": result.filename,
"type": "image",
"module": result.module,
"resource_id": result.resource_id,
"file_size_bytes": result.file_size_bytes,
}
@router.post(
"/upload-video",
summary="上传拆镜复刻视频素材",
description="上传后先记录为 pending UploadResource;创建拆镜总任务集后绑定 ShotReplicateTaskSet。",
)
async def upload_shot_replicate_video(
file: UploadFile = File(...),
duration_seconds: float | None = Query(None, description="前端识别的视频时长秒数,可选"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await upload_reference_file(
db,
file=file,
current_user=current_user,
module=UploadResourceModuleEnum.SHOT_REPLICATE.value,
resource_type=UploadResourceTypeEnum.VIDEO.value,
duration_seconds=duration_seconds,
)
await db.commit()
return {
"url": result.url,
"filename": result.filename,
"type": "video",
"module": result.module,
"resource_id": result.resource_id,
"file_size_bytes": result.file_size_bytes,
"duration_seconds": result.duration_seconds,
}
@router.post(
"/task-sets",
response_model=ShotTaskSetDetailOut,
@@ -264,6 +328,16 @@ async def create_shot_task_set(
try:
task_set = await create_task_set(db, current_user=current_user, req=req)
task_set_id = task_set.id
await bind_upload_resources(
db,
user_id=current_user.id,
module=UploadResourceModuleEnum.SHOT_REPLICATE.value,
source_model=UploadResourceSourceModelEnum.SHOT_REPLICATE_TASK_SET.value,
source_id=task_set_id,
resource_ids=[req.video_resource_id],
urls=[req.video_url],
allow_common_migrate=True,
)
await db.commit()
except HTTPException:
await db.rollback()
@@ -626,18 +700,69 @@ async def delete_shot_segment(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
user_id = _safe_user_id(current_user)
pending_ids: list[str] = []
try:
out = await delete_segment(db, current_user=current_user, segment_id=segment_id)
pending_ids = list(out.pending_delete_resource_ids or [])
await db.commit()
return out
except HTTPException:
await db.rollback()
except HTTPException as exc:
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_SEGMENT_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜片段 HTTPException 回滚失败",
user_id=user_id,
module=MODULE,
detail={"segment_id": segment_id},
original_exc=exc,
)
raise
except Exception as exc:
await db.rollback()
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_SEGMENT_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜片段主事务回滚失败",
user_id=user_id,
module=MODULE,
detail={"segment_id": segment_id},
original_exc=exc,
)
_log_api_exception_from_locals(exc, locals(), f"删除拆镜片段失败: {exc}")
log_upload_resource_exception(
event_type=UploadResourceEventEnum.SHOT_SEGMENT_DELETE_FAILED.value,
message=f"删除拆镜片段失败: {exc}",
user_id=user_id,
module=MODULE,
detail={"segment_id": segment_id},
exc=exc,
)
raise HTTPException(status_code=500, detail=f"删除拆镜片段失败: {exc}")
if pending_ids:
try:
await cleanup_upload_resource_files_after_commit(db, resource_ids=pending_ids)
await db.commit()
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_SEGMENT_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜片段 cleanup 事务回滚失败",
user_id=user_id,
module=MODULE,
detail={"segment_id": segment_id, "pending_ids": pending_ids},
original_exc=exc,
)
log_upload_resource_exception(
event_type=UploadResourceEventEnum.SHOT_SEGMENT_DELETE_CLEANUP_FAILED.value,
message=f"删除拆镜片段后清理真实文件失败: {exc}",
user_id=user_id,
resource_ids=pending_ids,
module=MODULE,
detail={"segment_id": segment_id},
exc=exc,
)
return out
@router.post(
"/segments/{segment_id}/replication-projects",
@@ -950,24 +1075,75 @@ async def generate_video(
@router.delete(
"/projects/{project_id}",
response_model=ShotReplicateDeleteOut,
summary="软删除拆镜复刻项目",
description="软删除拆镜复刻 ModuleGenerationProject,并联动软删除当前有效步骤和关联的 ChatGenerationTask",
"/task-sets/{task_set_id}",
response_model=ShotTaskSetDeleteOut,
summary="软删除拆镜任务集",
description="软删除整个 ShotReplicateTaskSet,并联动软删除全部片段和片段内部复刻项目;UploadResource 真实文件在事务提交后清理",
)
async def delete_project(
project_id: str = Path(..., description="拆镜复刻项目ID,即 module_generation_projects.id"),
async def delete_shot_task_set(
task_set_id: str = Path(..., description="拆镜任务集ID,即 shot_replicate_task_sets.id"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
user_id = _safe_user_id(current_user)
pending_ids: list[str] = []
try:
out = await delete_shot_replicate_project(db, current_user=current_user, project_id=project_id)
out = await delete_task_set(db, current_user=current_user, task_set_id=task_set_id)
pending_ids = list(out.pending_delete_resource_ids or [])
await db.commit()
return out
except HTTPException:
await db.rollback()
except HTTPException as exc:
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_TASK_SET_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜任务集 HTTPException 回滚失败",
user_id=user_id,
module=MODULE,
detail={"task_set_id": task_set_id},
original_exc=exc,
)
raise
except Exception as exc:
await db.rollback()
_log_api_exception_from_locals(exc, locals(), f"删除拆镜复刻项目失败: {exc}")
raise HTTPException(status_code=500, detail=f"删除拆镜复刻项目失败: {exc}")
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_TASK_SET_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜任务集主事务回滚失败",
user_id=user_id,
module=MODULE,
detail={"task_set_id": task_set_id},
original_exc=exc,
)
_log_api_exception_from_locals(exc, locals(), f"删除拆镜任务集失败: {exc}")
log_upload_resource_exception(
event_type=UploadResourceEventEnum.SHOT_TASK_SET_DELETE_FAILED.value,
message=f"删除拆镜任务集失败: {exc}",
user_id=user_id,
module=MODULE,
detail={"task_set_id": task_set_id},
exc=exc,
)
raise HTTPException(status_code=500, detail=f"删除拆镜任务集失败: {exc}")
if pending_ids:
try:
await cleanup_upload_resource_files_after_commit(db, resource_ids=pending_ids)
await db.commit()
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.SHOT_TASK_SET_DELETE_ROLLBACK_FAILED.value,
message="删除拆镜任务集 cleanup 事务回滚失败",
user_id=user_id,
module=MODULE,
detail={"task_set_id": task_set_id, "pending_ids": pending_ids},
original_exc=exc,
)
log_upload_resource_exception(
event_type=UploadResourceEventEnum.SHOT_TASK_SET_DELETE_CLEANUP_FAILED.value,
message=f"删除拆镜任务集后清理真实文件失败: {exc}",
user_id=user_id,
resource_ids=pending_ids,
module=MODULE,
detail={"task_set_id": task_set_id},
exc=exc,
)
return out