生成任务失败积分回退

This commit is contained in:
2026-06-03 16:15:45 +08:00
parent 7c56c02135
commit 1079a5a7d1
14 changed files with 931 additions and 259 deletions
+73 -24
View File
@@ -43,6 +43,13 @@ from app.services.notification import create_notification
from app.services.auth import hash_password, verify_password
from app.services.operation_log import log_operation
from app.services.resource_signed_url_service import build_resource_signed_url
from app.services.generation_billing_service import (
OWNER_GENERATION_RECORD,
charge_generation_media_by_params,
get_next_credit_attempt_no,
)
from app.services.generation_refund_service import mark_generation_record_failed_and_refund_once
from app.utils.id_gen import generate_id
from app.schemas.generation import GenerationType, ASPECT_RATIOS, RESOLUTIONS
@@ -1054,6 +1061,7 @@ async def admin_update_generation_status(
GenerationRecord.id == record_id,
GenerationRecord.deleted_at.is_(None),
)
.with_for_update()
.limit(1)
)
record = result.scalar_one_or_none()
@@ -1064,11 +1072,21 @@ async def admin_update_generation_status(
if new_status not in ("prompt_optimized", "generating", "completed", "failed"):
raise HTTPException(status_code=400, detail="无效状态")
record.status = new_status
if new_status == "failed":
await mark_generation_record_failed_and_refund_once(
db,
record=record,
error_message=body.get("error_message") or record.error_message or "管理员设置为失败",
)
else:
record.status = new_status
if body.get("video_url"):
record.video_url = body["video_url"]
if body.get("video_cover_url"):
record.video_cover_url = body["video_cover_url"]
if body.get("image_url"):
record.image_url = body["image_url"]
if new_status == "completed":
record.generated_at = datetime.now()
await db.flush()
@@ -1082,9 +1100,8 @@ async def admin_generate_video(
admin: User = Depends(get_admin_user),
db: AsyncSession = Depends(get_db),
):
"""Admin trigger video generation for a record with specified params."""
"""Admin trigger video/image generation for a record with specified params."""
from app.models.project import Project
from app.services.credits import calc_video_credits, deduct_credits, calc_image_credits
from app.services.video_queue import task_queue
result = await db.execute(
@@ -1095,6 +1112,7 @@ async def admin_generate_video(
GenerationRecord.deleted_at.is_(None),
Project.deleted_at.is_(None),
)
.with_for_update()
)
row = result.first()
if not row:
@@ -1106,6 +1124,12 @@ async def admin_generate_video(
if record.status not in ("prompt_optimized", "failed"):
raise HTTPException(status_code=400, detail=f"当前状态不允许生成{type_str}")
attempt_no = await get_next_credit_attempt_no(
db,
owner_type=OWNER_GENERATION_RECORD,
owner_id=record.id,
)
if record.gen_type == GenerationType.video:
# Video Generation
aspect_ratio = body.get("aspect_ratio", "16:9")
@@ -1116,55 +1140,80 @@ async def admin_generate_video(
raise HTTPException(status_code=400, detail="不支持的分辨率")
duration = record.duration or 5
video_credits = await calc_video_credits(db, duration, resolution)
await deduct_credits(
db, record.user_id, video_credits,
f"视频生成(管理后台) - {project_name}",
related_id=record_id,
media_billing = await charge_generation_media_by_params(
db,
user_id=record.user_id,
record_id=record.id,
gen_type="video",
duration=duration,
resolution=resolution,
project_name=project_name,
description_prefix="视频生成(管理后台)",
owner_type=OWNER_GENERATION_RECORD,
attempt_no=attempt_no,
)
record.aspect_ratio = aspect_ratio
record.resolution = resolution
record.credits_cost = (record.credits_cost or 0) + video_credits
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
record.status = "generating"
record.error_message = None
record.video_url = None
record.video_cover_url = None
record.image_url = None
record.seedance_task_id = None
await db.flush()
try:
from app.services.video_gen import get_active_engine, submit_video_task
engine = await get_active_engine(db)
task_id = await submit_video_task(db, engine, record)
record.seedance_task_id = task_id
await db.flush()
await task_queue.enqueue(record_id)
except Exception as e:
record.status = "failed"
record.error_message = str(e)
await mark_generation_record_failed_and_refund_once(
db,
record=record,
error_message=str(e),
)
await db.flush()
elif record.gen_type == GenerationType.image:
# Image generation
post_image_size = body.get("image_size", "")
image_credits = await calc_image_credits(db, post_image_size or record.image_size or "2K")
await deduct_credits(
db, record.user_id, image_credits,
f"图片生成 - {project_name}",
related_id=record_id,
image_size = post_image_size or record.image_size or "2K"
media_billing = await charge_generation_media_by_params(
db,
user_id=record.user_id,
record_id=record.id,
gen_type="image",
image_size=image_size,
project_name=project_name,
description_prefix="图片生成(管理后台)",
owner_type=OWNER_GENERATION_RECORD,
attempt_no=attempt_no,
)
record.image_size = image_size
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
record.status = "generating"
record.error_message = None
record.image_url = None
record.video_url = None
record.video_cover_url = None
record.seedance_task_id = None
await db.flush()
try:
record.image_size = post_image_size or record.image_size or "2K"
record.credits_cost = round(image_credits, 2)
record.status = "generating"
record.error_message = None
await db.flush()
await task_queue.enqueue(record_id)
except Exception as e:
record.status = "failed"
record.error_message = str(e)
await mark_generation_record_failed_and_refund_once(
db,
record=record,
error_message=str(e),
)
await db.flush()
return {"message": "ok", "record_id": record_id}