63e0ffeaea
- vidu_callback 改用 APP_BASE_URL 构建 callback_url,避免 Nginx 代理导致 scheme 不一致 - verify_signature 增加详细调试日志,打印 signing_string 和签名对比
129 lines
5.0 KiB
Python
129 lines
5.0 KiB
Python
"""
|
||
Vidu 回调路由
|
||
============
|
||
|
||
仅保留 Vidu 视频生成任务完成回调接口。
|
||
视频生成任务统一走 /tasks/video 创建,/tasks/{task_id} 轮询。
|
||
|
||
Vidu 任务完成后主动 POST 通知此接口。
|
||
回调更新 Async Engine TaskRegistry,供前端统一轮询。
|
||
"""
|
||
|
||
import logging
|
||
|
||
from fastapi import APIRouter, HTTPException, Request
|
||
|
||
from app.config import get_settings
|
||
from app.core.exceptions import PlatformError
|
||
from app.core.redis_client import get_redis_client
|
||
from app.platform_gateway import PlatformGateway
|
||
from app.schemas.common import success_response
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(prefix="/vidu", tags=["Vidu"])
|
||
|
||
|
||
def get_platform_gateway(request: Request) -> PlatformGateway:
|
||
"""从 app.state 获取 PlatformGateway"""
|
||
return request.app.state.platform_gateway
|
||
|
||
|
||
@router.post("/callback")
|
||
async def vidu_callback(request: Request):
|
||
"""
|
||
Vidu 视频生成任务完成回调
|
||
|
||
Vidu 任务完成后主动 POST 通知此接口。
|
||
无需登录校验(Vidu 外部调用),统一走 PlatformGateway 处理。
|
||
回调结果写入 Async Engine TaskRegistry,前端通过 /tasks/{task_id} 统一轮询。
|
||
"""
|
||
# 1. 统一走 PlatformGateway 处理回调(签名验证 + nonce 防重放)
|
||
gateway = request.app.state.platform_gateway
|
||
body_bytes = await request.body()
|
||
headers_dict = dict(request.headers)
|
||
|
||
# 使用 APP_BASE_URL 构建 callback_url,确保与提交任务时传给 Vidu 的一致
|
||
#(Nginx 反向代理可能导致 request.url 的 scheme 为 http,与 Vidu 签名时的 https 不一致)
|
||
app_base_url = get_settings().app_base_url
|
||
callback_url = f"{app_base_url}/api/v1/vidu/callback" if app_base_url else str(request.url)
|
||
logger.info(f"[Vidu] 收到回调: request_url={request.url}, callback_url={callback_url}, body={body_bytes.decode('utf-8', errors='replace')[:500]}")
|
||
|
||
try:
|
||
task_status = await gateway.handle_webhook(
|
||
platform="vidu",
|
||
headers=headers_dict,
|
||
body=body_bytes,
|
||
secret=get_settings().VIDU_API_KEY,
|
||
callback_url=callback_url,
|
||
)
|
||
except PlatformError as e:
|
||
logger.warning(f"[Vidu] 回调验证失败: {e}")
|
||
raise HTTPException(status_code=401, detail="回调签名验证失败")
|
||
except Exception as e:
|
||
logger.error(f"[Vidu] 回调处理失败: {e}")
|
||
raise HTTPException(status_code=500, detail="回调处理失败,请稍后重试")
|
||
|
||
logger.info(f"[Vidu] 回调解析完成: state={task_status.state}, result={task_status.result}, error={task_status.error_message}")
|
||
|
||
# 2. 通过 platform_task_id 反查 Async Engine 内部 task_id,更新 TaskRegistry
|
||
platform_task_id = (
|
||
task_status.result.get("task_id") if task_status.result else None
|
||
)
|
||
video_url = (
|
||
task_status.result.get("video_url") if task_status.result else None
|
||
)
|
||
|
||
logger.info(f"[Vidu] 准备反查 internal_task_id: platform_task_id={platform_task_id}")
|
||
|
||
internal_task_id = None
|
||
if platform_task_id:
|
||
internal_task_id = await gateway.get_internal_task_id_by_platform_task_id(
|
||
"vidu", platform_task_id
|
||
)
|
||
|
||
if internal_task_id:
|
||
# 更新 Async Engine TaskRegistry,供前端统一轮询 /tasks/{task_id}
|
||
#
|
||
# ⚠️ 注意:此处仅更新任务状态,不执行积分扣费。
|
||
# 视频生成的扣费在前端 VideoGeneration.tsx 拼接完成后执行。
|
||
# 如果用户在 Vidu 任务提交后关闭应用,前端不会执行扣费,形成漏扣。
|
||
# 当前接受此风险,详情见前端注释。
|
||
from app.scheduler.registry import TaskRegistry
|
||
|
||
registry = TaskRegistry(get_redis_client())
|
||
task_record = await registry.get(internal_task_id)
|
||
if task_record:
|
||
if task_status.state == "completed":
|
||
await registry.update(
|
||
internal_task_id,
|
||
status="completed",
|
||
progress=100,
|
||
message="视频生成完成",
|
||
completed=1,
|
||
total=1,
|
||
result={"video_url": video_url, "state": "success"},
|
||
)
|
||
elif task_status.state == "failed":
|
||
await registry.update(
|
||
internal_task_id,
|
||
status="failed",
|
||
message="视频生成失败",
|
||
error=task_status.error_message or "视频生成失败",
|
||
)
|
||
logger.info(
|
||
f"[Vidu] 回调已更新 TaskRegistry: task={internal_task_id}, "
|
||
f"state={task_status.state}, video_url={video_url}"
|
||
)
|
||
else:
|
||
logger.warning(
|
||
f"[Vidu] 回调找不到对应任务记录: internal={internal_task_id}, "
|
||
f"platform={platform_task_id}"
|
||
)
|
||
else:
|
||
logger.warning(
|
||
f"[Vidu] 回调无法反查内部 task_id: platform={platform_task_id}"
|
||
)
|
||
|
||
return success_response(message="回调已接收")
|