refactor(websocket): 重构 WebSocket 和 SSE 连接逻辑以支持开发和生产环境

- 统一处理 WebSocket 和 SSE 的 URL 构造逻辑
- 开发环境使用代理前缀,生产环境使用同域路径
- 移除硬编码端口,通过环境变量配置
This commit is contained in:
2025-09-21 23:52:06 +08:00
parent 7637a5c225
commit 067c6829f0

View File

@ -144,40 +144,31 @@ export class CustomService {
// 构造 WS URL
const base = (api.defaults.baseURL || '') as string; // 可能是 /api 或 http(s)://host/api
function toWsUrl(httpUrl: string) {
if (httpUrl.startsWith('https://')) return 'wss://' + httpUrl.slice('https://'.length);
if (httpUrl.startsWith('http://')) return 'ws://' + httpUrl.slice('http://'.length);
// 相对路径:拼 window.location
const origin = window.location.origin; // http(s)://host:port
const full = origin.replace(/^http/, 'ws') + (httpUrl.startsWith('/') ? httpUrl : '/' + httpUrl);
return full;
}
const path = `/flows/${id}/run/ws`;
// 取 token 放到查询参数WS 握手无法自定义 Authorization 头部)
const token = getToken();
// 新增WS 使用独立端口,默认 8855可通过 VITE_WS_PORT 覆盖
const wsPort = (import.meta as any).env?.VITE_WS_PORT || '8855';
let wsBase: string;
// 解析出 API 的路径前缀(用于生产环境相对路径),默认 /api
let apiPathPrefix = '/api';
if (base.startsWith('http://') || base.startsWith('https://')) {
try {
const u = new URL(base);
const proto = u.protocol === 'https:' ? 'wss:' : 'ws:';
u.protocol = proto;
u.port = wsPort; // 改为 WS 端口
wsBase = `${u.protocol}//${u.host}${u.pathname.replace(/\/$/, '')}`;
} catch {
const loc = window.location;
const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
wsBase = `${proto}//${loc.hostname}:${wsPort}${base.startsWith('/') ? base : '/' + base}`;
}
} else {
const loc = window.location;
const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
wsBase = `${proto}//${loc.hostname}:${wsPort}${base.startsWith('/') ? base : '/' + base}`;
apiPathPrefix = (u.pathname.replace(/\/$/, '') || '/api');
} catch {}
} else if (base.startsWith('/')) {
apiPathPrefix = (base.replace(/\/$/, '') || '/api');
}
const wsUrl = wsBase + path + (token ? (wsBase.includes('?') ? `&access_token=${encodeURIComponent(token)}` : `?access_token=${encodeURIComponent(token)}`) : '');
const path = `/flows/${id}/run/ws`;
// 取 token 放到查询参数WS 握手无法自定义 Authorization 头部)
const token = getToken();
const isDev = !!((import.meta as any).env?.DEV);
// 开发:走 /ws 前缀(由 Vite 代理到 8855 并 rewrite 到 /api
// 生产:走同域 /api 前缀,由 Nginx/网关反代,不显式暴露端口
const prefix = isDev ? '/ws' : apiPathPrefix;
const qs = token ? `?access_token=${encodeURIComponent(token)}` : '';
// 始终构造绝对 WS URL避免浏览器兼容性问题
const loc = window.location;
const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
const origin = `${proto}//${loc.host}`; // host 里已包含端口(开发时 5173线上通常无端口
const wsUrl = `${origin}${prefix}${path}${qs}`;
let ws: WebSocket | null = null;
let resolveDone: (v: RunResult | null) => void;
@ -253,27 +244,34 @@ export class CustomService {
return { cancel: () => {}, done: Promise.resolve<RunResult | null>(null) } as const;
}
const base = (api.defaults.baseURL || '') as string;
// 参照 WSSSE 使用独立端口,默认 8866可通过 VITE_SSE_PORT 覆盖
const ssePort = (import.meta as any).env?.VITE_SSE_PORT || '8866';
let sseBase: string;
if (base.startsWith('http://') || base.startsWith('https://')) {
try {
const u = new URL(base);
// 协议保持与 base 一致,仅替换端口
u.port = ssePort;
sseBase = `${u.protocol}//${u.host}${u.pathname.replace(/\/$/, '')}`;
} catch {
// 在开发环境通过 Vite 代理前缀 /sse 转发到 8866vite.config.ts 已配置 rewrite 到 /api
const useSseProxy = !!((import.meta as any).env?.DEV);
let url: string;
if (useSseProxy) {
url = `/sse/flows/${id}/run/stream`;
} else {
const base = (api.defaults.baseURL || '') as string;
// 参照 WSSSE 使用独立端口,默认 8866可通过 VITE_SSE_PORT 覆盖
const ssePort = (import.meta as any).env?.VITE_SSE_PORT || '8866';
let sseBase: string;
if (base.startsWith('http://') || base.startsWith('https://')) {
try {
const u = new URL(base);
// 协议保持与 base 一致,仅替换端口
u.port = ssePort;
sseBase = `${u.protocol}//${u.host}${u.pathname.replace(/\/$/, '')}`;
} catch {
const loc = window.location;
sseBase = `${loc.protocol}//${loc.hostname}:${ssePort}${base.startsWith('/') ? base : '/' + base}`;
}
} else {
const loc = window.location;
sseBase = `${loc.protocol}//${loc.hostname}:${ssePort}${base.startsWith('/') ? base : '/' + base}`;
}
} else {
const loc = window.location;
sseBase = `${loc.protocol}//${loc.hostname}:${ssePort}${base.startsWith('/') ? base : '/' + base}`;
url = sseBase + `/flows/${id}/run/stream`;
}
const url = sseBase + `/flows/${id}/run/stream`;
const { cancel, done } = postSSE<RunResult | null>(url, { input }, {
onMessage: (json: any) => {
try {