|
|
@@ -63,6 +63,7 @@
|
|
|
import { Icon } from '@/core/components/Icon';
|
|
|
import { useMessage } from '@/core/hooks/web/useMessage';
|
|
|
import { calcTask, getGovernTaskSseUrl, testSse } from '@/case/api/govern/governApi';
|
|
|
+ import { getToken } from '@/core/utils/auth';
|
|
|
|
|
|
type GovernRowStatus = 'pending' | 'running' | 'success' | 'fail';
|
|
|
|
|
|
@@ -385,22 +386,85 @@
|
|
|
eventSource?.close();
|
|
|
sseConnected.value = false;
|
|
|
errorText.value = '';
|
|
|
- eventSource = new EventSource(getGovernTaskSseUrl());
|
|
|
- eventSource.onopen = () => {
|
|
|
- sseConnected.value = true;
|
|
|
- errorText.value = '';
|
|
|
- };
|
|
|
- eventSource.onmessage = (event) => {
|
|
|
- handleSseMessage(event.data);
|
|
|
- };
|
|
|
- eventSource.onerror = () => {
|
|
|
- sseConnected.value = false;
|
|
|
- if (!finishedAt.value && !errorText.value) {
|
|
|
- errorText.value = 'SSE 连接中断,请稍后重试。';
|
|
|
+ // sa-token 仅从请求头读取 token,原生 EventSource 带不了自定义请求头,
|
|
|
+ // 这里与 cleanProgress.vue 一致改用 fetch 流式读取(EventSource 语义手动对齐)。
|
|
|
+ const controller = new AbortController();
|
|
|
+ // 占位句柄:让 cleanup() 的 eventSource?.close() 语义保持不变,支持 abort 中断
|
|
|
+ const handle = { close: () => controller.abort() } as unknown as EventSource;
|
|
|
+ eventSource = handle;
|
|
|
+
|
|
|
+ void (async () => {
|
|
|
+ try {
|
|
|
+ const token = getToken();
|
|
|
+ const res = await fetch(getGovernTaskSseUrl(), {
|
|
|
+ headers: {
|
|
|
+ Accept: 'text/event-stream',
|
|
|
+ ...(token ? { 'x-token': token as string } : {}),
|
|
|
+ },
|
|
|
+ credentials: 'include',
|
|
|
+ signal: controller.signal,
|
|
|
+ });
|
|
|
+ const contentType = res.headers.get('content-type') || '';
|
|
|
+ if (!res.ok || !contentType.includes('text/event-stream')) {
|
|
|
+ // 未登录等场景后端返回的是普通 Result JSON
|
|
|
+ let msg = 'SSE 连接失败';
|
|
|
+ try {
|
|
|
+ const json = await res.json();
|
|
|
+ msg = json?.message || msg;
|
|
|
+ } catch {
|
|
|
+ /* ignore */
|
|
|
+ }
|
|
|
+ sseConnected.value = false;
|
|
|
+ if (!errorText.value) {
|
|
|
+ errorText.value = msg;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ sseConnected.value = true;
|
|
|
+ errorText.value = '';
|
|
|
+ const reader = res.body?.getReader();
|
|
|
+ if (!reader) {
|
|
|
+ errorText.value = 'SSE 连接中断,请稍后重试。';
|
|
|
+ sseConnected.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const decoder = new TextDecoder();
|
|
|
+ let buffer = '';
|
|
|
+ for (;;) {
|
|
|
+ const { value, done } = await reader.read();
|
|
|
+ if (done) break;
|
|
|
+ buffer += decoder.decode(value, { stream: true });
|
|
|
+ let idx = buffer.indexOf('\n\n');
|
|
|
+ while (idx >= 0) {
|
|
|
+ const frame = buffer.slice(0, idx);
|
|
|
+ buffer = buffer.slice(idx + 2);
|
|
|
+ const data = frame
|
|
|
+ .split('\n')
|
|
|
+ .filter((line) => line.startsWith('data:'))
|
|
|
+ .map((line) => line.slice(5).trimStart())
|
|
|
+ .join('\n');
|
|
|
+ if (data) {
|
|
|
+ handleSseMessage(data);
|
|
|
+ }
|
|
|
+ idx = buffer.indexOf('\n\n');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 服务端关闭连接:与 EventSource 的 onerror 语义对齐
|
|
|
+ sseConnected.value = false;
|
|
|
+ if (!finishedAt.value && !errorText.value) {
|
|
|
+ errorText.value = 'SSE 连接中断,请稍后重试。';
|
|
|
+ }
|
|
|
+ eventSource = null;
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error?.name !== 'AbortError') {
|
|
|
+ sseConnected.value = false;
|
|
|
+ if (!finishedAt.value && !errorText.value) {
|
|
|
+ errorText.value = 'SSE 连接中断,请稍后重试。';
|
|
|
+ }
|
|
|
+ eventSource = null;
|
|
|
+ }
|
|
|
}
|
|
|
- eventSource?.close();
|
|
|
- eventSource = null;
|
|
|
- };
|
|
|
+ })();
|
|
|
};
|
|
|
|
|
|
const startTask = async () => {
|