Browse Source

websocket增加心跳机制

develop
fuguobin 1 year ago
parent
commit
f3f68d45dc
  1. 1
      .env.development
  2. 3
      .env.production
  3. 6
      README.md
  4. 2
      src/types/auto-imports.d.ts
  5. 82
      src/utils/socket.ts
  6. 14
      src/views/monitoring/screen/components/main.vue
  7. 56
      vite.config.ts

1
.env.development

@ -2,4 +2,5 @@
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
VITE_APP_TITLE = 'vue-vite-project-admin' VITE_APP_TITLE = 'vue-vite-project-admin'
VITE_APP_PORT = 8089 VITE_APP_PORT = 8089
VITE_APP_WS_API = 'ws://board.heatiot.cn:8001/prod-api' ## websocket地址
VITE_APP_BASE_API = '/dev-api' ## '/dev-api'线上接口 '/mock'本地模拟数据 VITE_APP_BASE_API = '/dev-api' ## '/dev-api'线上接口 '/mock'本地模拟数据

3
.env.production

@ -2,4 +2,5 @@
VITE_APP_ENV = 'production' VITE_APP_ENV = 'production'
VITE_APP_TITLE = 'vue-vite-project-admin' VITE_APP_TITLE = 'vue-vite-project-admin'
VITE_APP_PORT = 8089 VITE_APP_PORT = 8089
VITE_APP_BASE_API = 'http://board.heatiot.cn:8001/prod-api/' VITE_APP_WS_API = 'ws://board.heatiot.cn:8001/prod-api' ## websocket地址
VITE_APP_BASE_API = 'http://board.heatiot.cn:8001/prod-api/' ## 线上接口

6
README.md

@ -6,11 +6,11 @@
``` bash ``` bash
# install dependencies # install dependencies
npm install pnpm install
# serve with hot reload at localhost:8080 # serve with hot reload at localhost:8080
npm run dev pnpm run dev
# build for production with minification # build for production with minification
npm run build pnpm run build
``` ```

2
src/types/auto-imports.d.ts

@ -2,7 +2,6 @@
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope']
const ElForm: typeof import('element-plus/es')['ElForm']
const ElMessage: typeof import('element-plus/es')['ElMessage'] const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const ElNotification: typeof import('element-plus/es')['ElNotification'] const ElNotification: typeof import('element-plus/es')['ElNotification']
@ -273,7 +272,6 @@ import { UnwrapRef } from 'vue'
declare module 'vue' { declare module 'vue' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']> readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']>
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']> readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
readonly ElNotification: UnwrapRef<typeof import('element-plus/es')['ElNotification']> readonly ElNotification: UnwrapRef<typeof import('element-plus/es')['ElNotification']>

82
src/utils/socket.ts

