From 89f5e8577eeb08376f2165048655baef624e208f Mon Sep 17 00:00:00 2001 From: xlsea Date: Mon, 2 Sep 2024 09:34:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B4=E5=90=88=20sse=20=E6=8E=A8?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.test | 7 +-- src/hooks/common/table.ts | 17 ++++--- src/layouts/base-layout/index.vue | 2 + .../global-header/components/user-avatar.vue | 2 +- src/service/api/auth.ts | 14 ++++++ src/service/request/index.ts | 7 +-- src/store/modules/auth/index.ts | 8 +++- src/store/modules/route/index.ts | 2 +- src/typings/api/api.d.ts | 2 +- src/typings/env.d.ts | 5 ++- src/utils/service.ts | 2 +- src/utils/sse.ts | 45 +++++++++++++++++++ src/utils/websocket.ts | 13 ++++-- src/views/_builtin/iframe-page/[url].vue | 2 + .../_builtin/login/modules/pwd-login.vue | 2 +- 15 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 src/utils/sse.ts diff --git a/.env.test b/.env.test index 687478a3..688df37c 100644 --- a/.env.test +++ b/.env.test @@ -1,15 +1,16 @@ # backend service base url, test environment -VITE_SERVICE_BASE_URL=http://127.0.0.1:8080 +VITE_SERVICE_BASE_URL=http://154.44.10.176:8080 VITE_APP_BASE_API=/dev-api -VITE_APP_WEBSOCKET=true +VITE_APP_WEBSOCKET=N +VITE_APP_SSE=Y # app client id VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e # 接口加密功能开关(如需关闭 后端也必须对应关闭) -VITE_APP_ENCRYPT=true +VITE_APP_ENCRYPT=Y # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 diff --git a/src/hooks/common/table.ts b/src/hooks/common/table.ts index 297bdfaf..f583029f 100644 --- a/src/hooks/common/table.ts +++ b/src/hooks/common/table.ts @@ -38,7 +38,10 @@ export function useTable(config: NaiveUI.NaiveTabl apiParams, columns: config.columns, transformer: res => { - const { rows: records = [], pageNum: current = 1, pageSize: size = 10, total = 0 } = res.data || {}; + const { rows: records = [], total = 0 } = res.data || {}; + + const current = searchParams.pageNum as number; + const size = (searchParams.pageSize || 0) as number; // Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors. const pageSize = size <= 0 ? 10 : size; @@ -124,8 +127,8 @@ export function useTable(config: NaiveUI.NaiveTabl pagination.page = page; updateSearchParams({ - current: page, - size: pagination.pageSize! + pageNum: page, + pageSize: pagination.pageSize! }); getData(); @@ -135,8 +138,8 @@ export function useTable(config: NaiveUI.NaiveTabl pagination.page = 1; updateSearchParams({ - current: pagination.page, - size: pageSize + pageNum: pagination.page, + pageSize }); getData(); @@ -174,8 +177,8 @@ export function useTable(config: NaiveUI.NaiveTabl }); updateSearchParams({ - current: pageNum, - size: pagination.pageSize! + pageNum, + pageSize: pagination.pageSize! }); await getData(); diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue index f7f05859..3e58cd4e 100644 --- a/src/layouts/base-layout/index.vue +++ b/src/layouts/base-layout/index.vue @@ -5,6 +5,7 @@ import type { LayoutMode } from '@sa/materials'; import { useAppStore } from '@/store/modules/app'; import { useThemeStore } from '@/store/modules/theme'; import { initWebSocket } from '@/utils/websocket'; +import { initSSE } from '@/utils/sse'; import GlobalHeader from '../modules/global-header/index.vue'; import GlobalSider from '../modules/global-sider/index.vue'; import GlobalTab from '../modules/global-tab/index.vue'; @@ -105,6 +106,7 @@ function getSiderCollapsedWidth() { onMounted(() => { const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; initWebSocket(`${protocol + window.location.host + import.meta.env.VITE_APP_BASE_API}/resource/websocket`); + initSSE(`${import.meta.env.VITE_APP_BASE_API}/resource/sse`); }); diff --git a/src/layouts/modules/global-header/components/user-avatar.vue b/src/layouts/modules/global-header/components/user-avatar.vue index a1668afb..5766f345 100644 --- a/src/layouts/modules/global-header/components/user-avatar.vue +++ b/src/layouts/modules/global-header/components/user-avatar.vue @@ -50,7 +50,7 @@ function logout() { positiveText: $t('common.confirm'), negativeText: $t('common.cancel'), onPositiveClick: () => { - authStore.resetStore(); + authStore.logout(); } }); } diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index e4f78d77..664faa03 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -39,3 +39,17 @@ export function fetchLogin(data: Api.Auth.LoginData) { export function fetchGetUserInfo() { return request({ url: '/system/user/getInfo' }); } + +/** Logout */ +export function fetchLogout() { + if (import.meta.env.VITE_APP_SSE === 'Y') { + request({ + url: '/resource/sse/close', + method: 'get' + }); + } + return request({ + url: '/auth/logout', + method: 'post' + }); +} diff --git a/src/service/request/index.ts b/src/service/request/index.ts index 1b942eb0..b338a5a7 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -58,7 +58,7 @@ export const request = createFlatRequest { routeStore.resetStore(); } + async function logout() { + await fetchLogout(); + resetStore(); + } + /** * Login * @@ -147,6 +152,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { loginLoading, resetStore, login, + logout, initUserInfo }; }); diff --git a/src/store/modules/route/index.ts b/src/store/modules/route/index.ts index dca1452d..fa90ffd5 100644 --- a/src/store/modules/route/index.ts +++ b/src/store/modules/route/index.ts @@ -264,7 +264,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => { setIsInitAuthRoute(true); } else { // if fetch user routes failed, reset store - authStore.resetStore(); + authStore.logout(); } } diff --git a/src/typings/api/api.d.ts b/src/typings/api/api.d.ts index 80bdf7b3..ed06f877 100644 --- a/src/typings/api/api.d.ts +++ b/src/typings/api/api.d.ts @@ -21,7 +21,7 @@ declare namespace Api { } /** common search params of table */ - type CommonSearchParams = Pick; + type CommonSearchParams = Pick; /** * enable status diff --git a/src/typings/env.d.ts b/src/typings/env.d.ts index 15550c4c..af918d6e 100644 --- a/src/typings/env.d.ts +++ b/src/typings/env.d.ts @@ -104,10 +104,11 @@ declare namespace Env { /** Used to differentiate storage across different domains */ readonly VITE_STORAGE_PREFIX?: string; readonly VITE_APP_CLIENT_ID?: string; - readonly VITE_APP_ENCRYPT?: string; + readonly VITE_APP_ENCRYPT?: CommonType.YesOrNo; readonly VITE_APP_RSA_PUBLIC_KEY?: string; readonly VITE_APP_RSA_PRIVATE_KEY?: string; - readonly VITE_APP_WEBSOCKET: string; + readonly VITE_APP_WEBSOCKET: CommonType.YesOrNo; + readonly VITE_APP_SSE: CommonType.YesOrNo; } } diff --git a/src/utils/service.ts b/src/utils/service.ts index 651c1179..ed7bbdd9 100644 --- a/src/utils/service.ts +++ b/src/utils/service.ts @@ -36,7 +36,7 @@ export function createServiceConfig(env: Env.ImportMeta) { const config: App.Service.ServiceConfig = { baseURL: httpConfig.baseURL, - ws: VITE_APP_WEBSOCKET === 'true', + ws: VITE_APP_WEBSOCKET === 'Y', proxyPattern: VITE_APP_BASE_API, other: otherConfig }; diff --git a/src/utils/sse.ts b/src/utils/sse.ts new file mode 100644 index 00000000..3c9b803f --- /dev/null +++ b/src/utils/sse.ts @@ -0,0 +1,45 @@ +import { useEventSource } from '@vueuse/core'; +import { watch } from 'vue'; +import useNoticeStore from '@/store/modules/notice'; +import { localStg } from './storage'; + +// 初始化 +export const initSSE = (url: any) => { + if (import.meta.env.VITE_APP_SSE === 'N') { + return; + } + const token = localStg.get('token'); + const sseUrl = `${url}?Authorization=Bearer ${token}&clientid=${import.meta.env.VITE_APP_CLIENT_ID}`; + const { data, error } = useEventSource(sseUrl, [], { + autoReconnect: { + retries: 10, + delay: 3000, + onFailed() { + // eslint-disable-next-line no-console + console.error('Failed to connect after 10 retries'); + } + } + }); + + watch(error, () => { + // eslint-disable-next-line no-console + console.error('SSE connection error:', error.value); + error.value = null; + }); + + watch(data, () => { + if (!data.value) return; + useNoticeStore().addNotice({ + message: data.value, + read: false, + time: new Date().toLocaleString() + }); + window.$notification?.create({ + title: '消息', + content: data.value, + type: 'success', + duration: 3000 + }); + data.value = null; + }); +}; diff --git a/src/utils/websocket.ts b/src/utils/websocket.ts index 09251b7a..3e63a28a 100644 --- a/src/utils/websocket.ts +++ b/src/utils/websocket.ts @@ -32,7 +32,7 @@ let socketError = 0; // 错误次数 // 初始化socket export function initWebSocket(url: any) { - if (import.meta.env.VITE_APP_WEBSOCKET === 'false') { + if (import.meta.env.VITE_APP_WEBSOCKET === 'N') { return null; } socketUrl = url; @@ -50,6 +50,7 @@ export function initWebSocket(url: any) { // socket 连接成功 export function websocketonopen() { websocket.onopen = () => { + // eslint-disable-next-line no-console console.log('连接 websocket 成功'); resetHeart(); }; @@ -58,14 +59,16 @@ export function websocketonopen() { // socket 连接失败 export function websocketonerror() { websocket.onerror = (e: any) => { - console.log('连接 websocket 失败', e); + // eslint-disable-next-line no-console + console.error('连接 websocket 失败', e); }; } // socket 断开链接 export function websocketclose() { websocket.onclose = (e: any) => { - console.log('断开连接', e); + // eslint-disable-next-line no-console + console.warn('断开连接', e); }; } @@ -102,9 +105,11 @@ export function reconnect() { clearInterval(heartTime); initWebSocket(socketUrl); socketError += 1; + // eslint-disable-next-line no-console console.log('socket重连', socketError); } else { - console.log('重试次数已用完'); + // eslint-disable-next-line no-console + console.warn('重试次数已用完'); clearInterval(heartTime); } } diff --git a/src/views/_builtin/iframe-page/[url].vue b/src/views/_builtin/iframe-page/[url].vue index b28c952c..4b10da27 100644 --- a/src/views/_builtin/iframe-page/[url].vue +++ b/src/views/_builtin/iframe-page/[url].vue @@ -8,10 +8,12 @@ interface Props { defineProps(); onMounted(() => { + // eslint-disable-next-line no-console console.log('mounted'); }); onActivated(() => { + // eslint-disable-next-line no-console console.log('activated'); }); diff --git a/src/views/_builtin/login/modules/pwd-login.vue b/src/views/_builtin/login/modules/pwd-login.vue index bad732e0..0db3ce8d 100644 --- a/src/views/_builtin/login/modules/pwd-login.vue +++ b/src/views/_builtin/login/modules/pwd-login.vue @@ -92,7 +92,7 @@ handleFetchCaptchaCode(); - +