websocket 初步封装

pull/56/head
puhui999 2024-07-11 17:19:21 +08:00
parent 39440c1e67
commit 9399a3e223
4 changed files with 157 additions and 98 deletions

View File

@ -88,7 +88,6 @@
}
},
"dependencies": {
"@hyoga/uni-socket.io": "^1.0.1",
"dayjs": "^1.11.7",
"lodash": "^4.17.21",
"luch-request": "^3.0.8",

View File

@ -2,7 +2,7 @@
<s-layout class="chat-wrap" title="客服" navbar="inner">
<!-- 头部连接状态展示 -->
<div class="status">
{{ socketState.isConnect ? "连接客服成功" : '网络已断开,请检查网络后刷新重试' }}
<!-- {{ socketState.isConnect ? "连接客服成功" : '网络已断开,请检查网络后刷新重试' }}-->
</div>
<!-- 覆盖头部导航栏背景颜色 -->
<div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div>
@ -29,7 +29,7 @@
<script setup>
import ChatBox from './components/chatBox.vue';
import { nextTick, reactive, ref, toRefs } from 'vue';
import { nextTick, reactive, ref } from 'vue';
import sheep from '@/sheep';
import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
import MessageInput from '@/pages/chat/components/messageInput.vue';
@ -41,8 +41,6 @@
import { useWebSocket } from '@/sheep/hooks/useWebSocket';
const sys_navBar = sheep.$platform.navbar;
const { socketInit, state } = useWebSocket();
const socketState = toRefs(state).socketState;
const chat = reactive({
msg: '',
@ -143,12 +141,21 @@
}
//======================= end =======================
useWebSocket({
//
onConnected:()=>{
},
//
onClosed:()=>{
},
//
onMessage:(data)=>{
console.log(data);
}
});
onLoad(async () => {
socketInit({}, (res) => {
console.log(res);
//
});
await nextTick()
//
await getMessageList()

View File

@ -1,96 +1,131 @@
import { reactive, ref } from 'vue';
import io from '@hyoga/uni-socket.io';
import { reactive, ref, onBeforeUnmount } from 'vue';
import { baseUrl, websocketPath } from '@/sheep/config';
export function useWebSocket() {
const SocketIo = ref(null)
// chat状态数据
const state = reactive({
socketState: {
isConnect: true, //是否连接成功
isConnecting: false, //重连中不允许新的socket开启。
tip: '',
},
chatConfig: {}, // 配置信息
});
/**
* 连接初始化
* @param {Object} config - 配置信息
* @param {Function} callBack -回调函数,有新消息接入保持底部
*/
const socketInit = (config, callBack) => {
state.chatConfig = config;
if (SocketIo.value && SocketIo.value.connected) return; // 如果socket已经连接返回false
if (state.socketState.isConnecting) return; // 重连中返回false
import { copyValueToTarget } from '@/sheep/util';
// 启动初始化
SocketIo.value = io(baseUrl + websocketPath, {
path:websocketPath,
query:{
token: getAccessToken()
},
reconnection: true, // 默认 true 是否断线重连
reconnectionAttempts: 5, // 默认无限次 断线尝试次数
reconnectionDelay: 1000, // 默认 1000进行下一次重连的间隔。
reconnectionDelayMax: 5000, // 默认 5000 重新连接等待的最长时间 默认 5000
randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
timeout: 20000, // 默认 20s
transports: ['websocket', 'polling'], // websocket | polling,
...config,
});
// 监听连接
SocketIo.value.on('connect', async (res) => {
console.log('socket:connect');
// 消息返回
callBack && callBack(res)
});
// 监听错误 error
SocketIo.value.on('error', (error) => {
console.log('error:', error);
});
// 重连失败 connect_error
SocketIo.value.on('connect_error', (error) => {
console.log('connect_error');
});
// 连接上,但无反应 connect_timeout
SocketIo.value.on('connect_timeout', (error) => {
console.log(error, 'connect_timeout');
});
// 服务进程销毁 disconnect
SocketIo.value.on('disconnect', (error) => {
console.log(error, 'disconnect');
});
// 服务重启重连上reconnect
SocketIo.value.on('reconnect', (error) => {
console.log(error, 'reconnect');
});
// 开始重连reconnect_attempt
SocketIo.value.on('reconnect_attempt', (error) => {
state.socketState.isConnect = false;
state.socketState.isConnecting = true;
console.log(error, 'reconnect_attempt');
});
// 重新连接中reconnecting
SocketIo.value.on('reconnecting', (error) => {
console.log(error, 'reconnecting');
});
// 重新连接错误reconnect_error
SocketIo.value.on('reconnect_error', (error) => {
console.log('reconnect_error');
});
// 重新连接失败reconnect_failed
SocketIo.value.on('reconnect_failed', (error) => {
state.socketState.isConnecting = false;
console.log(error, 'reconnect_failed');
});
};
export function useWebSocket(opt) {
// 获取token
const getAccessToken = () => {
return uni.getStorageSync('token');
};
return {
state,
socketInit
}
const options = reactive({
url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getAccessToken(), // ws 地址
isReconnecting: false, // 正在重新连接
reconnectInterval: 3000, // 重连间隔,单位毫秒
heartBeatInterval: 5000, // 心跳间隔,单位毫秒
pingTimeoutDuration: 1000, // 超过这个时间后端没有返回pong则判定后端断线了。
heartBeatTimer: null, // 心跳计时器
destroy: false, // 是否销毁
onConnected: () => {
}, // 连接成功时触发
onClosed: () => {
}, // 连接关闭时触发
onMessage: (data) => {
}, // 收到消息
});
const Socket = ref(null); // Socket 链接实例
const initEventListeners = () => {
Socket.value.onOpen(() => {
// WebSocket连接已打开
options.onConnected();
startHeartBeat();
});
Socket.value.onMessage((res) => {
try {
const obj = JSON.parse(res.data);
if (obj.type === 'pong') {
// 收到pong消息心跳正常无需处理
resetPingTimeout(); // 重置计时
} else {
// 处理其他消息
options.onMessage(obj);
}
} catch {
console.error(res.data);
}
});
Socket.value.onClose((res) => {
// WebSocket连接已关闭
if (options.destroy) {
options.onClosed();
return;
}
stopHeartBeat();
if (!options.isReconnecting) {
reconnect();
}
});
};
const sendMessage = (message) => {
if (Socket.value) {
Socket.value.send({
data: message,
});
}
};
const startHeartBeat = () => {
options.heartBeatTimer = setInterval(() => {
sendMessage(JSON.stringify({
type: 'ping',
})); // 发送ping消息
options.pingTimeout = setTimeout(() => {
// 未收到pong消息尝试重连...
reconnect();
}, options.pingTimeoutDuration);
}, options.heartBeatInterval);
};
const stopHeartBeat = () => {
if (options.heartBeatTimer) {
clearInterval(options.heartBeatTimer);
}
};
const reconnect = () => {
options.isReconnecting = true;
setTimeout(() => {
onReconnect();
initSocket();
options.isReconnecting = false;
}, options.reconnectInterval);
};
const resetPingTimeout = () => {
clearTimeout(options.pingTimeout); // 重置计时
};
const close = () => {
options.destroy = true;
stopHeartBeat();
if (Socket.value) {
Socket.value.close();
Socket.value = null;
}
};
/**
* 重连时触发
*/
const onReconnect = () => {
console.log('尝试重连...');
};
const initSocket = () => {
copyValueToTarget(options, opt);
Socket.value = uni.connectSocket({
url: options.url,
complete: () => {
},
});
initEventListeners();
};
initSocket();
onBeforeUnmount(()=>{
close()
})
}

View File

@ -113,3 +113,21 @@ export function resetPagination(pagination) {
pagination.total = 0;
pagination.pageNo = 1;
}
/**
* 将值复制到目标对象且以目标对象属性为准target: {a:1} source:{a:2,b:3} 结果为{a:2}
* @param target 目标对象
* @param source 源对象
*/
export const copyValueToTarget = (target, source) => {
const newObj = Object.assign({}, target, source)
// 删除多余属性
Object.keys(newObj).forEach((key) => {
// 如果不是target中的属性则删除
if (Object.keys(target).indexOf(key) === -1) {
delete newObj[key]
}
})
// 更新目标对象值
Object.assign(target, newObj)
}