Files
2026-07-09 09:10:49 +08:00

226 lines
9.6 KiB
Python

from __future__ import annotations
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_user, get_db
from app.enums.upload_resource import UploadResourceEventEnum
from app.models.user import User
from app.schemas.upload_resource import (
UploadResourceHistoryBatchDeleteOut,
UploadResourceHistoryBatchDeleteRequest,
UploadResourceHistoryDayItemsOut,
UploadResourceHistoryGroupedOut,
)
from app.services.upload_resource.delete_service import (
cleanup_upload_resource_history_files,
mark_upload_resource_history_deleted,
)
from app.services.upload_resource.history_service import (
list_upload_resource_history_day_items,
list_upload_resource_history_grouped_days,
)
from app.services.upload_resource.log_service import (
log_upload_resource_exception,
log_upload_resource_event,
safe_rollback_with_log,
)
router = APIRouter(
prefix="/upload-resources",
tags=["upload-resources"],
)
_HISTORY_DESCRIPTION = (
"查询当前用户可展示、可复用、可删除的上传素材历史。"
"只返回 UploadResource 中未删除、未绑定业务记录、bind_status=pending、"
"delete_policy=user_deletable、resource_type=image/video/audio 的记录。"
"已绑定爆款开头复刻、拆镜复刻、拆镜切片或其它业务记录的素材不会返回,也不能通过该模块删除。"
)
@router.get(
"/history",
response_model=UploadResourceHistoryGroupedOut,
summary="获取上传历史素材日期分组",
description=(
_HISTORY_DESCRIPTION
+ "返回结构与 /generation-ai/history 接近,generated_date 表示上传日期。"
"每个日期默认回填前 10 条 items,避免前端首屏再次逐日请求。"
),
responses={
200: {"description": "查询成功,返回上传素材日期分组"},
400: {"description": "resource_type 不合法或分页参数不合法"},
401: {"description": "未登录或 Token 无效"},
},
)
async def list_history_grouped_days(
resource_type: str | None = Query(
None,
description="素材类型筛选:image=图片,video=视频,audio=音频;为空表示全部",
examples=["image"],
),
page: int = Query(1, ge=1, description="日期分组页码,从 1 开始"),
page_size: int = Query(10, ge=1, le=10, description="日期分组每页数量,最大 10"),
keyword: str | None = Query(None, description="文件名或资源 URL 模糊搜索,可为空"),
scene: str | None = Query(
None,
description="前端使用场景标识:record=素材云展示,picker=AI创作选择器;仅用于日志排查,不影响过滤规则",
examples=["picker"],
),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
try:
return await list_upload_resource_history_grouped_days(
db,
user_id=current_user.id,
resource_type=resource_type,
page=page,
page_size=page_size,
keyword=keyword,
)
except Exception as exc: # noqa: BLE001
log_upload_resource_exception(
event_type=UploadResourceEventEnum.HISTORY_LIST_FAILED.value,
message=f"查询上传历史素材日期分组失败: {exc}",
user_id=current_user.id,
detail={"resource_type": resource_type, "page": page, "page_size": page_size, "keyword": keyword, "scene": scene},
exc=exc,
)
raise
@router.get(
"/history/{generated_date}",
response_model=UploadResourceHistoryDayItemsOut,
summary="获取指定日期下上传历史素材",
description=(
_HISTORY_DESCRIPTION
+ "generated_date 格式为 YYYY-MM-DD,字段名沿用生成历史接口,实际语义为上传日期。"
),
responses={
200: {"description": "查询成功,返回指定上传日期下的素材列表"},
400: {"description": "日期格式错误、resource_type 不合法或分页参数不合法"},
401: {"description": "未登录或 Token 无效"},
},
)
async def list_history_day_items(
generated_date: str = Path(..., description="上传日期,格式 YYYY-MM-DD", examples=["2026-07-08"]),
resource_type: str | None = Query(
None,
description="素材类型筛选:image=图片,video=视频,audio=音频;为空表示全部",
examples=["video"],
),
page: int = Query(1, ge=1, description="当前日期下分页页码,从 1 开始"),
page_size: int = Query(20, ge=1, le=100, description="当前日期下每页数量,最大 100"),
keyword: str | None = Query(None, description="文件名或资源 URL 模糊搜索,可为空"),
scene: str | None = Query(
None,
description="前端使用场景标识:record=素材云展示,picker=AI创作选择器;仅用于日志排查,不影响过滤规则",
examples=["record"],
),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
try:
return await list_upload_resource_history_day_items(
db,
user_id=current_user.id,
generated_date=generated_date,
resource_type=resource_type,
page=page,
page_size=page_size,
keyword=keyword,
)
except Exception as exc: # noqa: BLE001
log_upload_resource_exception(
event_type=UploadResourceEventEnum.HISTORY_DAY_LIST_FAILED.value,
message=f"查询指定日期上传历史素材失败: {exc}",
user_id=current_user.id,
detail={"generated_date": generated_date, "resource_type": resource_type, "page": page, "page_size": page_size, "keyword": keyword, "scene": scene},
exc=exc,
)
raise
@router.delete(
"/history/batch",
response_model=UploadResourceHistoryBatchDeleteOut,
summary="批量删除上传历史素材",
description=(
"批量删除当前用户的上传历史素材。一次最多 30 条。"
"只允许删除未绑定业务记录、bind_status=pending、delete_policy=user_deletable 的 UploadResource。"
"删除顺序为:主事务内软删 DB 记录并释放上传容量 -> commit 成功 -> 再删除真实静态文件 -> 写回真实文件删除状态。"
"如果真实文件删除失败,主删除不回滚,file_delete_status 会保留 delete_failed/pending_delete 供后续补偿排查。"
),
responses={
200: {"description": "删除成功,返回软删数量、释放容量和真实文件清理结果"},
400: {"description": "resource_ids 为空、重复或超过 30 条"},
404: {"description": "部分素材不存在、已删除、已绑定业务记录或无权操作"},
401: {"description": "未登录或 Token 无效"},
},
)
async def batch_delete_upload_history(
req: UploadResourceHistoryBatchDeleteRequest = Body(..., description="批量删除上传历史素材请求体"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result: UploadResourceHistoryBatchDeleteOut | None = None
user_id = str(current_user.id)
try:
result = await mark_upload_resource_history_deleted(db, current_user=current_user, resource_ids=req.resource_ids)
await db.commit()
log_upload_resource_event(
event_type=UploadResourceEventEnum.DELETE_BATCH_COMMIT_SUCCESS.value,
user_id=user_id,
detail={
"requested_ids": result.requested_ids,
"deleted_ids": result.deleted_ids,
"released_size_bytes": result.released_size_bytes,
},
)
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.DELETE_BATCH_ROLLBACK_FAILED.value,
message="批量删除上传历史素材主事务回滚失败",
user_id=user_id,
detail={"resource_ids": req.resource_ids},
original_exc=exc,
)
log_upload_resource_exception(
event_type=UploadResourceEventEnum.DELETE_BATCH_FAILED.value,
message=f"批量删除上传历史素材失败: {exc}",
user_id=user_id,
resource_ids=req.resource_ids,
detail={"resource_ids": req.resource_ids},
exc=exc,
)
raise
if not result:
raise HTTPException(status_code=500, detail="批量删除上传历史素材失败")
try:
result = await cleanup_upload_resource_history_files(db, result=result, current_user=current_user)
await db.commit()
except Exception as exc: # noqa: BLE001
await safe_rollback_with_log(
db,
event_type=UploadResourceEventEnum.DELETE_BATCH_ROLLBACK_FAILED.value,
message="批量删除上传历史素材 cleanup 事务回滚失败",
user_id=user_id,
detail={"resource_ids": req.resource_ids, "deleted_ids": result.deleted_ids if result else []},
original_exc=exc,
)
log_upload_resource_exception(
event_type=UploadResourceEventEnum.DELETE_BATCH_CLEANUP_FAILED.value,
message=f"批量删除上传历史素材后清理真实文件失败: {exc}",
user_id=user_id,
resource_ids=result.deleted_ids if result else req.resource_ids,
detail={"resource_ids": req.resource_ids, "deleted_ids": result.deleted_ids if result else []},
exc=exc,
)
return result