websocket 初步封装
							parent
							
								
									39440c1e67
								
							
						
					
					
						commit
						9399a3e223
					
				|  | @ -88,7 +88,6 @@ | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@hyoga/uni-socket.io": "^1.0.1", |  | ||||||
|     "dayjs": "^1.11.7", |     "dayjs": "^1.11.7", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|     "luch-request": "^3.0.8", |     "luch-request": "^3.0.8", | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
|   <s-layout class="chat-wrap" title="客服" navbar="inner"> |   <s-layout class="chat-wrap" title="客服" navbar="inner"> | ||||||
|     <!-- 头部连接状态展示  --> |     <!-- 头部连接状态展示  --> | ||||||
|     <div class="status"> |     <div class="status"> | ||||||
|       {{ socketState.isConnect ? "连接客服成功" : '网络已断开,请检查网络后刷新重试' }} | <!--      {{ socketState.isConnect ? "连接客服成功" : '网络已断开,请检查网络后刷新重试' }}--> | ||||||
|     </div> |     </div> | ||||||
|     <!--  覆盖头部导航栏背景颜色  --> |     <!--  覆盖头部导航栏背景颜色  --> | ||||||
|     <div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div> |     <div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div> | ||||||
|  | @ -29,7 +29,7 @@ | ||||||
| 
 | 
 | ||||||
| <script setup> | <script setup> | ||||||
|   import ChatBox from './components/chatBox.vue'; |   import ChatBox from './components/chatBox.vue'; | ||||||
|   import { nextTick, reactive, ref, toRefs } from 'vue'; |   import { nextTick, reactive, ref } from 'vue'; | ||||||
|   import sheep from '@/sheep'; |   import sheep from '@/sheep'; | ||||||
|   import ToolsPopup from '@/pages/chat/components/toolsPopup.vue'; |   import ToolsPopup from '@/pages/chat/components/toolsPopup.vue'; | ||||||
|   import MessageInput from '@/pages/chat/components/messageInput.vue'; |   import MessageInput from '@/pages/chat/components/messageInput.vue'; | ||||||
|  | @ -41,8 +41,6 @@ | ||||||
|   import { useWebSocket } from '@/sheep/hooks/useWebSocket'; |   import { useWebSocket } from '@/sheep/hooks/useWebSocket'; | ||||||
| 
 | 
 | ||||||
|   const sys_navBar = sheep.$platform.navbar; |   const sys_navBar = sheep.$platform.navbar; | ||||||
|   const { socketInit, state } = useWebSocket(); |  | ||||||
|   const socketState = toRefs(state).socketState; |  | ||||||
| 
 | 
 | ||||||
|   const chat = reactive({ |   const chat = reactive({ | ||||||
|     msg: '', |     msg: '', | ||||||
|  | @ -143,12 +141,21 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   //======================= 聊天工具相关 end ======================= |   //======================= 聊天工具相关 end ======================= | ||||||
|  |   useWebSocket({ | ||||||
|  |     // 连接成功 | ||||||
|  |     onConnected:()=>{ | ||||||
| 
 | 
 | ||||||
|  |     }, | ||||||
|  |     // 连接关闭 | ||||||
|  |     onClosed:()=>{ | ||||||
|  | 
 | ||||||
|  |     }, | ||||||
|  |     // 收到消息 | ||||||
|  |     onMessage:(data)=>{ | ||||||
|  |       console.log(data); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|   onLoad(async () => { |   onLoad(async () => { | ||||||
|     socketInit({}, (res) => { |  | ||||||
|       console.log(res); |  | ||||||
|     // 监听服务端消息 |  | ||||||
|     }); |  | ||||||
|     await nextTick() |     await nextTick() | ||||||
|     // 加载历史消息 |     // 加载历史消息 | ||||||
|     await getMessageList() |     await getMessageList() | ||||||
|  |  | ||||||
|  | @ -1,96 +1,131 @@ | ||||||
| import { reactive, ref } from 'vue'; | import { reactive, ref, onBeforeUnmount } from 'vue'; | ||||||
| import io from '@hyoga/uni-socket.io'; |  | ||||||
| import { baseUrl, websocketPath } from '@/sheep/config'; | import { baseUrl, websocketPath } from '@/sheep/config'; | ||||||
| export function useWebSocket() { | import { copyValueToTarget } from '@/sheep/util'; | ||||||
|   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
 |  | ||||||
| 
 | 
 | ||||||
|     // 启动初始化
 | export function useWebSocket(opt) { | ||||||
|     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'); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|   // 获取token
 |   // 获取token
 | ||||||
|   const getAccessToken = () => { |   const getAccessToken = () => { | ||||||
|     return uni.getStorageSync('token'); |     return uni.getStorageSync('token'); | ||||||
|   }; |   }; | ||||||
|   return { | 
 | ||||||
|     state, |   const options = reactive({ | ||||||
|     socketInit |     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() | ||||||
|  |   }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -113,3 +113,21 @@ export function resetPagination(pagination) { | ||||||
|   pagination.total = 0; |   pagination.total = 0; | ||||||
|   pagination.pageNo = 1; |   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) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 puhui999
						puhui999