会员积分改版V1

This commit is contained in:
2026-08-11 09:24:18 +08:00
parent fe24e51b97
commit b9fd07f293
111 changed files with 9355 additions and 3900 deletions
@@ -19,7 +19,6 @@ from app.enums.common import (
)
from app.enums.credit_record import CreditRecordBillingScene, CreditRecordChargeKind, CreditRecordOwnerType
from app.enums.generation_task import ChatGenerationTaskStatus
from app.enums.llm_billing import LlmBillingConfigKey
from app.enums.shot_replicate import (
ShotSegmentReplicateStatusEnum,
ShotSplitStatusEnum,
@@ -51,13 +50,14 @@ from app.services.generation.pipeline.db_lock_service import (
from app.services.generation.task_factory_service import create_chat_generation_task_for_module
from app.services.llm_billing import (
LlmBillingContext,
ensure_hold_exists,
ensure_pre_deducted,
log_provider_failure,
log_provider_start,
log_provider_success,
release_on_failure,
settle_success,
start_hold,
mark_business_success,
pre_deduct,
record_provider_exception,
finalize_llm_business_failure,
)
from app.services.hot_opening_video_prompt_service import (
build_final_video_prompt,
@@ -369,8 +369,7 @@ def build_v2_video_prompt_billing_context(
source_step_id=str(step_id),
source_step_code=VIDEO_PROMPT_OPTIMIZE,
related_id=str(step_id),
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_VIDEO_PROMPT.value,
description_prefix=f"{display_name}视频提词优化",
description_prefix=f"{display_name}视频提词优化",
trace_id=f"module-v2-video-prompt:{step_id}",
)
@@ -433,7 +432,7 @@ async def create_hot_opening_project_v2(
video_config=video_config,
target_platform=req.target_platform or "抖音",
)
await start_hold(
await pre_deduct(
db,
build_v2_video_prompt_billing_context(
user_id=str(current_user.id),
@@ -600,7 +599,7 @@ async def create_shot_replicate_project_v2(
urls=[req.material_image_url],
allow_common_migrate=True,
)
await start_hold(
await pre_deduct(
db,
build_v2_video_prompt_billing_context(
user_id=str(current_user.id),
@@ -724,7 +723,7 @@ async def rebuild_video_prompt_step_v2(
project.final_video_cover_url = None
project.completed_at = None
project.error_message = None
await start_hold(
await pre_deduct(
db,
build_v2_video_prompt_billing_context(
user_id=str(project.user_id),
@@ -799,19 +798,16 @@ async def mark_video_prompt_dispatch_failed_v2(
message=error_message,
detail={"dispatch_compensated": True},
)
await release_on_failure(
db,
build_v2_video_prompt_billing_context(
user_id=str(project.user_id),
project_id=str(project.id),
step_id=str(step.id),
step_version=int(step.version or 1),
module=config.module,
display_name=config.display_name,
),
error=error_message,
billing_context = build_v2_video_prompt_billing_context(
user_id=str(project.user_id),
project_id=str(project.id),
step_id=str(step.id),
step_version=int(step.version or 1),
module=config.module,
display_name=config.display_name,
)
await db.commit()
await finalize_llm_business_failure(billing_context, error=error_message)
async def run_video_prompt_optimize_v2(
@@ -888,14 +884,13 @@ async def run_video_prompt_optimize_v2(
source_step_id=project_snapshot["step_id"],
source_step_code=VIDEO_PROMPT_OPTIMIZE,
related_id=project_snapshot["step_id"],
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_VIDEO_PROMPT.value,
description_prefix=f"{config.display_name}视频提词优化",
trace_id=f"module-v2-video-prompt:{project_snapshot['step_id']}",
)
hold_validation = await ensure_hold_exists(db, llm_billing_context)
if not hold_validation.can_execute:
pre_deduct_validation = await ensure_pre_deducted(db, llm_billing_context)
if not pre_deduct_validation.can_execute:
step.status = ModuleStepStatusEnum.FAILED.value
step.error_message = f"LLM账务状态异常({hold_validation.state.value}),已终止任务"
step.error_message = f"LLM账务状态异常({pre_deduct_validation.state.value}),已终止任务"
step.completed_at = utc_now()
project.status = ModuleProjectStatusEnum.FAILED.value
project.error_message = step.error_message
@@ -906,7 +901,7 @@ async def run_video_prompt_optimize_v2(
provider_succeeded = False
usage: dict[str, Any] = {}
log_provider_start(llm_billing_context, detail={"prompt_type": "video", "flow_version": "v2"})
await log_provider_start(db, llm_billing_context, detail={"prompt_type": "video", "flow_version": "v2"})
prompt_schema, final_prompt, usage = await optimize_hot_opening_video_prompt(
db,
user_id=project_snapshot["user_id"],
@@ -921,9 +916,11 @@ async def run_video_prompt_optimize_v2(
module=project_snapshot["module"],
project_id=project_snapshot["project_id"],
step_id=project_snapshot["step_id"],
fixed_model_config_id=llm_billing_context.model_config_id,
fixed_model_snapshot=llm_billing_context.model_parameters_snapshot,
)
provider_succeeded = True
log_provider_success(llm_billing_context, usage=usage)
await log_provider_success(db, llm_billing_context, usage=usage)
if execution_guard is not None:
await execution_guard()
@@ -945,24 +942,20 @@ async def run_video_prompt_optimize_v2(
row = locked.first()
if not row:
await db.rollback()
# Provider 已成功,即使业务对象被异常移除,也必须按真实 usage 完成幂等结算。
await settle_success(
db,
await log_provider_failure(db, llm_billing_context, error="业务对象已失效,供应商结果无法落库")
await finalize_llm_business_failure(
llm_billing_context,
usage=usage,
description=f"{config.display_name}-视频提词优化(业务对象失效结算)",
error="业务对象已失效,供应商结果无法落库",
)
await db.commit()
return None
project, step = row
if int(step.version) != expected_version or step.input_json != expected_input or step.status != ModuleStepStatusEnum.PROCESSING.value:
await settle_success(
db,
await db.rollback()
await log_provider_failure(db, llm_billing_context, error="业务步骤版本已失效,供应商结果被丢弃")
await finalize_llm_business_failure(
llm_billing_context,
usage=usage,
description=f"{config.display_name}-视频提词优化(失效结果结算)",
error="业务步骤版本已失效,供应商结果被丢弃",
)
await db.commit()
log_module_event_file(
module=project_snapshot["module"],
event_type=ModuleEventTypeEnum.STALE_STEP_RESULT_DISCARDED.value,
@@ -973,7 +966,7 @@ async def run_video_prompt_optimize_v2(
detail={"expected_version": expected_version},
)
return None
billing = await settle_success(
billing = await mark_business_success(
db,
llm_billing_context,
usage=usage,
@@ -1018,33 +1011,24 @@ async def run_video_prompt_optimize_v2(
)
await db.commit()
return step
except DatabaseRowLockBusy:
except DatabaseRowLockBusy as exc:
await db.rollback()
if locals().get("provider_succeeded", False):
await settle_success(
db,
if "llm_billing_context" in locals() and locals().get("provider_succeeded", False):
await log_provider_failure(db, llm_billing_context, error="业务行锁失败,供应商结果无法落库")
await finalize_llm_business_failure(
llm_billing_context,
usage=locals().get("usage") or {},
description=f"{config.display_name}-视频提词优化(行锁失败结算)",
error="业务行锁失败,供应商结果无法落库",
)
await db.commit()
return None
raise
raise exc
except Exception as exc:
await db.rollback()
if "llm_billing_context" in locals() and not locals().get("provider_succeeded", False):
log_provider_failure(llm_billing_context, error=str(exc))
if "llm_billing_context" in locals():
if locals().get("provider_succeeded", False):
await log_provider_failure(db, llm_billing_context, error=str(exc))
else:
provider_succeeded, usage = await record_provider_exception(db, llm_billing_context, exc)
try:
if "llm_billing_context" in locals():
if locals().get("provider_succeeded", False):
await settle_success(
db,
llm_billing_context,
usage=locals().get("usage") or {},
description=f"{config.display_name}-视频提词优化(本地失败结算)",
)
else:
await release_on_failure(db, llm_billing_context, error=str(exc))
if execution_guard is not None:
await execution_guard()
result = await execute_with_lock_timeout(
@@ -1070,9 +1054,9 @@ async def run_video_prompt_optimize_v2(
step.completed_at = utc_now()
project.status = ModuleProjectStatusEnum.FAILED.value
project.error_message = str(exc) if str(exc) else type(exc).__name__
# provider 已成功时 settle_success 已在当前事务写入 RELEASE/CHARGE
# 即使业务步骤已不存在或已不是 processing,也必须提交账务结算。
await db.commit()
if "llm_billing_context" in locals():
await finalize_llm_business_failure(llm_billing_context, error=str(exc))
log_module_error(
module=row[0].module if row else "module_generation_v2",
event_type=ModuleEventTypeEnum.VIDEO_PROMPT_FAILED.value,
@@ -1092,8 +1076,11 @@ async def run_video_prompt_optimize_v2(
detail={"origin_error": str(exc), "provider_succeeded": locals().get("provider_succeeded", False)},
exc=mark_exc,
)
if locals().get("provider_succeeded", False):
raise
try:
if "llm_billing_context" in locals():
await finalize_llm_business_failure(llm_billing_context, error=str(exc))
except Exception:
pass
return None