@ -2,29 +2,38 @@
* Author: Fu Guobin * Author: Fu Guobin
* Date: 2022/08/02 * Date: 2022/08/02
* Last Modified by: Fu Guobin * Last Modified by: Fu Guobin
* Last Modified time: 2023/08/28 * Last Modified time: 2023/09/15
* Copyright:Daniel(Fu Guobin) * Copyright:Daniel(Fu Guobin)
* Description:websocket函数封装 * Description:websocket方法封装
*/ */
import { reactive, toRefs } from 'vue';
import { tableStore } from '@/store/modules/table';
import mitt from '@/plugins/bus'; import mitt from '@/plugins/bus';
// const tableStoreCounter = tableStore();
class WebSocketService { class WebSocketService {
url: string;
websocket: WebSocket | null; websocket: WebSocket | null;
isInitialized: boolean; isInitialized: boolean;
isConnected: boolean; isConnected: boolean;
heartbeatInterval: number;
reconnectInterval: number;
maxReconnectAttempts: number;
reconnectAttempts: number;
data: any; data: any;
constructor() { constructor() {
this.url = '';
this.websocket = null; this.websocket = null;
this.isInitialized = false; this.isInitialized = false;
this.isConnected = false; this.isConnected = false; //握手
this.heartbeatInterval = 30000; // 默认心跳30秒
this.reconnectInterval = 5000; // 默认重连5秒
this.maxReconnectAttempts = 5; // 默认尝试重连5次
this.reconnectAttempts = 0;
this.data = null; this.data = null;
} }
initialize(url: string): void { initialize(url: string): void {
//初始化
this.url = url;
this.websocket = new WebSocket(url); this.websocket = new WebSocket(url);
this.websocket.onopen = this.onOpen.bind(this); this.websocket.onopen = this.onOpen.bind(this);
this.websocket.onclose = this.onClose.bind(this); this.websocket.onclose = this.onClose.bind(this);
@ -34,24 +43,24 @@ class WebSocketService {
} }
onOpen(): void { onOpen(): void {
this.isConnected = true; this.isConnected = true; // 进行握手操作,如果需要的话
// 进行握手操作,如果需要的话 this.reconnectAttempts = 0; // 重置重连次数
this.startHeartbeat();
} }
onClose(): void { onSend(data: any): void {
this.isConnected = false; //发送消息处理
// 关闭WebSocket连接的处理逻辑 console.log('websocketSend:', JSON.stringify(data));
if (this.isConnected) {
this.websocket?.send(JSON.stringify(data));
} }
onError(error: Event): void {
console.error('WebSocket error:', error);
// 错误处理的逻辑
} }
onMessage(event: MessageEvent): void { onMessage(event: MessageEvent): void {
//获取消息处理
if (event.data != '连接成功') { if (event.data != '连接成功') {
const response = JSON.parse(event.data); const response = JSON.parse(event.data);
console.log("response:",response); console.log('response:', response);
this.data = response; this.data = response;
// 处理返回的数据 // 处理返回的数据
if (response.code === 'datareal') { if (response.code === 'datareal') {
@ -61,19 +70,40 @@ class WebSocketService {
console.log('waring'); console.log('waring');
mitt.emit('waringMessage', response); mitt.emit('waringMessage', response);
} }
// const oldData = tableStoreCounter.tableDataStore;
// const index = oldData.findIndex((obj) => obj.id === response.id);
// if (index !== -1) {
// oldData.splice(index, 1, response);
// }
// tableStoreCounter.tableDataAction(oldData);
} }
} }
send(data: any): void { onClose(): void {
console.log('websocketSend:', JSON.stringify(data)); // 关闭WebSocket连接的处理逻辑
if (this.isConnected) { this.isConnected = false;
this.websocket?.send(JSON.stringify(data)); this.stopHeartbeat();
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
this.initialize(this.url);
}, this.reconnectInterval);
}
}
onError(error: Event): void {
console.error('WebSocket error:', error);
// 错误处理的逻辑
}
startHeartbeat() {
// 发送心跳
this.heartbeatInterval = setInterval(() => {
if (this.websocket?.readyState === WebSocket.OPEN) {
this.websocket.send('ping'); //心跳消息
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
// 停止心跳
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = 0;
} }
} }

14
src/views/monitoring/screen/components/main.vue

@ -16,8 +16,8 @@
<vxe-grid ref="tableRef" class="tableGrid" align="center" auto-resize keep-source :height="sidebarHeight - 4" <vxe-grid ref="tableRef" class="tableGrid" align="center" auto-resize keep-source :height="sidebarHeight - 4"
header-row-class-name="headerRowClass" header-cell-class-name="headerCellClass" row-class-name="tableRowClass" header-row-class-name="headerRowClass" header-cell-class-name="headerCellClass" row-class-name="tableRowClass"
cell-class-name="tableCellClass" :sort-config="{ multiple: true, trigger: 'cell' }" :stripe="true" :border="false" cell-class-name="tableCellClass" :sort-config="{ multiple: true, trigger: 'cell' }" :stripe="true" :border="false"
:column-config="{ resizable: true, useKey: true }" :row-config="{ useKey: true }" :span-method="mergeRowMethod" :columns="tableColumn" :column-config="{ resizable: true, useKey: true }" :row-config="{ useKey: true }" :span-method="mergeRowMethod"
:data="tableData" @cell-dblclick="cellDBLClickEvent"> :columns="tableColumn" :data="tableData" @cell-dblclick="cellDBLClickEvent">
<template #deviceuuid_default="{ row }"> <template #deviceuuid_default="{ row }">
<div class="title"> <div class="title">
<svg-icon icon-class="warning_lights" style="fill:currentColor;width: 15px;height: 15px;color: green;" <svg-icon icon-class="warning_lights" style="fill:currentColor;width: 15px;height: 15px;color: green;"
@ -119,12 +119,14 @@ const formRules = ref<VxeFormPropTypes.Rules>({
const userStorageInfo = sessionStorage.getItem('userInfo') const userStorageInfo = sessionStorage.getItem('userInfo')
const userInfo = JSON.parse(userStorageInfo === null ? '' : userStorageInfo); const userInfo = JSON.parse(userStorageInfo === null ? '' : userStorageInfo);
const apiUrl = import.meta.env.VITE_APP_WS_API
const wsUrl = `${apiUrl}/websocket/${userInfo.userName}`; //websocket
// const loginIp = userInfo.loginIp.split('.').join(''); // const loginIp = userInfo.loginIp.split('.').join('');
// const baseApi = "http://172.1.2.106:9000"//websocket // const baseApi = "http://172.1.2.106:9000"//websocket
const baseApi = "http://board.heatiot.cn:8001/prod-api"//websocket // const baseApi = "http://board.heatiot.cn:8001/prod-api"//websocket
// const baseApi = import.meta.env.VITE_APP_BASE_API // const baseApi = import.meta.env.VITE_APP_BASE_API
const apiUrl = baseApi.replace(/https?:/, ''); // const apiUrl = baseApi.replace(/https?:/, '');
const wsUrl = `ws:${apiUrl}/websocket/${userInfo.userName}`; //websocket // const wsUrl = `ws:${apiUrl}/websocket/${userInfo.userName}`; //websocket
// const wsData = ref(socket.data); // const wsData = ref(socket.data);
const emit = defineEmits(['tableHeaderData']); const emit = defineEmits(['tableHeaderData']);
@ -377,7 +379,7 @@ const submitEvent: VxeFormEvents.Submit = () => {
}, },
}; };
console.log(submitData); console.log(submitData);
socket.send(submitData); socket.onSend(submitData);
formLoading.value = false; formLoading.value = false;
editModal.value = false; editModal.value = false;

56
vite.config.ts

@ -2,7 +2,7 @@
* Author: Fu Guobin * Author: Fu Guobin
* Date: 2022/12/28 * Date: 2022/12/28
* Last Modified by: Fu Guobin * Last Modified by: Fu Guobin
* Last Modified time: 2023/08/28 * Last Modified time: 2023/09/15
* Copyright:Daniel(Fu Guobin) * Copyright:Daniel(Fu Guobin)
* Description:vite配置 * Description:vite配置
*/ */
@ -11,8 +11,8 @@ import { UserConfig, ConfigEnv, loadEnv, defineConfig } from 'vite';
import AutoImport from 'unplugin-auto-import/vite'; import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import Icons from 'unplugin-icons/vite'; import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver'; import IconsResolver from 'unplugin-icons/resolver';
import path from 'path'; import path from 'path';
@ -28,29 +28,28 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
} }
}, },
//静态资源处理 //静态资源处理
assetsInclude: "", assetsInclude: '',
//控制台输出的级别 info 、warn、error、silent //控制台输出的级别 info 、warn、error、silent
logLevel: "info", logLevel: 'info',
// 设为false 可以避免 vite 清屏而错过在终端中打印某些关键信息 // 设为false 可以避免 vite 清屏而错过在终端中打印某些关键信息
clearScreen: true, clearScreen: true,
//本地运行配置,以及反向代理配置 //本地运行配置,以及反向代理配置
server: { server: {
host: "localhost", host: 'localhost',
https: false,//是否启用 http 2 https: false, //是否启用 http 2
cors: true,//为开发服务器配置 CORS , 默认启用并允许任何源 cors: true, //为开发服务器配置 CORS , 默认启用并允许任何源
open: true,//服务启动时自动在浏览器中打开应用 open: true, //服务启动时自动在浏览器中打开应用
port: Number(env.VITE_APP_PORT), port: Number(env.VITE_APP_PORT),
strictPort: false, //设为true时端口被占用则直接退出,不会尝试下一个可用端口 strictPort: false, //设为true时端口被占用则直接退出,不会尝试下一个可用端口
force: false,//是否强制依赖预构建 force: false, //是否强制依赖预构建
hmr: true,//配置HMR hmr: true, //配置HMR
proxy: { proxy: {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
// target: 'http://172.1.2.106:9000/',//本地接口地址 // target: 'http://172.1.2.106:9000/',//本地接口地址
// target: 'http://172.1.2.48:9000/',//本地接口地址 // target: 'http://172.1.2.48:9000/',//本地接口地址
target: 'http://board.heatiot.cn:8001/prod-api/',//线上接口地址 target: 'http://board.heatiot.cn:8001/prod-api/', //线上接口地址
changeOrigin: true, changeOrigin: true,
rewrite: path => rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
} }
} }
}, },
@ -95,10 +94,10 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
createSvgIconsPlugin({ createSvgIconsPlugin({
// 指定需要缓存的图标文件夹 // 指定需要缓存的图标文件夹
iconDirs: [path.resolve(pathSrc, "assets/icons")], iconDirs: [path.resolve(pathSrc, 'assets/icons')],
// 指定symbolId格式 // 指定symbolId格式
symbolId: "icon-[dir]-[name]", symbolId: 'icon-[dir]-[name]'
}), })
], ],
css: { css: {
// CSS 预处理器 // CSS 预处理器
@ -112,14 +111,18 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
} }
} }
}, },
// esbuild: {
// //打包时去除console和debugger代码
// drop: ['console', 'debugger']
// },
//打包配置 //打包配置
build: { build: {
//浏览器兼容性 "esnext"|"modules" //浏览器兼容性 "esnext"|"modules"
target: "modules", target: 'modules',
//指定输出路径 //指定输出路径
outDir: "dist", outDir: 'dist',
//生成静态资源的存放路径 //生成静态资源的存放路径
assetsDir: "assets", assetsDir: 'assets',
//小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项 //小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项
assetsInlineLimit: 4096, assetsInlineLimit: 4096,
//启用/禁用 CSS 代码拆分 //启用/禁用 CSS 代码拆分
@ -127,19 +130,22 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
//构建后是否生成 source map 文件 //构建后是否生成 source map 文件
sourcemap: false, sourcemap: false,
//自定义底层的 Rollup 打包配置 //自定义底层的 Rollup 打包配置
rollupOptions: { rollupOptions: {},
},
//@rollup/plugin-commonjs 插件的选项 //@rollup/plugin-commonjs 插件的选项
commonjsOptions: { commonjsOptions: {},
},
//当设置为 true,构建后将会生成 manifest.json 文件 //当设置为 true,构建后将会生成 manifest.json 文件
manifest: false, manifest: false,
// 设置为 false 可以禁用最小化混淆, // 设置为 false 可以禁用最小化混淆,
// 或是用来指定使用哪种混淆器 // 或是用来指定使用哪种混淆器
// boolean | 'terser' | 'esbuild' // boolean | 'terser' | 'esbuild'
minify: "terser", //terser 构建后文件体积更小 minify: 'terser', //terser 构建后文件体积更小
//传递给 Terser 的更多 minify 选项。 //传递给 Terser 的更多 minify 选项。
terserOptions: { terserOptions: {
compress: {
//生产环境时移除console
drop_console: true,
drop_debugger: true
}
}, },
//设置为 false 来禁用将构建后的文件写入磁盘 //设置为 false 来禁用将构建后的文件写入磁盘
write: true, write: true,

Loading…
Cancel
Save