From 0f645b01e8cd3714a1961a4ed7e64ce77a87f50f Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 24 Aug 2026 15:16:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Step7=20=E7=A1=AE=E8=AE=A4=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=BC=98=E5=85=88=E8=B0=83=E7=94=A8=20confirmGenerati?= =?UTF-8?q?on=20=E5=A4=8D=E7=94=A8=E9=A2=84=E8=A7=88=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=8D=A1=2010%=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:前端调用 createGenerationTask 创建全新渲染任务,后端 worker 因 source_edit_plan_id 为空进入 failed 分支但未更新 DB 状态, 导致前端轮询永远停在 running(卡 10%)。 修复策略: - 主路径:Step6 封面流程创建预览时,将 previewTaskId 暴露给父组件; Step7 确认生成优先调用 confirmGeneration(previewTaskId) 复用预览 产物,后端会检查预览是否完成、分辨率是否一致,满足条件直接复用秒出。 - Fallback:无预览任务或 confirmGeneration 失败时,退回 createGenerationTask 并传递 source_edit_plan_id。 - 防过期:素材、模板、时长、比例、标题样式或 BGM 任一变化时 自动清除 previewTaskId,避免复用过期预览。 - 防竞态:useStep6Cover 用 useRef 指纹校验,参数变化后旧任务的 回调不会污染新状态。 - generateCount > 1 时跳过 confirmGeneration,直接走批量创建。 改动文件(7 个): - useGenerateVideo.ts:导入 confirmGeneration,新增双路径逻辑 - useGenerateFormState/index.ts:新增 previewTaskId 共享状态 - useStep6Cover.ts:createPreview 后回调 onPreviewTaskCreated,带指纹校验 - Step6CoverSettings.tsx / GenerateStepContent.tsx:透传回调 - GeneratePage.tsx:useEffect 监听参数变化自动清除 previewTaskId - generate-video/types.ts:UseGenerateVideoProps 新增 previewTaskId --- apps/web/src/pages/generate/GeneratePage.tsx | 2 +- apps/web/src/pages/generate/hooks/useGenerateVideo.ts | 6 ++++-- apps/web/src/pages/generate/hooks/useStep6Cover.ts | 9 +++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 29ed669a9..fa48251da 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -107,7 +107,7 @@ const GeneratePage: React.FC = () => { useEffect(() => { setPreviewTaskId(null) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [previewAssetIds, selectedTemplate, duration, videoRatio, titleKey, setPreviewTaskId]) + }, [previewAssetIds, selectedTemplate, duration, videoRatio, titleKey, bgm, setPreviewTaskId]) const previewAssetsEnabled = currentStep >= 4 && previewAssetIds.length > 0 const { diff --git a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts index 569c05793..e8f1d14dc 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts @@ -96,8 +96,10 @@ export function useGenerateVideo(props: UseGenerateVideoProps) { // ── Step7 确认生成:优先复用预览产物(秒出),fallback 到全量渲染 ── let taskId: string | undefined - // 主路径:如果有预览任务 ID,调用 confirmGeneration 复用预览产物 - if (props.previewTaskId) { + // 主路径:如果有预览任务 ID 且只生成 1 个视频,调用 confirmGeneration 复用预览产物 + // generateCount > 1 时需要走 createGenerationTask 支持批量生成 + const isSingleGenerate = !props.generateCount || props.generateCount === 1 + if (props.previewTaskId && isSingleGenerate) { try { console.log( "[handleGenerate] 尝试 confirmGeneration 复用预览产物, previewTaskId:", diff --git a/apps/web/src/pages/generate/hooks/useStep6Cover.ts b/apps/web/src/pages/generate/hooks/useStep6Cover.ts index 4ddffa546..e863559d6 100644 --- a/apps/web/src/pages/generate/hooks/useStep6Cover.ts +++ b/apps/web/src/pages/generate/hooks/useStep6Cover.ts @@ -2,7 +2,7 @@ * Step 6 封面设置 Hook * 封装封面设置的交互逻辑,对接后端封面模板 CRUD API */ -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { message } from "antd" import type { CoverConfig, CoverTemplate } from "../types/cover" import { generateCover } from "@/api/generation" @@ -40,6 +40,8 @@ export function useStep6Cover({ onPreviewTaskCreated, }: UseStep6CoverProps) { const [generating, setGenerating] = useState(false) + // 防竞态:记录当前预览生成的参数指纹,任务完成时校验一致性 + const previewParamsRef = useRef("") // ── 封面设置弹窗状态 ── const [showCoverSettings, setShowCoverSettings] = useState(false) @@ -154,6 +156,8 @@ export function useStep6Cover({ console.log("[Step6] 检测到预览缺失,尝试自动创建预览渲染任务...") message.info("正在准备预览视频,请稍候...") try { + // 记录当前参数指纹,用于任务完成时校验一致性(防竞态) + previewParamsRef.current = JSON.stringify({ selectedTemplate, assetIds, titleSettings }) const previewResp = await createPreview({ template_id: selectedTemplate, asset_ids: assetIds, @@ -174,7 +178,8 @@ export function useStep6Cover({ : {}), }) // 将预览任务 ID 暴露给父组件,供 Step7 确认生成时复用(confirmGeneration) - if (previewResp.task_id) { + const currentFingerprint = JSON.stringify({ selectedTemplate, assetIds, titleSettings }) + if (previewResp.task_id && previewParamsRef.current === currentFingerprint) { onPreviewTaskCreated?.(previewResp.task_id) } // 轮询等待预览渲染完成:递归 setTimeout 避免请求重叠 + 120s 超时兜底