feat(wordcloud): 收口在途开发(布局/存储/前端)+ R4 WCD 生产任务(jobs wcd_file)与生产订单列表
This commit is contained in:
@@ -45,7 +45,7 @@ class JobRunner:
|
||||
return current_stage, current_progress
|
||||
|
||||
def run(self, job_id: str, paths: JobPaths, config: dict) -> None:
|
||||
t_start = time.time()
|
||||
t_start = time.perf_counter()
|
||||
log.info("=" * 50)
|
||||
log.info("[Runner] 任务启动 job_id=%s", job_id)
|
||||
log.info(" config_path = %s", paths.config_path)
|
||||
@@ -65,6 +65,11 @@ class JobRunner:
|
||||
]
|
||||
|
||||
env = os.environ.copy()
|
||||
# Force unbuffered stdout so every print() flushes immediately and the
|
||||
# frontend SSE log view shows each step in real time. Without this,
|
||||
# Python block-buffers stdout when it is a pipe, so lines pile up and
|
||||
# only arrive in bursts after the buffer fills or the process exits.
|
||||
env["PYTHONUNBUFFERED"] = "1"
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=str(self.project_root),
|
||||
@@ -77,24 +82,63 @@ class JobRunner:
|
||||
|
||||
stage = "starting"
|
||||
progress = 1
|
||||
output_lines: list[str] = [] # collect all output lines for error reporting
|
||||
preview_sent = False
|
||||
|
||||
assert process.stdout is not None
|
||||
for raw in process.stdout:
|
||||
line = raw.rstrip("\n")
|
||||
output_lines.append(line)
|
||||
log.info("[Pipeline] %s", line)
|
||||
stage, progress = self._parse_stage(line, stage, progress)
|
||||
elapsed = time.perf_counter() - t_start
|
||||
self.manager.add_event(
|
||||
job_id,
|
||||
kind="log",
|
||||
stage=stage,
|
||||
progress_percent=progress,
|
||||
message=line,
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
# The PNG is complete before SVG/DB export starts. Publish it as a
|
||||
# preview so the frontend does not wait for the slower artifacts.
|
||||
if not preview_sent and line.startswith("已保存:"):
|
||||
candidate = Path(line.split(":", 1)[1].strip())
|
||||
if candidate.suffix.lower() == ".png" and candidate.exists():
|
||||
self.manager.set_artifacts(job_id, {"png": str(candidate)})
|
||||
self.manager.add_event(
|
||||
job_id,
|
||||
kind="status",
|
||||
stage="preview_ready",
|
||||
progress_percent=max(progress, 94),
|
||||
message="预览已生成,后台继续导出其余文件",
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
preview_sent = True
|
||||
|
||||
ret = process.wait()
|
||||
elapsed = time.time() - t_start
|
||||
elapsed = time.perf_counter() - t_start
|
||||
log.info("[Runner] 子进程退出 code=%d 耗时=%.2fs", ret, elapsed)
|
||||
|
||||
# ── 错误时:截取最后 30 行输出作为详细错误信息 ─────────────
|
||||
error_detail = None
|
||||
if ret != 0:
|
||||
# 找到 "生成失败" 或 "错误" 或 traceback 之后的内容
|
||||
error_lines = []
|
||||
captured = False
|
||||
for line in reversed(output_lines):
|
||||
if not captured:
|
||||
error_lines.append(line)
|
||||
if any(kw in line for kw in ("生成失败", "error", "Error", "Traceback", "traceback", "未满足", "放置")):
|
||||
captured = True
|
||||
elif len(error_lines) < 30:
|
||||
error_lines.append(line)
|
||||
else:
|
||||
break
|
||||
error_lines.reverse()
|
||||
error_detail = "\n".join(error_lines) if error_lines else f"script exited with code {ret}"
|
||||
log.info("[Runner] 错误详情:\n%s", error_detail)
|
||||
|
||||
png = next(paths.output_dir.glob("*.png"), None)
|
||||
# NOTE: do NOT use "*[!_stroke].svg" — in glob, [!...] is a character class,
|
||||
# so filenames ending with "e.svg" (e.g. AutoResize.svg) are incorrectly skipped.
|
||||
@@ -142,15 +186,24 @@ class JobRunner:
|
||||
return
|
||||
|
||||
if ret == 0:
|
||||
done_message = f"任务完成,用时 {elapsed:.2f} 秒"
|
||||
log.info("[Runner] ✅ 任务完成 job_id=%s 总耗时=%.2fs", job_id, elapsed)
|
||||
self.manager.add_event(
|
||||
job_id,
|
||||
kind="status",
|
||||
stage="completed",
|
||||
progress_percent=100,
|
||||
message="任务完成",
|
||||
message=done_message,
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
self.manager.set_status(
|
||||
job_id,
|
||||
status="success",
|
||||
stage="completed",
|
||||
progress_percent=100,
|
||||
message=done_message,
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
self.manager.set_status(job_id, status="success", stage="completed", progress_percent=100, message="任务完成")
|
||||
else:
|
||||
log.error("[Runner] ❌ 任务失败 job_id=%s exit_code=%d 耗时=%.2fs", job_id, ret, elapsed)
|
||||
self.manager.add_event(
|
||||
@@ -158,7 +211,8 @@ class JobRunner:
|
||||
kind="status",
|
||||
stage="failed",
|
||||
progress_percent=100,
|
||||
message=f"任务失败,退出码: {ret}",
|
||||
message=f"任务失败,退出码: {ret},用时 {elapsed:.2f} 秒",
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
self.manager.set_status(
|
||||
job_id,
|
||||
@@ -166,5 +220,6 @@ class JobRunner:
|
||||
stage="failed",
|
||||
progress_percent=100,
|
||||
message="任务失败",
|
||||
error=f"script exited with code {ret}",
|
||||
error=error_detail or f"script exited with code {ret}",
|
||||
elapsed_seconds=round(elapsed, 3),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user