feat: 文件夹批量处理引擎(后端)

- BatchWorker 单线程轮询 batch_jobs 表,处理 source=batch 的运行,
  与主调度器互不抢占(next_queued_run 排除 batch 来源)
- 直接读取用户所选文件夹下的视频逐个执行流水线,不上传到工作目录;
  中间态与产物落在视频旁同名文件夹,batch.done.json 完成标记去重
- 支持暂停/继续、失败容错(单视频失败不阻塞后续)、删除任务只清库
- 孤儿清理跳过 source=batch 运行,防止误删用户视频文件夹
- workflow_runs 新增 source 列(upload/batch),旧库自动迁移
This commit is contained in:
2026-08-23 16:25:09 +08:00
parent b16e0e9f3c
commit 711867e79f
10 changed files with 2056 additions and 9 deletions
+12 -3
View File
@@ -21,10 +21,11 @@ from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from fastapi.staticfiles import StaticFiles # noqa: E402
from wov_app import registry
from wov_app.config import DB_PATH, STORAGE_DIR, WORKSPACE_ROOT
from wov_app.batch import BatchWorker
from wov_app.config import BATCH_ENABLED, DB_PATH, STORAGE_DIR, WORKSPACE_ROOT
from wov_app.db import Database
from wov_app.maintenance import OrphanCleaner
from wov_app.routers import apps, workflows
from wov_app.routers import apps, batch, workflows
from wov_app.scheduler import WorkflowScheduler
from wov_app.seed import seed_default_workflows
@@ -50,12 +51,19 @@ async def lifespan(app: FastAPI):
# 孤儿数据清理默认开启,定时清除死数据;测试可关闭。
if os.getenv("WOV_CLEANUP_ENABLED", "1") == "1":
cleaner.start()
# 批量处理引擎:处理用户所选文件夹的批量任务(source=batch 的运行),
# 与主调度器互不抢占;默认开启,测试可关闭。
batch_worker = BatchWorker(db)
if os.getenv("WOV_BATCH_ENABLED", "1") == "1":
batch_worker.start()
# 共享对象挂到 app.state,路由通过 Depends 延迟获取。
app.state.db = db
app.state.scheduler = scheduler
app.state.cleaner = cleaner
app.state.batch = batch_worker
yield
# 退出时先停调度器与清理器,避免残留后台线程。
# 退出时先停批量引擎、调度器与清理器,避免残留后台线程。
batch_worker.stop()
cleaner.stop()
scheduler.stop()
@@ -72,6 +80,7 @@ app.add_middleware(
app.include_router(workflows.router)
app.include_router(apps.router)
app.include_router(batch.router)
@app.get("/health")