feat(task-logs): 实现任务日志 SSE 实时推送

实现任务日志 SSE 实时推送功能,解决前端无法流式输出日志的问题

变更内容:
- 后端:新增 /task-logs/stream SSE 端点,支持实时日志推送
- 后端:实现 2 秒轮询间隔检测新日志,支持心跳机制
- 后端:支持通过 query 参数 access_token 传递 JWT 认证
- 前端:新增 createTaskLogStream 函数封装 EventSource
- 前端:修改 TaskCenter.vue 集成 SSE,支持实时更新和自动滚动
- 前端:添加流式连接状态指示器

关联 commit:
- 实现后端 SSE Stream 方法
- 注册 SSE 路由
- 实现前端 SSE 客户端
- 集成 SSE 到任务中心页面
This commit is contained in:
2026-07-28 17:06:44 +08:00
parent 7d6efeb3dd
commit 8904330632
4 changed files with 309 additions and 2 deletions
+46
View File
@@ -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
}