feat: 新增webSocket demo
This commit is contained in:
parent
bbf6558569
commit
bf74cdc0c1
@ -385,6 +385,12 @@ export const asyncRoutes:RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/mydemo/StoreDemo.vue'),
|
component: () => import('@/views/mydemo/StoreDemo.vue'),
|
||||||
name: 'StoreDemo',
|
name: 'StoreDemo',
|
||||||
meta: { title: 'StoreDemo', icon: 'lock' }
|
meta: { title: 'StoreDemo', icon: 'lock' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'webSocket-demo',
|
||||||
|
component: () => import('@/views/mydemo/WebSocketDemo.vue'),
|
||||||
|
name: 'WebSocketDemo',
|
||||||
|
meta: { title: 'WebSocketDemo', icon: 'lock' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -7,6 +7,7 @@ import permissionStore from './permission';
|
|||||||
|
|
||||||
export interface IUserState {
|
export interface IUserState {
|
||||||
token: string;
|
token: string;
|
||||||
|
userId: string,
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
introduction: string;
|
introduction: string;
|
||||||
@ -17,6 +18,7 @@ export default defineStore({
|
|||||||
id: 'user',
|
id: 'user',
|
||||||
state: ():IUserState => ({
|
state: ():IUserState => ({
|
||||||
token: getToken(),
|
token: getToken(),
|
||||||
|
userId: '',
|
||||||
name: '',
|
name: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
introduction: '',
|
introduction: '',
|
||||||
|
19
src/store/modules/webSocket.ts
Normal file
19
src/store/modules/webSocket.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
interface socketState {
|
||||||
|
socketId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineStore('useWebSocket', {
|
||||||
|
state: (): socketState => ({
|
||||||
|
socketId: '' // 会话id
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setSocketID(id: string) {
|
||||||
|
this.socketId = id;
|
||||||
|
},
|
||||||
|
getSocketID() {
|
||||||
|
return this.socketId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
241
src/utils/webSocket.js
Normal file
241
src/utils/webSocket.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* @Description: Socket构造函数
|
||||||
|
* @Author: Cherry
|
||||||
|
* @Date: 2022-07-21 10:29:53
|
||||||
|
* @LastEditors: Cherry 2858937488@qq.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Socket {
|
||||||
|
constructor(options) {
|
||||||
|
// 连接地址
|
||||||
|
this.url = options.url || '';
|
||||||
|
// 连接对象
|
||||||
|
this.socket = null;
|
||||||
|
// 是否允许重新连接
|
||||||
|
this.lockReconnect = false;
|
||||||
|
// 心跳间隔
|
||||||
|
this.timeout = options.timeout || 1000 * 20;
|
||||||
|
// 服务器响应间隔
|
||||||
|
this.serverTimeout = options.serverTimeout || 2000;
|
||||||
|
// 重连间隔
|
||||||
|
this.reTimeout = options.reTimeout || 1000 * 10;
|
||||||
|
// 心跳定时器
|
||||||
|
this.timeoutTimer = null;
|
||||||
|
// 服务器响应定时器
|
||||||
|
this.serverTimeoutTimer = null;
|
||||||
|
// 重连定时器
|
||||||
|
this.reTimeoutTimer = null;
|
||||||
|
// 重连计数,3次重连机制
|
||||||
|
this.reCount = 0;
|
||||||
|
// 制空打印方法
|
||||||
|
const initConsole = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
log: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
error: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
warn: () => {}
|
||||||
|
};
|
||||||
|
// 开启控制台打印
|
||||||
|
this.logs = options.openConsole ? console : initConsole;
|
||||||
|
// 消息传递
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
this.onMsgEventCb = options.onMsgEventCb || (() => {});
|
||||||
|
// 异常断开连接回调
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
this.onDisconnectCb = options.onDisconnectCb || (() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立连接
|
||||||
|
create() {
|
||||||
|
if (!this.url) {
|
||||||
|
this.logs.error('请输入长连接地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 创建WebSocket对象
|
||||||
|
this.socket = new WebSocket(this.url);
|
||||||
|
// 创建socket对象的事件监听
|
||||||
|
this.initEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立事件监听
|
||||||
|
initEvent() {
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.error('长连接对象不存在,请先调用create方法创建长连接对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 监听连接事件
|
||||||
|
this.socket.addEventListener('open', this.onOpenEvent.bind(this));
|
||||||
|
// 监听错误事件
|
||||||
|
this.socket.addEventListener('error', this.onErrorEvent.bind(this));
|
||||||
|
// 监听服务端发送消息事件
|
||||||
|
this.socket.addEventListener('message', this.onMessageEvent.bind(this));
|
||||||
|
// 监听关闭连接事件
|
||||||
|
this.socket.addEventListener('close', this.onCloseEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除事件监听
|
||||||
|
removeEvent() {
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.error('长连接对象不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 移除监听连接事件
|
||||||
|
this.socket.removeEventListener('open', this.onOpenEvent.bind(this));
|
||||||
|
// 移除监听错误事件
|
||||||
|
this.socket.removeEventListener('error', this.onErrorEvent.bind(this));
|
||||||
|
// 移除监听服务端发送消息事件
|
||||||
|
this.socket.removeEventListener('message', this.onMessageEvent.bind(this));
|
||||||
|
// 移除监听关闭连接事件
|
||||||
|
this.socket.removeEventListener('close', this.onCloseEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接成功回调
|
||||||
|
onOpenEvent(e) {
|
||||||
|
this.logs.log('onOpenEvent e:', e);
|
||||||
|
const t = e.currentTarget || {};
|
||||||
|
if (t.readyState === 1) {
|
||||||
|
this.closeReConnect();
|
||||||
|
this.heartCheckStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接失败回调
|
||||||
|
onErrorEvent(err) {
|
||||||
|
this.logs.error('onErrorEvent err:', err);
|
||||||
|
this.reConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接受服务端消息回调
|
||||||
|
onMessageEvent(e) {
|
||||||
|
this.logs.log('onMessageEvent e:', e);
|
||||||
|
this.heartCheckReset();
|
||||||
|
const { data } = e;
|
||||||
|
try {
|
||||||
|
const dataObj = data ? JSON.parse(data) : {};
|
||||||
|
if (dataObj.msgType === 2 || dataObj.msgType === 3) {
|
||||||
|
this.onMsgEventCb(dataObj);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.logs.log('data解析失败 err:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭连接回调
|
||||||
|
onCloseEvent(e) {
|
||||||
|
this.logs.log('onCloseEvent e:', e);
|
||||||
|
this.logs.log(`websocket 断开: ${e.code} ${e.reason} ${e.wasClean}`);
|
||||||
|
this.reConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立连接心跳
|
||||||
|
heartCheckStart() {
|
||||||
|
this.timeoutTimer = setTimeout(() => {
|
||||||
|
this.send(
|
||||||
|
JSON.stringify({
|
||||||
|
msgType: 1,
|
||||||
|
heart: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.logs.log('send heart');
|
||||||
|
|
||||||
|
// 3、发送数据 2s后没有接收到返回的数据进行关闭websocket重连
|
||||||
|
this.serverTimeoutTimer = setTimeout(() => {
|
||||||
|
this.logs.log('后台挂掉,没有心跳了....');
|
||||||
|
clearTimeout(this.timeoutTimer);
|
||||||
|
clearTimeout(this.serverTimeoutTimer);
|
||||||
|
this.reConnect();
|
||||||
|
}, this.serverTimeout);
|
||||||
|
}, this.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置连接心跳
|
||||||
|
heartCheckReset() {
|
||||||
|
clearTimeout(this.timeoutTimer);
|
||||||
|
clearTimeout(this.serverTimeoutTimer);
|
||||||
|
this.heartCheckStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重连
|
||||||
|
reConnect() {
|
||||||
|
this.timeoutTimer && clearTimeout(this.timeoutTimer);
|
||||||
|
this.serverTimeoutTimer && clearTimeout(this.serverTimeoutTimer);
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.log('无长连接对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logs.log('reConnect reCount:', this.reCount, ' lockReconnect:', this.lockReconnect);
|
||||||
|
if (this.lockReconnect) return;
|
||||||
|
if (this.reCount > 2) {
|
||||||
|
this.closeReConnect();
|
||||||
|
this.close();
|
||||||
|
this.removeEvent();
|
||||||
|
this.socket = null;
|
||||||
|
this.onDisconnectCb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lockReconnect = true;
|
||||||
|
// 没连接上会一直重连,设置延迟避免请求过多
|
||||||
|
this.reTimeoutTimer && clearTimeout(this.reTimeoutTimer);
|
||||||
|
this.reTimeoutTimer = setTimeout(() => {
|
||||||
|
this.close();
|
||||||
|
this.removeEvent();
|
||||||
|
this.socket = null;
|
||||||
|
this.create();
|
||||||
|
this.reCount += 1;
|
||||||
|
this.lockReconnect = false;
|
||||||
|
}, this.reTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消重连
|
||||||
|
closeReConnect() {
|
||||||
|
this.reTimeoutTimer && clearTimeout(this.reTimeoutTimer);
|
||||||
|
this.reCount = 0;
|
||||||
|
this.lockReconnect = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息方法
|
||||||
|
send(data) {
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.error('长连接对象不存在,请先调用create方法创建长连接对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.socket.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息内容
|
||||||
|
sendMsgCustom(msg) {
|
||||||
|
this.send(JSON.stringify({
|
||||||
|
msgType: 2,
|
||||||
|
id: `${new Date().getTime()}`, // demo简单定义,id规则建议用uuid
|
||||||
|
content: msg,
|
||||||
|
self: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭连接方法
|
||||||
|
close() {
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.error('长连接对象不存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销方法
|
||||||
|
destroy() {
|
||||||
|
if (!this.socket) {
|
||||||
|
this.logs.log('无长连接对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.timeoutTimer && clearTimeout(this.timeoutTimer);
|
||||||
|
this.serverTimeoutTimer && clearTimeout(this.serverTimeoutTimer);
|
||||||
|
this.closeReConnect();
|
||||||
|
this.close();
|
||||||
|
this.removeEvent();
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Socket;
|
219
src/views/mydemo/WebSocketDemo.vue
Normal file
219
src/views/mydemo/WebSocketDemo.vue
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: Cherry 2858937488@qq.com
|
||||||
|
* @Date: 2023-03-14 10:40:55
|
||||||
|
* @LastEditors: Cherry 2858937488@qq.com
|
||||||
|
* @LastEditTime: 2023-03-16 13:47:40
|
||||||
|
* @FilePath: \vue3-admin\vue3-element-admin\src\views\mydemo\WebSocketDemo.vue
|
||||||
|
* @Description: websocket
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="web-socket-demo">
|
||||||
|
<h3>node搭建WebSocket服务器</h3>
|
||||||
|
<p>一、新建文件夹demo,然后用npm init命令新建一个项目ws-demo</p>
|
||||||
|
<p>mkdir demo</p>
|
||||||
|
<p>cd demo</p>
|
||||||
|
<p>npm init</p>
|
||||||
|
|
||||||
|
<h3>二、安装依赖ws</h3>
|
||||||
|
<p>npm install ws</p>
|
||||||
|
|
||||||
|
<h3>三、修改入口文件index.js</h3>
|
||||||
|
<p>const WebSocket = require('ws') </p>
|
||||||
|
<p>const ws = new WebSocket.Server({ port: 5001 })</p>
|
||||||
|
<p>ws.on('connection', ws => {</p>
|
||||||
|
<p>console.log('server connection')</p>
|
||||||
|
|
||||||
|
<p>ws.on('message', msg => {</p>
|
||||||
|
|
||||||
|
<p>console.log('服务端接收的消息:', msg)</p>
|
||||||
|
|
||||||
|
<p>const data = JSON.parse(msg)</p>
|
||||||
|
<p>console.log('服务端接收的消息解析:', data)</p>
|
||||||
|
|
||||||
|
<p>if(data.msgType === 1){</p>
|
||||||
|
<p>ws.send(JSON.stringify({</p>
|
||||||
|
<p> msgType: 1,</p>
|
||||||
|
<p> heart: true</p>
|
||||||
|
<p>}))</p>
|
||||||
|
<p>}else{</p>
|
||||||
|
<p>ws.send(JSON.stringify({</p>
|
||||||
|
<p> msgType: 3,</p>
|
||||||
|
<p> content: '接收成功',</p>
|
||||||
|
<p> data})</p>
|
||||||
|
<p>)</p>
|
||||||
|
<p>ws.send(JSON.stringify({</p>
|
||||||
|
<p> msgType: 2,</p>
|
||||||
|
<p> id: `${new Date().getTime()}`,//demo简单定义,id规则建议用uuid</p>
|
||||||
|
<p> content: '模拟回复',</p>
|
||||||
|
<p> self: false</p>
|
||||||
|
<p>}))</p>
|
||||||
|
<p>}</p>
|
||||||
|
<p>})</p>
|
||||||
|
|
||||||
|
<p>ws.send(JSON.stringify({</p>
|
||||||
|
<p> msgType: 0,</p>
|
||||||
|
<p> content: '连接已建立'</p>
|
||||||
|
<p>}))</p>
|
||||||
|
|
||||||
|
<p>})</p>
|
||||||
|
|
||||||
|
<h3>四、运行index.js</h3>
|
||||||
|
<p>node index.js</p>
|
||||||
|
|
||||||
|
<h3>五、本地交互</h3>
|
||||||
|
<div class="item">请输入运行地址进行连接:</div>
|
||||||
|
<div class="flex">
|
||||||
|
<el-input v-model="linkUrl" placeholder="连接地址" />
|
||||||
|
<el-button type="primary" @click="startConnection" v-if="!isConnected">连接</el-button>
|
||||||
|
<el-button type="primary" v-else @click="disconnected">断开连接</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="item">信息交互:</div>
|
||||||
|
<div class="flex">
|
||||||
|
<el-input v-model="msg" placeholder="交互信息" placeholder-class="input-placeholder" />
|
||||||
|
<el-button type="primary" @click="sendMsg" :disabled="disabled">发送</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="item">信息列表:</div>
|
||||||
|
<div class="list">
|
||||||
|
<div v-for="item in msgList" :key="item.id" :class="item.self ? 'self' : 'other'">{{ item.content }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Socket from '@/utils/websocket.js';
|
||||||
|
import webSocketStore from '@/store/modules/webSocket';
|
||||||
|
import userStore from '@/store/modules/user';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
interface MsgObj {
|
||||||
|
id: string | number,
|
||||||
|
content: string,
|
||||||
|
self: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkUrl = ref<string>('ws://127.0.0.1:5001');// 连接地址
|
||||||
|
const socket = ref<any>(null); // 长连接对象
|
||||||
|
const isConnected = ref<boolean>(false);// 是否连接
|
||||||
|
const msg = ref<string>('');
|
||||||
|
const disabled = ref<boolean>(false);// 是否禁止发送消息
|
||||||
|
const msgList = ref<MsgObj[]>([]);
|
||||||
|
|
||||||
|
const startConnection = () => {
|
||||||
|
if (!linkUrl.value) {
|
||||||
|
ElMessage({
|
||||||
|
message: '连接地址不能为空!',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isConnected.value = true;
|
||||||
|
createSocket();
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnected = () => {
|
||||||
|
isConnected.value = false;
|
||||||
|
destroySocket();
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendMsg = () => {
|
||||||
|
if (!socket.value) {
|
||||||
|
ElMessage({
|
||||||
|
message: '请先建立连接!',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!msg.value) {
|
||||||
|
ElMessage({
|
||||||
|
message: '发送的消息内容不能为空!',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disabled.value = true;
|
||||||
|
socket.value.sendMsgCustom(msg.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSocket = () => {
|
||||||
|
console.log('创建长连接', !socket.value);
|
||||||
|
const usewebSocketStore = webSocketStore();
|
||||||
|
const useUserStore = userStore();
|
||||||
|
const id = usewebSocketStore.getSocketID() || `${useUserStore.userId}-${new Date().getTime() / 1000}`;
|
||||||
|
if (!usewebSocketStore.getSocketID()) {
|
||||||
|
usewebSocketStore.setSocketID(id);
|
||||||
|
}
|
||||||
|
socket.value = new Socket({
|
||||||
|
url: linkUrl.value + '/' + id,
|
||||||
|
openConsole: process.env.NODE_ENV !== 'production',
|
||||||
|
onMsgEventCb: getMsg,
|
||||||
|
onDisconnectCb: disconnectCallback
|
||||||
|
});
|
||||||
|
socket.value.create();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 接受消息
|
||||||
|
const getMsg = (dataObj) => {
|
||||||
|
console.log('getMsg:', dataObj);
|
||||||
|
if (dataObj.msgType === 3) {
|
||||||
|
ElMessage({
|
||||||
|
message: '发送成功!',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
msgList.value.push(dataObj.data);
|
||||||
|
msg.value = '';
|
||||||
|
disabled.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgList.value.push(dataObj);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 断开连接提示
|
||||||
|
const disconnectCallback = () => {
|
||||||
|
socket.value = null;
|
||||||
|
ElMessage({
|
||||||
|
message: '连接已断开...',
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
disconnected();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 销毁长连接
|
||||||
|
const destroySocket = () => {
|
||||||
|
console.log('销毁长连接', !!socket.value);
|
||||||
|
if (!socket.value) return;
|
||||||
|
socket.value.destroy();
|
||||||
|
socket.value = null;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.web-socket-demo {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
width: 500px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
width: 500px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #efe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other {
|
||||||
|
margin: 10px 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user