fix: 统一工作流三个出口的错误反馈

- 关联 EASY-2,补齐安全错误、节点展示与执行标识

- 保留历史可读摘要并验证接口、SSE 与界面兼容
This commit is contained in:
2026-09-08 10:49:02 +08:00
parent 9722bea701
commit c7c301d3b9
35 changed files with 1171 additions and 358 deletions

View File

@@ -25,7 +25,11 @@ import {
} from '#/utils/workflow-share-context';
import { refreshTokenApi } from './core';
import { isInactiveSseRequest } from './sseRequestLifecycle';
import {
isInactiveSseRequest,
readSseRequestError,
SseRequestError,
} from './sseRequestLifecycle';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const ERROR_MESSAGE_DEDUP_WINDOW = 800;
@@ -235,7 +239,7 @@ export class SseClient {
});
if (!res.ok) {
const error = new Error(`HTTP ${res.status}: ${res.statusText}`);
const error = await readSseRequestError(res);
options?.onError?.(error);
return;
}
@@ -258,7 +262,7 @@ export class SseClient {
}
}
showErrorOnce(errorMessage);
options?.onError?.(new Error(errorMessage));
options?.onError?.(new SseRequestError(res.status, errorMessage));
return;
}

View File

@@ -1,6 +1,10 @@
import { describe, expect, it } from 'vitest';
import { isInactiveSseRequest } from './sseRequestLifecycle';
import {
isInactiveSseRequest,
readSseRequestError,
SseRequestError,
} from './sseRequestLifecycle';
describe('sseRequestLifecycle', () => {
it('treats an explicit abort as an inactive request', () => {
@@ -17,3 +21,19 @@ describe('sseRequestLifecycle', () => {
expect(isInactiveSseRequest(controller.signal, 1, 1)).toBe(false);
});
});
it('keeps HTTP rejection distinguishable from transport interruption', async () => {
for (const status of [400, 403, 500]) {
const error = await readSseRequestError(
new Response(JSON.stringify({ message: '运行请求被拒绝' }), { status }),
);
expect(error).toBeInstanceOf(SseRequestError);
expect(error.status).toBe(status);
expect(error.message).toBe('运行请求被拒绝');
}
const error = await readSseRequestError(
new Response('<html>PRIVATE_GATEWAY_BODY</html>', { status: 502 }),
);
expect(error.message).toContain('502');
expect(error.message).not.toContain('PRIVATE_GATEWAY_BODY');
});

View File

@@ -8,3 +8,24 @@ export function isInactiveSseRequest(
) {
return signal.aborted || currentRequestId !== requestId;
}
/** 服务端明确拒绝请求,与已经建立的事件流断线区分。 */
export class SseRequestError extends Error {
constructor(
public readonly status: number,
message: string,
) {
super(message);
this.name = 'SseRequestError';
}
}
export async function readSseRequestError(response: Response) {
let message = `请求失败HTTP ${response.status}`;
try {
const body = await response.json();
if (typeof body?.message === 'string') message = body.message;
} catch {
// 网关可能返回 HTML只展示状态不把原始正文当作错误文案。
}
return new SseRequestError(response.status, message);
}