前端 Websocket 公共方法封装
摘要:代码简单使用 function initSocket(userId { const { socket, destroy } = useSocketWithoutUrl(`/ws/websocket/${userId}`, { isInitConnect: false } ; socket.init( ; return { socket, destroy }; } __上面使用代码放在需要初始化WS地方即可,但是如果用...
代码简单使用
function initSocket(userId) {
const { socket, destroy } = useSocketWithoutUrl(`/ws/websocket/${userId}`, { isInitConnect: false });
socket.init();
return { socket, destroy };
}
上面使用代码放在需要初始化WS地方即可,但是如果用360浏览器的话,需要特别处理一下。
360浏览器对WebSocket URL的严格校验
问题表现:360浏览器要求WebSocket URL必须是完整格式(包含协议和域名)
代码兼容
function initSocket(userId, ws_url) {
// 构建完整WebSocket URL
const buildFullUrl = (path) => {
if (path.startsWith('ws://') || path.startsWith('wss://')) {
return path;
}
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
return `${protocol}${window.location.host}${path}`;
};
const finalUrl = userId
? buildFullUrl(`/ws/websocket/${userId}`)
: buildFullUrl(ws_url);
const { socket, destroy } = useSocketWithoutUrl(finalUrl, { isInitConnect: false });
socket.init();
return { socket, destroy };
}
实现代码如下
import { onUnmounted } from 'vue';
/**
* WebSocket 工具类
* 用于管理 WebSocket 连接、发送消息、接收消息等功能
* 支持自动重连、心跳检测等功能
* 使用单例模式确保全局只有一个 WebSocket 实例
* 使用 Vue 的生命周期钩子函数来管理 WebSocket 的连接和销毁
* 使用事件监听机制来处理 WebSocket 的各种事件
* 使用默认的 WebSocket URL 和可选的配置参数
* @example
* const { socket, send, on, off, destroy } = useSocket('/ws/websocket/123', { heartbeatInterval: 30000 });
* socket.on('open', () => console.log('WebSocket opened'));
* socket.on('message', (data) => console.log('Received message:', data));
* send(JSON.stringify({ type: 'message', content: 'Hello, WebSocket!' }));
* on('close', () => console.log('WebSocket closed'));
* off('message'); // 移除消息监听
* destroy(); // 销毁 WebSocket 实例
* @returns {Object} 包含 WebSocket 实例、发送消息方法、事件监听方法和销毁方法
* @property {WebSocket} socket - WebSocket 实例
* @property {Function} send - 发送消息方法
* @property {Function} on - 添加事件监听方法
* @property {Function} off - 移除事件监听方法
* @property {Function} destroy - 销毁 WebSocket 实例方法
* opts - 可选配置参数
* @property {number} opts.heartbeatInterval - 心跳检测间隔,单位毫秒,默认 30000
* @property {number} opts.reconnectInterval - 重连间隔,单位毫秒,默认 5000
* @property {number} opts.maxReconnectAttempts - 最大重连次数,默认 5
* @property {boolean} opts.isInitConnect - 是否在实例化时自动连接,默认 true
...
*/
const Socket = (() => {
let instance = null;
class Socket {
url;
ws = null;
opts = {};
reconnectAttempts = 0;
listeners = {};
heartbeatInterval = null;
isConnecting = false;
constructor(url, opts = {}) {
this.url = url;
this.opts = {
heartbeatInterval: 30000,
reconnectInterval: 5000,
maxReconnectAttempts: 5,
isInitConnect: true,
...opts
};
this.opts.isInitConnect && this.init();
}
static getInstance(url, opts) {
if (!instance) {
instance = new Socket(url, opts);
}
return instance;
}
init() {
if (this.isConnecting) return;
this.isConnecting = true;
this.ws = new WebSocket(this.url);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
onOpen(event) {
// console.log('WebSocket opened:', event);
this.isConnecting = false;
this.reconnectAttempts = 0;
this.startHeartbeat();
this.emit('open', event);
}
onMessage(event) {
// console.log('WebSocket message received:', event.data);
this.emit('message', event.data);
}
onError(event) {
console.error('WebSocket error:', event);
this.isConnecting = false;
this.emit('error', event);
}
onClose(event) {
console.log('WebSocket closed:', event);
this.isConnecting = false;
this.stopHeartbeat();
this.emit('close', event);
if (this.reconnectAttempts < this.opts.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
this.init();
}, this.opts.reconnectInterval);
}
}
startHeartbeat() {
if (!this.opts.heartbeatInterval) return;
this.heartbeatInterval = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'heartbeat', timestamp: Date.now() }));
}
}, this.opts.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
send(data) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
console.error('WebSocket is not open. Cannot send:', data);
// 自动重连并发送
if (!this.isConnecting) {
this.init();
}
}
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
off(event) {
if (this.listeners[event]) {
delete this.listeners[event];
}
}
emit(event, data) {
this.listeners[event]?.forEach(callback => callback(data));
}
// 销毁实例方法
static destroyInstance() {
if (instance) {
instance.stopHeartbeat();
instance.ws?.close();
instance = null;
}
}
}
return Socket;
})();
//
export function useSocket(url, opts = {}) {
const socket = Socket.getInstance(url, opts);
onUnmounted(() => {
socket.off('open');
socket.off('message');
socket.off('error');
socket.off('close');
});
return {
socket,
send: socket.send.bind(socket),
on: socket.on.bind(socket),
off: socket.off.bind(socket),
destroy: Socket.destroyInstance.bind(Socket) // 添加销毁方法
};
}
// Export the default URL socket
export function useSocketWithoutUrl(url, opts = {}) {
return useSocket(url, opts);
}
本文链接:https://blog.smallhao.fun/?id=30 转载需授权!
Chen’Blog版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!