mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +08:00
merge: SSE 改用原生 EventSource + 'notice' event + SseEnvelope 强类型信封
This commit is contained in:
commit
fe8e294775
@ -35,6 +35,7 @@
|
||||
"image-conversion": "2.1.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.5.4",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.4",
|
||||
"screenfull": "6.0.2",
|
||||
|
||||
102
src/utils/sse.ts
102
src/utils/sse.ts
@ -1,42 +1,102 @@
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { useNoticeStore } from '@/store/modules/notice';
|
||||
import { getSseEventBus } from '@/utils/sseEventBus';
|
||||
|
||||
/**
|
||||
* Strongly-typed SSE envelope shared by every event:
|
||||
* { msgType: "...", data: { ...business fields... } }
|
||||
* Frontend dispatches by msgType, then casts data to the corresponding shape.
|
||||
*/
|
||||
interface SseEnvelope<T = unknown> {
|
||||
msgType: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/** data shape under msgType in {LOGIN_WELCOME, WORKFLOW_TASK, SYSTEM_NOTICE} */
|
||||
interface NoticeData {
|
||||
title: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Business-level SSE event names forwarded to the global event bus.
|
||||
// Feature modules subscribe via getSseEventBus().on('xxx', handler).
|
||||
const FORWARD_EVENTS = ['customerservice'];
|
||||
|
||||
const MAX_RETRIES = 5;
|
||||
const RETRY_DELAY = 5000;
|
||||
|
||||
let eventSource: EventSource | null = null;
|
||||
let retries = 0;
|
||||
|
||||
// 初始化
|
||||
export const initSSE = (url: any) => {
|
||||
if (import.meta.env.VITE_APP_SSE === 'false') {
|
||||
return;
|
||||
}
|
||||
connect(url);
|
||||
};
|
||||
|
||||
url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
|
||||
const { data, error } = useEventSource(url, [], {
|
||||
autoReconnect: {
|
||||
retries: 5,
|
||||
delay: 5000,
|
||||
onFailed() {
|
||||
console.log('Failed to connect after 5 retries');
|
||||
}
|
||||
function parseEnvelope<T = unknown>(raw: string): SseEnvelope<T> | null {
|
||||
try {
|
||||
const env = JSON.parse(raw);
|
||||
if (env && typeof env === 'object' && typeof env.msgType === 'string') {
|
||||
return env as SseEnvelope<T>;
|
||||
}
|
||||
});
|
||||
console.warn('SSE: payload missing msgType', raw);
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.warn('SSE: invalid JSON envelope', raw, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
watch(error, () => {
|
||||
console.log('SSE connection error:', error.value);
|
||||
error.value = null;
|
||||
});
|
||||
function connect(url: string) {
|
||||
const fullUrl = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
|
||||
eventSource = new EventSource(fullUrl);
|
||||
|
||||
watch(data, () => {
|
||||
if (!data.value) return;
|
||||
// "notice" event: login welcome / workflow task / system announcement.
|
||||
// All three share NoticeData shape; msgType differentiates the source.
|
||||
eventSource.addEventListener('notice', (e: MessageEvent) => {
|
||||
if (!e.data) return;
|
||||
const env = parseEnvelope<NoticeData>(e.data);
|
||||
if (!env) return;
|
||||
const { title, message } = env.data;
|
||||
useNoticeStore().addNotice({
|
||||
message: data.value,
|
||||
title,
|
||||
message,
|
||||
read: false,
|
||||
time: new Date().toLocaleString()
|
||||
});
|
||||
ElNotification({
|
||||
title: '消息',
|
||||
message: data.value,
|
||||
title,
|
||||
message,
|
||||
type: 'success',
|
||||
duration: 3000
|
||||
});
|
||||
data.value = null;
|
||||
});
|
||||
};
|
||||
|
||||
// Business events: forward to global bus, feature modules subscribe themselves
|
||||
const bus = getSseEventBus();
|
||||
FORWARD_EVENTS.forEach((name) => {
|
||||
eventSource!.addEventListener(name, (e: MessageEvent) => {
|
||||
bus.emit(name, e.data);
|
||||
});
|
||||
});
|
||||
|
||||
eventSource.onopen = () => {
|
||||
retries = 0;
|
||||
console.log('SSE connected');
|
||||
};
|
||||
|
||||
eventSource.onerror = (err) => {
|
||||
console.log('SSE connection error:', err);
|
||||
eventSource?.close();
|
||||
eventSource = null;
|
||||
if (retries < MAX_RETRIES) {
|
||||
retries += 1;
|
||||
setTimeout(() => connect(url), RETRY_DELAY);
|
||||
} else {
|
||||
console.log('SSE failed after', MAX_RETRIES, 'retries');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
21
src/utils/sseEventBus.ts
Normal file
21
src/utils/sseEventBus.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import mitt, { Emitter } from 'mitt';
|
||||
|
||||
/**
|
||||
* SSE event bus events shape: protocol-level event name -> raw data string
|
||||
* Consumers JSON.parse the payload themselves
|
||||
*/
|
||||
export type SseBusEvents = Record<string, string>;
|
||||
|
||||
let bus: Emitter<SseBusEvents> | null = null;
|
||||
|
||||
/**
|
||||
* Lazily-initialized singleton event bus
|
||||
* Used by sse.ts to forward business event names (e.g. "customerservice")
|
||||
* to feature modules without creating a second EventSource
|
||||
*/
|
||||
export function getSseEventBus(): Emitter<SseBusEvents> {
|
||||
if (!bus) {
|
||||
bus = mitt<SseBusEvents>();
|
||||
}
|
||||
return bus;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user