Storyboard 基础设施排雷与 AIGC 探索方案
> 文档维护者: 全栈后端架构师 & AI 产品经理 > 更新日期: 2026-03-25 > 核心任务: 基础设施稳定化 + AIGC 内容脑暴
🎯 任务 A:硬核排雷 (Infrastructure Stabilization)
1. 链路排查方案:10分钟无响应精准定位指南
当视频提取任务在前端显示"处理中"长达 10 分钟没反应时,按照以下步骤精准定位:
📊 排查流程图
前端"处理中" (10分钟)
↓
[步骤 1] 数据库状态验证
↓ (process_status = 1)
[步骤 2] PHP 端日志检查
↓ (有任务触发记录)
[步骤 3] Celery 队列状态检查
↓ (任务在队列中)
[步骤 4] Worker 日志与状态检查
↓ (Worker 正在处理)
[步骤 5] Python 任务日志深度排查
↓
定位到具体卡死环节🔍 详细排查步骤
步骤 1:数据库状态验证 (最快)
检查点:account_direction_reference_video 表
-- 查看该任务的当前状态
SELECT
id,
title,
process_status,
create_time,
update_time,
TIMESTAMPDIFF(MINUTE, create_time, NOW()) as minutes_elapsed
FROM account_direction_reference_video
WHERE id = {inspiration_id};
-- 查看最近 10 分钟所有处理中的任务
SELECT
id,
title,
process_status,
create_time,
TIMESTAMPDIFF(MINUTE, create_time, NOW()) as minutes_elapsed
FROM account_direction_reference_video
WHERE process_status = 1
AND create_time > DATE_SUB(NOW(), INTERVAL 10 MINUTE)
ORDER BY create_time DESC;process_status 状态码说明:
0= 未处理1= 处理中 (🔴 如果卡在这里超过 10 分钟)2= 已完成3= 失败
判断依据:
- 如果
process_status != 1→ 不是卡在链路中,状态已更新 - 如果
update_time很久没变 → 任务确实卡死了
步骤 2:PHP 端日志检查
日志路径:api/runtime/log/
# 进入 api 目录
cd /Users/mzy/docker/dnmp/www/stooland/api
# 查看今天的日志
tail -f runtime/log/$(date +%Y%m%d).log
# 搜索特定 inspiration_id 的日志
grep "inspiration_id\|PyBridge" runtime/log/$(date +%Y%m%d).log
# 搜索 Webhook 回调记录
grep "video-asset-extracted" runtime/log/$(date +%Y%m%d).log关键日志关键字:
Triggered PyBridge video asset processing→ PHP 成功触发任务Failed to trigger PyBridge→ 触发失败Webhook或video-asset-extracted→ 收到回调
判断依据:
- 有
Triggered但无Webhook→ 问题在 Python/Celery 端 - 无
Triggered→ 问题在 PHP 端触发环节
步骤 3:Celery 队列状态检查
检查命令:
# 进入 muse-engine 目录
cd /Users/mzy/docker/dnmp/www/stooland/muse-engine
# 方式 1: 使用 celery inspect 查看队列状态
celery -A src.celery_app inspect active
# 查看已注册的任务
celery -A src.celery_app inspect registered
# 查看队列长度 (需要 flower 或直接连 Redis)
redis-cli -h localhost -p 6379 LLEN celery
# 如果配置了 flower,浏览器打开监控面板
# http://localhost:5555判断依据:
inspect active为空 → 任务没进队列,或队列积压LLEN celery数值很大 → 队列积压严重
步骤 4:Worker 日志与状态检查
Worker 日志位置:根据启动方式而定
# 如果是用 start.sh 启动的,查看日志文件
cd /Users/mzy/docker/dnmp/www/stooland/muse-engine
tail -f nohup.out 2>&1 | grep -E "process_video_assets|ERROR|CRITICAL"
# 或者查看系统进程
ps aux | grep celery
# 检查 Worker 是否存活
celery -A src.celery_app inspect ping关键日志模式:
# 正常启动
[INFO] celery@xxx ready.
# 任务接收
[INFO] Received task: src.tasks.video_tasks.process_video_assets[xxx]
# 任务进度
[job_id] Starting video asset processing...
[job_id] Downloading video...
[job_id] Extracting audio...
[job_id] Extracting frames...
# 异常
[ERROR] CRITICAL ERROR: ...
[WARNING] Anti-scraping protection triggered判断依据:
- 有
Received task但没有后续进度 → 任务在下载或处理环节卡死 - 有
Anti-scraping→ 被反爬拦截
步骤 5:Python 任务日志深度排查
Job 日志位置:muse-engine/tmp/ 或配置的日志目录
# 查看具体 job_id 的日志
# 首先从数据库或 PHP 日志找到 job_id
# 如果有 job_id,直接查找相关日志
cd /Users/mzy/docker/dnmp/www/stooland/muse-engine
find . -name "*{job_id}*" -type f 2>/dev/null
# 查看 tmp 目录下最近的日志
ls -lt tmp/ | head -20
# 检查临时文件是否残留
ls -lt tmp/dl_*.mp4 2>/dev/null | head -10
ls -lt tmp/audio_*.mp3 2>/dev/null | head -10卡死环节定位:
| 最后看到的日志 | 卡死环节 | 可能原因 |
|---|---|---|
Downloading video to... | 视频下载 | 1. 网络慢/超时<br>2. 被反爬返回 HTML<br>3. 链接失效 |
Extracting audio... | 音频提取 | 1. 视频文件损坏<br>2. FFmpeg 卡死 |
Extracting frames... | 帧提取 | 1. 视频码率异常<br>2. 内存不足 |
Uploading audio to OSS | OSS 上传 | 1. OSS 连接问题<br>2. 权限问题 |
Sending callback to... | 回调 PHP | 1. PHP 服务不可达<br>2. 网络超时 |
🛠️ 快速排查脚本 (一键诊断)
创建快速诊断脚本:
#!/bin/bash
# 文件名: diagnose_stuck_task.sh
# 使用: ./diagnose_stuck_task.sh <inspiration_id>
INSPIRATION_ID=$1
if [ -z "$INSPIRATION_ID" ]; then
echo "Usage: $0 <inspiration_id>"
exit 1
fi
echo "========================================"
echo "🔍 Task Diagnostics for ID: $INSPIRATION_ID"
echo "========================================"
# 步骤 1: 数据库状态
echo ""
echo "[1/5] 检查数据库状态..."
# (需要配置数据库连接)
# 步骤 2: PHP 日志
echo ""
echo "[2/5] 检查 PHP 日志..."
cd /Users/mzy/docker/dnmp/www/stooland/api
grep -C 5 "inspiration_id.*$INSPIRATION_ID" runtime/log/$(date +%Y%m%d).log || echo "未找到该 ID 的 PHP 日志"
# 步骤 3: Celery 队列
echo ""
echo "[3/5] 检查 Celery 队列..."
cd /Users/mzy/docker/dnmp/www/stooland/muse-engine
celery -A src.celery_app inspect active 2>/dev/null || echo "Celery inspect 失败,请检查 Worker"
# 步骤 4: Worker 日志
echo ""
echo "[4/5] 检查 Worker 最新日志..."
if [ -f nohup.out ]; then
tail -50 nohup.out | grep -E "process_video_assets|ERROR|CRITICAL|WARNING"
else
echo "未找到 nohup.out,请检查 Worker 启动方式"
fi
echo ""
echo "========================================"
echo "✅ 诊断完成"
echo "========================================"2. Playwright Fallback 策略工程化集成方案
📐 架构设计
┌─────────────────────────────────────────────────────────┐
│ PHP VideoExtractService │
├─────────────────────────────────────────────────────────┤
│ 通道 0-7 (第三方 API, 轻量级, 快速) │
│ parseWithHhlqilongzhu() → parseWithYujn() → ... │
└──────────────────────┬──────────────────────────────────┘
│ 全部失败
↓
┌─────────────────────────────────────────────────────────┐
│ 新增通道 8: Playwright Fallback Gateway │
├─────────────────────────────────────────────────────────┤
│ 调用 Python HTTP 服务 /api/playwright/extract │
└──────────────────────┬──────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────┐
│ Python: Playwright Extraction Service │
├─────────────────────────────────────────────────────────┤
│ PlaywrightFallbackStrategy.execute() │
│ → 无头浏览器访问 │
│ → 拦截 Network .mp4 流 │
│ → 返回 video_url │
└──────────────────────┬──────────────────────────────────┘
│
↓
返回给 PHP,继续后续流程🔧 实施步骤
步骤 1: 在 muse-engine 中创建 Playwright HTTP 服务
创建文件:muse-engine/src/service/playwright_service.py
# -*- coding: utf-8 -*-
import asyncio
import sys
import os
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from loguru import logger
# 添加 social-scraper 到路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'social-scraper'))
from strategies.playwright_fallback_strategy import PlaywrightFallbackStrategy
router = APIRouter(prefix="/playwright", tags=["playwright"])
class PlaywrightExtractRequest(BaseModel):
url: str
headless: bool = True
class PlaywrightExtractResponse(BaseModel):
status: str
video_url: str = None
title: str = None
message: str = None
@router.post("/extract", response_model=PlaywrightExtractResponse)
async def extract_video(request: PlaywrightExtractRequest):
"""
使用 Playwright 兜底策略提取视频
"""
logger.info(f"[PlaywrightService] 收到提取请求: {request.url[:80]}...")
try:
strategy = PlaywrightFallbackStrategy(headless=request.headless)
result = await strategy.execute(request.url)
logger.info(f"[PlaywrightService] 提取结果: {result}")
return PlaywrightExtractResponse(
status=result.get("status"),
video_url=result.get("video_url"),
title=result.get("title"),
message=result.get("message")
)
except Exception as e:
logger.error(f"[PlaywrightService] 提取异常: {e}")
raise HTTPException(status_code=500, detail=str(e))步骤 2: 在 fastapi_app.py 中注册路由
修改 muse-engine/src/service/fastapi_app.py,添加:
# 在文件顶部导入
from src.service.playwright_service import router as playwright_router
# 在 create_fastapi_app() 函数中添加
app.include_router(playwright_router)步骤 3: 在 PHP VideoExtractService 中添加通道 8
修改 api/app/aigc/service/VideoExtractService.php,添加新方法:
/**
* 通道 8: Playwright 兜底策略 (最终武器)
*/
protected function parseWithPlaywright(string $url): ?array
{
try {
Log::info("[VideoExtractService] 启用 Playwright 兜底策略: " . $url);
$pybridgeUrl = env('APP.PYBRIDGE_URL', 'http://127.0.0.1:8787');
$response = $this->client->post($pybridgeUrl . '/playwright/extract', [
'json' => [
'url' => $url,
'headless' => true
],
'timeout' => 45.0 // Playwright 需要更长时间
]);
$body = $response->getBody()->getContents();
$data = json_decode($body, true);
if (isset($data['status']) && $data['status'] === 'success') {
$videoUrl = trim($data['video_url'] ?? '', " `");
if (!empty($videoUrl)) {
return [
'video_url' => $videoUrl,
'cover_url' => '',
'title' => $data['title'] ?? '未命名视频',
'platform' => 'douyin',
'source_url' => $url
];
}
} elseif (isset($data['status']) && $data['status'] === 'not_found') {
Log::warning("[VideoExtractService] Playwright 检测到作品已删除: " . $data['message']);
return null;
}
Log::warning("[VideoExtractService] Playwright 提取失败: " . ($data['message'] ?? 'unknown'));
return null;
} catch (\Exception $e) {
Log::error("[VideoExtractService] Playwright exception: " . $e->getMessage());
return null;
}
}然后在 extract() 方法的通道列表末尾添加:
// 通道 8: Playwright 兜底策略 (最终武器)
Log::info("[VideoExtractService] All APIs failed, trying Playwright fallback...");
$result = $this->parseWithPlaywright($url);
if ($result) {
return $result;
}步骤 4: 配置依赖与环境
在 muse-engine/requirements.txt 中添加:
playwright>=1.40.0安装 Playwright 浏览器:
cd /Users/mzy/docker/dnmp/www/stooland/muse-engine
pip install playwright
playwright install chromium步骤 5: 增加超时与重试机制
在 muse-engine/src/tasks/video_tasks.py 的 process_video_assets_task 中:
# 下载视频时增加更完善的重试
async def download():
headers = {"User-Agent": "Mozilla/5.0 ..."}
client_kwargs = {
"timeout": 300.0, # 5 分钟
"follow_redirects": True,
"headers": headers,
"trust_env": True
}
async with httpx.AsyncClient(**client_kwargs) as client:
max_retries = 5 # 增加到 5 次
backoff_times = [2, 5, 10, 20, 30] # 指数退避
for attempt in range(max_retries):
try:
# ... 原有下载逻辑 ...
if success:
return True
except Exception as e:
last_error = e
logger.warning(f"Download attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1:
wait_time = backoff_times[attempt]
logger.info(f"Waiting {wait_time}s before retry...")
await asyncio.sleep(wait_time)
raise Exception(f"Failed after {max_retries} attempts")📊 监控与告警
建立 Playwright 使用监控:
# 在 playwright_service.py 中添加统计
from datetime import datetime
playwright_stats = {
"total_calls": 0,
"success_count": 0,
"failed_count": 0,
"not_found_count": 0,
"last_calls": []
}
@router.post("/extract")
async def extract_video(request: PlaywrightExtractRequest):
global playwright_stats
playwright_stats["total_calls"] += 1
# ... 原有逻辑 ...
if result.get("status") == "success":
playwright_stats["success_count"] += 1
elif result.get("status") == "not_found":
playwright_stats["not_found_count"] += 1
else:
playwright_stats["failed_count"] += 1
# 记录最近 100 次调用
playwright_stats["last_calls"].append({
"time": datetime.now().isoformat(),
"url": request.url[:50],
"status": result.get("status")
})
if len(playwright_stats["last_calls"]) > 100:
playwright_stats["last_calls"].pop(0)
@router.get("/stats")
async def get_stats():
return playwright_stats🎯 任务 B:AIGC 奇观脑暴 (Product Brainstorming)
水果 + AIGC 趣味内容短视频策划
基于现有能力(火山 ASR、ComfyUI/即梦图像生成、TTS),针对"水果行不行"账号转型,提出以下 3 个具体短视频策划方向:
🎬 策划方向一:《水果赛博朋克图鉴》
核心概念:用 ComfyUI 将普通水果变成赛博朋克风格的"未来水果",打造视觉奇观
内容范式:
[0-3秒] 钩子开场 (视觉冲击)
→ 画面:暗黑背景,霓虹灯光闪烁,一个普通苹果缓缓浮现
→ 台词(TTS):"你见过 2077 年的苹果吗?"
→ 音效:电子脉冲声 + glitch 特效
[3-15秒] AI 变身过程 (奇观展示)
→ 画面:普通苹果 → (分镜切换/渐变) → 赛博朋克苹果
- 表皮变成金属质感 + 霓虹纹路
- 果柄变成光纤/数据线
- 周围漂浮 hologram 文字:"CYBER-FRUIT v2.0"
→ 台词(TTS):"注入赛博基因,启动霓虹涂层... 完成!"
→ BGM:赛博朋克风电子乐
[15-25秒] 系列展示 (完播钩子)
→ 画面:快速切换 3-5 种赛博水果
- 赛博西瓜:外壳是透明玻璃罩,内部果肉是发光像素
- 赛博草莓:表面是 LED 点阵,会变换颜色
- 赛博榴莲:尖刺变成能量武器造型
→ 台词(TTS):"西瓜、草莓、榴莲... 每一个都是未来限定!"
[25-30秒] 互动结尾
→ 画面:所有赛博水果排成一排 + 文字叠加
→ 台词(TTS):"下一个你想看什么水果赛博化?评论区告诉我!"
→ 画面文字:"点赞关注,解锁更多未来水果!"技术实现路径:
- 即梦/ComfyUI 工作流:
- ControlNet + Reference Only 保持水果轮廓
- LoRA:
cyberpunk_style_v1+neon_lights_v2 - Prompt:
cyberpunk style, glowing neon lines, metallic texture, futuristic, {fruit}, holographic interface, dark background, 8k, ultra detailed
- 分镜脚本生成:让 LLM 基于这个范式自动生成分镜
- 后期合成:用 FFmpeg 做转场、加 Glitch 特效、BGM
🎬 策划方向二:《水果冷笑话 AI 剧场》
核心概念:水果拟人化,用数字人讲水果冷笑话,搞笑 + 轻科普
内容范式:
[0-2秒] 快速开场
→ 画面:数字人(水果造型)出现
→ 台词(TTS):"嘿,我是小苹果!"
[2-12秒] 冷笑话表演
→ 画面:数字人做夸张表情 + 手势
→ 台词(TTS):
"你知道为什么苹果最怕去医院吗?"
(停顿 2 秒)
"因为医生总是说:'来,把苹果核(hu)交出来!'"
→ 音效:"咚~"(冷场鼓声)+ 乌鸦飞过
[12-20秒] 趣味科普 (价值点)
→ 画面:苹果切开的特写 + 知识点字幕
→ 台词(TTS):
"不过说真的,苹果核其实含有微量氰化物,
但只要不吃个几十斤,根本没事~"
→ 画面文字:"知识冷,但有用!"
[20-28秒] 互动 + 预告
→ 画面:数字人 wink + 下集预告
→ 台词(TTS):
"明天听我讲榴莲的社死现场!
点赞关注,水果笑话天天见~"技术实现路径:
- 数字人:阿里数字人或即梦数字人,定制水果主题 avatar
- TTS:火山引擎 TTS,用活泼/搞笑音色
- 冷笑话生成:LLM Prompt:
你是一个水果冷笑话专家。请生成关于{水果}的冷笑话,100字以内,要谐音梗。然后再补充一个关于该水果的趣味小知识。 - 自动流水线:可以做成完全自动化的日更内容
🎬 策划方向三:《水果时光倒流机》
核心概念:用 AI 让水果"逆生长",从成品水果变回树苗、种子,视觉奇观 + 治愈感
内容范式:
[0-3秒] 钩子提问
→ 画面:一个鲜红的草莓特写
→ 台词(TTS):"你见过草莓变回种子的样子吗?"
→ BGM:神秘、好奇的氛围音乐
[3-20秒] 逆生长奇观 (核心看点)
→ 画面:用 AI 生成的"逆生长"动画序列
- 阶段 1 (0-5s):成熟草莓 → 半生草莓(颜色变淡,体积缩小)
- 阶段 2 (5-10s):半生草莓 → 小白花(花瓣慢慢张开)
- 阶段 3 (10-15s):小白花 → 草莓苗(叶子缩回,茎变细)
- 阶段 4 (15-20s):草莓苗 → 种子(最终回到一颗小小的种子)
→ 台词(TTS):
"让时光倒流... 3... 2... 1...
从果实,到花朵,到青苗,最后回到最初的那颗种子。"
→ 特效:画面加上"倒带"滤镜、时间轴数字倒流
[20-28秒] 治愈升华
→ 画面:种子落在土壤里,渐渐发芽(快速正向播放)
→ 台词(TTS):
"但每一颗种子,都蕴含着再次变成果实的力量。
就像你我,随时可以重新开始。"
→ BGM:温暖、治愈的音乐
[28-30秒] 结尾互动
→ 画面:文字叠加
→ 台词(TTS):"明天想看什么水果的时光倒流?"
→ 画面文字:"点赞 + 关注,治愈每天"技术实现路径:
- 关键帧生成:
- 用即梦/ComfyUI 生成 5-8 个关键帧(成熟、半熟、开花、幼苗、种子...)
- Prompt:
{stage description} of {fruit}, hyper realistic, natural lighting, 8k
- 帧插值:用 RIFE 或 FILM 做帧间插值,生成平滑过渡
- 视频合成:FFmpeg 把图片序列转视频,加 BGM、字幕、特效
- 批量生产:可以做一个"水果选择器",选水果自动生成全套
📊 三个策划对比
| 策划方向 | 核心卖点 | 技术难度 | 量产性 | 目标受众 |
|---|---|---|---|---|
| 赛博朋克图鉴 | 视觉奇观、酷炫 | ⭐⭐⭐ | ⭐⭐ | 科技爱好者、Z世代 |
| 冷笑话 AI 剧场 | 搞笑、日更轻松 | ⭐ | ⭐⭐⭐⭐⭐ | 泛娱乐大众、学生 |
| 时光倒流机 | 治愈、反差感 | ⭐⭐⭐⭐ | ⭐⭐ | 喜欢治愈内容的用户 |
🎯 总结与行动计划
基础设施排雷优先级
- 立即执行:按照"链路排查方案"建立诊断流程
- 本周完成:Playwright Fallback 工程化集成
- 本周内:增加超时监控与自动重试机制
AIGC 内容探索优先级
- 先试水:《水果冷笑话 AI 剧场》(技术门槛最低,容易量产)
- 同步探索:《水果赛博朋克图鉴》(视觉效果最好,容易出爆款)
- 长期规划:《水果时光倒流机》(技术最复杂,但差异化最强)