From 890433063221b66ebaac88054ddf42fda7207413 Mon Sep 17 00:00:00 2001 From: hezhaohui Date: Tue, 28 Jul 2026 17:06:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(task-logs):=20=E5=AE=9E=E7=8E=B0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=97=A5=E5=BF=97=20SSE=20=E5=AE=9E=E6=97=B6=E6=8E=A8?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现任务日志 SSE 实时推送功能,解决前端无法流式输出日志的问题 变更内容: - 后端:新增 /task-logs/stream SSE 端点,支持实时日志推送 - 后端:实现 2 秒轮询间隔检测新日志,支持心跳机制 - 后端:支持通过 query 参数 access_token 传递 JWT 认证 - 前端:新增 createTaskLogStream 函数封装 EventSource - 前端:修改 TaskCenter.vue 集成 SSE,支持实时更新和自动滚动 - 前端:添加流式连接状态指示器 关联 commit: - 实现后端 SSE Stream 方法 - 注册 SSE 路由 - 实现前端 SSE 客户端 - 集成 SSE 到任务中心页面 --- frontend/src/api/taskLog.ts | 46 ++++++ frontend/src/views/task/TaskCenter.vue | 69 ++++++++- server/internal/handler/task_log.go | 195 +++++++++++++++++++++++++ server/internal/router/router.go | 1 + 4 files changed, 309 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/taskLog.ts b/frontend/src/api/taskLog.ts index 34b7050..4791b15 100644 --- a/frontend/src/api/taskLog.ts +++ b/frontend/src/api/taskLog.ts @@ -79,3 +79,49 @@ function parseResponseBody(text: string) { return { error: text } } } + +export interface TaskLogStreamCallbacks { + onInit?: (lines: TaskLogLine[]) => void + onUpdate?: (lines: TaskLogLine[]) => void + onFinished?: () => void + onError?: (error: string) => void +} + +export function createTaskLogStream(taskId: string, callbacks: TaskLogStreamCallbacks): EventSource { + const token = getToken() + const url = `/auth/api/v1/task-logs/stream?task_id=${encodeURIComponent(taskId)}&access_token=${encodeURIComponent(token || '')}` + + const es = new EventSource(url) + + es.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + switch (data.type) { + case 'init': + callbacks.onInit?.(Array.isArray(data.lines) ? data.lines : []) + break + case 'update': + callbacks.onUpdate?.(Array.isArray(data.lines) ? data.lines : []) + break + case 'finished': + callbacks.onFinished?.() + es.close() + break + case 'error': + callbacks.onError?.(data.error || 'Unknown error') + break + } + } catch { + // ignore parse errors for heartbeat etc. + } + } + + es.onerror = () => { + // EventSource will auto-reconnect, but we report connection errors + if (es.readyState === EventSource.CLOSED) { + callbacks.onError?.('Connection closed') + } + } + + return es +} diff --git a/frontend/src/views/task/TaskCenter.vue b/frontend/src/views/task/TaskCenter.vue index 021802c..ace6795 100644 --- a/frontend/src/views/task/TaskCenter.vue +++ b/frontend/src/views/task/TaskCenter.vue @@ -52,6 +52,7 @@

任务日志 {{ selectedTaskName }} + ● 实时更新中

{{ selectedTaskMeta }} @@ -72,8 +73,8 @@