update 重构 common-sse 与 common-websocket 合并为 ruoyi-common-push 推送模块
This commit is contained in:
+7
-4
@@ -29,8 +29,11 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
|
|||||||
# 客户端id
|
# 客户端id
|
||||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||||
|
|
||||||
# websocket 开关 默认使用sse推送
|
# 统一消息推送开关
|
||||||
VITE_APP_WEBSOCKET = false
|
VITE_APP_MESSAGE_ENABLED = true
|
||||||
|
|
||||||
# sse 开关
|
# sse / websocket
|
||||||
VITE_APP_SSE = true
|
VITE_APP_MESSAGE_TRANSPORT = 'sse'
|
||||||
|
|
||||||
|
# 统一消息推送路径
|
||||||
|
VITE_APP_MESSAGE_PATH = '/resource/message'
|
||||||
|
|||||||
+7
-4
@@ -32,8 +32,11 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
|
|||||||
# 客户端id
|
# 客户端id
|
||||||
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||||
|
|
||||||
# websocket 开关 默认使用sse推送
|
# 统一消息推送开关
|
||||||
VITE_APP_WEBSOCKET = false
|
VITE_APP_MESSAGE_ENABLED = true
|
||||||
|
|
||||||
# sse 开关
|
# sse / websocket
|
||||||
VITE_APP_SSE = true
|
VITE_APP_MESSAGE_TRANSPORT = 'sse'
|
||||||
|
|
||||||
|
# 统一消息推送路径
|
||||||
|
VITE_APP_MESSAGE_PATH = '/resource/message'
|
||||||
|
|||||||
+7
-2
@@ -2,6 +2,7 @@ import request from '@/utils/request';
|
|||||||
import { AxiosPromise } from 'axios';
|
import { AxiosPromise } from 'axios';
|
||||||
import { LoginData, LoginResult, VerifyCodeResult } from './types';
|
import { LoginData, LoginResult, VerifyCodeResult } from './types';
|
||||||
import { UserInfo } from '@/api/system/user/types';
|
import { UserInfo } from '@/api/system/user/types';
|
||||||
|
import { closePush } from '@/utils/push';
|
||||||
|
|
||||||
// pc端固定客户端授权id
|
// pc端固定客户端授权id
|
||||||
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
|
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
|
||||||
@@ -51,9 +52,13 @@ export function register(data: any) {
|
|||||||
* 注销
|
* 注销
|
||||||
*/
|
*/
|
||||||
export function logout() {
|
export function logout() {
|
||||||
if (import.meta.env.VITE_APP_SSE === 'true') {
|
closePush();
|
||||||
|
if (
|
||||||
|
import.meta.env.VITE_APP_MESSAGE_ENABLED === 'true'
|
||||||
|
&& import.meta.env.VITE_APP_MESSAGE_TRANSPORT.toLowerCase() !== 'websocket'
|
||||||
|
) {
|
||||||
request({
|
request({
|
||||||
url: '/resource/sse/close',
|
url: import.meta.env.VITE_APP_MESSAGE_PATH + '/close',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ import { AppMain, Navbar, Settings, TagsView } from './components';
|
|||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
import { initWebSocket } from '@/utils/websocket';
|
import { initPush } from '@/utils/push';
|
||||||
import { initSSE } from '@/utils/sse';
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const theme = computed(() => settingsStore.theme);
|
const theme = computed(() => settingsStore.theme);
|
||||||
@@ -61,12 +60,7 @@ watchEffect(() => {
|
|||||||
const settingRef = ref<InstanceType<typeof Settings>>();
|
const settingRef = ref<InstanceType<typeof Settings>>();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
initPush();
|
||||||
initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + '/resource/websocket');
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initSSE(import.meta.env.VITE_APP_BASE_API + '/resource/sse');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClickOutside = () => {
|
const handleClickOutside = () => {
|
||||||
|
|||||||
Vendored
+3
-2
@@ -18,8 +18,9 @@ interface ImportMetaEnv {
|
|||||||
VITE_APP_RSA_PUBLIC_KEY: string;
|
VITE_APP_RSA_PUBLIC_KEY: string;
|
||||||
VITE_APP_RSA_PRIVATE_KEY: string;
|
VITE_APP_RSA_PRIVATE_KEY: string;
|
||||||
VITE_APP_CLIENT_ID: string;
|
VITE_APP_CLIENT_ID: string;
|
||||||
VITE_APP_WEBSOCKET: string;
|
VITE_APP_MESSAGE_ENABLED: string;
|
||||||
VITE_APP_SSE: string;
|
VITE_APP_MESSAGE_TRANSPORT: string;
|
||||||
|
VITE_APP_MESSAGE_PATH: string;
|
||||||
}
|
}
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv;
|
readonly env: ImportMetaEnv;
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { getToken } from '@/utils/auth';
|
||||||
|
import { ElNotification } from 'element-plus';
|
||||||
|
import { useNoticeStore } from '@/store/modules/notice';
|
||||||
|
import { parsePushMessage, PUSH_MESSAGE_TYPE, shouldAppendNotice } from '@/utils/push-message';
|
||||||
|
|
||||||
|
let closePushConnection: (() => void) | undefined;
|
||||||
|
|
||||||
|
const appendNotice = (raw: string) => {
|
||||||
|
const payload = parsePushMessage(raw);
|
||||||
|
if (!shouldAppendNotice(payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useNoticeStore().addNotice({
|
||||||
|
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '系统消息',
|
||||||
|
type: payload.type,
|
||||||
|
source: payload.source,
|
||||||
|
message: payload.message ?? '',
|
||||||
|
content: payload.data?.noticeContent,
|
||||||
|
data: payload.data,
|
||||||
|
path: payload.path,
|
||||||
|
query: payload.query,
|
||||||
|
read: false,
|
||||||
|
time: new Date(payload.timestamp ?? Date.now()).toLocaleString()
|
||||||
|
});
|
||||||
|
ElNotification({
|
||||||
|
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '消息',
|
||||||
|
message: payload.message ?? '',
|
||||||
|
type: 'success',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSseUrl = (path: string) => {
|
||||||
|
return `${import.meta.env.VITE_APP_BASE_API}${path}?Authorization=Bearer ${getToken()}&clientid=${import.meta.env.VITE_APP_CLIENT_ID}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildWsUrl = (path: string) => {
|
||||||
|
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||||
|
return `${protocol}${window.location.host}${buildSseUrl(path)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initSsePush = (url: string) => {
|
||||||
|
const { data, error, close } = useEventSource(url, [], {
|
||||||
|
autoReconnect: {
|
||||||
|
retries: 5,
|
||||||
|
delay: 5000,
|
||||||
|
onFailed() {
|
||||||
|
console.log('Failed to connect after 5 retries');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
closePushConnection = close;
|
||||||
|
|
||||||
|
watch(error, () => {
|
||||||
|
console.log('SSE connection error:', error.value);
|
||||||
|
error.value = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(data, () => {
|
||||||
|
if (!data.value) return;
|
||||||
|
appendNotice(data.value);
|
||||||
|
data.value = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWsPush = (url: string) => {
|
||||||
|
const { close } = useWebSocket(url, {
|
||||||
|
autoReconnect: {
|
||||||
|
retries: 3,
|
||||||
|
delay: 1000,
|
||||||
|
onFailed() {
|
||||||
|
console.log('websocket重连失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
heartbeat: {
|
||||||
|
message: 'ping',
|
||||||
|
interval: 10000,
|
||||||
|
pongTimeout: 2000
|
||||||
|
},
|
||||||
|
onConnected() {
|
||||||
|
console.log('websocket已经连接');
|
||||||
|
},
|
||||||
|
onDisconnected() {
|
||||||
|
console.log('websocket已经断开');
|
||||||
|
},
|
||||||
|
onMessage: (_, e) => {
|
||||||
|
if (String(e.data) === 'pong') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appendNotice(String(e.data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
closePushConnection = close;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initPush = () => {
|
||||||
|
closePush();
|
||||||
|
if (import.meta.env.VITE_APP_MESSAGE_ENABLED === 'false') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = import.meta.env.VITE_APP_MESSAGE_PATH || '/resource/message';
|
||||||
|
const transport = import.meta.env.VITE_APP_MESSAGE_TRANSPORT || 'sse';
|
||||||
|
if (transport.toLowerCase() === 'websocket') {
|
||||||
|
initWsPush(buildWsUrl(path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initSsePush(buildSseUrl(path));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closePush = () => {
|
||||||
|
closePushConnection?.();
|
||||||
|
closePushConnection = undefined;
|
||||||
|
};
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { getToken } from '@/utils/auth';
|
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
import { useNoticeStore } from '@/store/modules/notice';
|
|
||||||
import { parsePushMessage, PUSH_MESSAGE_TYPE, shouldAppendNotice } from '@/utils/push-message';
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
export const initSSE = (url: any) => {
|
|
||||||
if (import.meta.env.VITE_APP_SSE === 'false') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(error, () => {
|
|
||||||
console.log('SSE connection error:', error.value);
|
|
||||||
error.value = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(data, () => {
|
|
||||||
if (!data.value) return;
|
|
||||||
const payload = parsePushMessage(data.value);
|
|
||||||
if (shouldAppendNotice(payload)) {
|
|
||||||
useNoticeStore().addNotice({
|
|
||||||
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '系统消息',
|
|
||||||
type: payload.type,
|
|
||||||
source: payload.source,
|
|
||||||
message: payload.message ?? '',
|
|
||||||
content: payload.data?.noticeContent,
|
|
||||||
data: payload.data,
|
|
||||||
path: payload.path,
|
|
||||||
query: payload.query,
|
|
||||||
read: false,
|
|
||||||
time: new Date(payload.timestamp ?? Date.now()).toLocaleString()
|
|
||||||
});
|
|
||||||
ElNotification({
|
|
||||||
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '消息',
|
|
||||||
message: payload.message ?? '',
|
|
||||||
type: 'success',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
data.value = null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { getToken } from '@/utils/auth';
|
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
import { useNoticeStore } from '@/store/modules/notice';
|
|
||||||
import { parsePushMessage, PUSH_MESSAGE_TYPE, shouldAppendNotice } from '@/utils/push-message';
|
|
||||||
|
|
||||||
// 初始化socket
|
|
||||||
export const initWebSocket = (url: any) => {
|
|
||||||
if (import.meta.env.VITE_APP_WEBSOCKET === 'false') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
|
|
||||||
useWebSocket(url, {
|
|
||||||
autoReconnect: {
|
|
||||||
// 重连最大次数
|
|
||||||
retries: 3,
|
|
||||||
// 重连间隔
|
|
||||||
delay: 1000,
|
|
||||||
onFailed() {
|
|
||||||
console.log('websocket重连失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
heartbeat: {
|
|
||||||
message: JSON.stringify({ type: 'ping' }),
|
|
||||||
// 发送心跳的间隔
|
|
||||||
interval: 10000,
|
|
||||||
// 接收到心跳response的超时时间
|
|
||||||
pongTimeout: 2000
|
|
||||||
},
|
|
||||||
onConnected() {
|
|
||||||
console.log('websocket已经连接');
|
|
||||||
},
|
|
||||||
onDisconnected() {
|
|
||||||
console.log('websocket已经断开');
|
|
||||||
},
|
|
||||||
onMessage: (_, e) => {
|
|
||||||
if (typeof e.data === 'string' && e.data.includes('ping')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const payload = parsePushMessage(String(e.data));
|
|
||||||
if (shouldAppendNotice(payload)) {
|
|
||||||
useNoticeStore().addNotice({
|
|
||||||
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '系统消息',
|
|
||||||
type: payload.type,
|
|
||||||
source: payload.source,
|
|
||||||
message: payload.message ?? '',
|
|
||||||
content: payload.data?.noticeContent,
|
|
||||||
data: payload.data,
|
|
||||||
path: payload.path,
|
|
||||||
query: payload.query,
|
|
||||||
read: false,
|
|
||||||
time: new Date(payload.timestamp ?? Date.now()).toLocaleString()
|
|
||||||
});
|
|
||||||
ElNotification({
|
|
||||||
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '消息',
|
|
||||||
message: payload.message ?? '',
|
|
||||||
type: 'success',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user