289 lines
8.4 KiB
JavaScript
289 lines
8.4 KiB
JavaScript
import { defineStore } from "pinia";
|
||
|
||
// #ifdef H5
|
||
const socketUrl = "http://192.168.1.42:2348";
|
||
// #endif
|
||
// #ifndef H5
|
||
const socketUrl = "ws://192.168.1.42:2348";
|
||
// #endif
|
||
|
||
// 聊天
|
||
export const useChatStore = defineStore("chat", {
|
||
state: () => {
|
||
return {
|
||
socketUrl,
|
||
isConnect: false,
|
||
socketTask: null,
|
||
onReceiveMsg: () => {},
|
||
chatList: [],
|
||
|
||
// ========== 新增:Socket 保活与重连状态 ==========
|
||
isManualClose: false, // 是否主动关闭(true:不重连;false:自动重连)
|
||
heartbeatTimer: null, // 心跳定时器
|
||
reconnectTimer: null, // 重连定时器
|
||
reconnectCount: 0, // 当前重连次数
|
||
maxReconnectCount: 5, // 最大重连次数(避免无限重连)
|
||
reconnectDelay: 1000, // 初始重连延迟(ms)
|
||
maxReconnectDelay: 8000, // 最大重连延迟(ms)
|
||
heartbeatInterval: 30000, // 心跳间隔(30s,根据服务器调整)
|
||
};
|
||
},
|
||
|
||
actions: {
|
||
init() {
|
||
if (!this.isConnect) {
|
||
return uni.showToast({
|
||
title: "请先连接socket",
|
||
icon: "none",
|
||
});
|
||
}
|
||
this.sendMessage(
|
||
{
|
||
type: "OnbocChat",
|
||
operate_type: "init",
|
||
shop_id: uni.getStorageSync("shopId"),
|
||
token: uni.getStorageSync("iToken").tokenValue || "",
|
||
},
|
||
false
|
||
);
|
||
},
|
||
|
||
sendMessage(msg, isAutoAppend = true) {
|
||
// 1. 主动关闭状态:提示用户
|
||
if (this.isManualClose) {
|
||
return uni.showToast({
|
||
title: "Socket已主动关闭,请重新连接",
|
||
icon: "none",
|
||
});
|
||
}
|
||
|
||
// 2. 连接未建立:尝试重连后发送
|
||
if (!this.isConnect) {
|
||
this.reconnect();
|
||
// 延迟发送,确保重连成功
|
||
setTimeout(() => {
|
||
this.sendMessage(msg, isAutoAppend);
|
||
}, this.reconnectDelay);
|
||
return;
|
||
}
|
||
|
||
// 3. 正常发送消息
|
||
console.log('发送的消息', msg);
|
||
const message = isAutoAppend
|
||
? {
|
||
type: "OnbocChat",
|
||
operate_type: "sendMsg",
|
||
shop_id: uni.getStorageSync("shopId"),
|
||
token: uni.getStorageSync("iToken").tokenValue || "",
|
||
...msg,
|
||
}
|
||
: msg;
|
||
|
||
this.socketTask.send({
|
||
data: JSON.stringify(message),
|
||
success: (res) => {
|
||
console.log("发送成功", res);
|
||
},
|
||
fail: (error) => {
|
||
console.log("发送失败", error);
|
||
// 发送失败可能是连接断开,触发重连
|
||
if (!this.isManualClose) {
|
||
this.reconnect();
|
||
}
|
||
},
|
||
});
|
||
},
|
||
|
||
// ========== 新增:发送心跳包 ==========
|
||
sendHeartbeat() {
|
||
if (!this.isConnect || this.isManualClose) return;
|
||
|
||
this.socketTask.send({
|
||
data: JSON.stringify({
|
||
type: "OnbocChat",
|
||
operate_type: "heartbeat", // 心跳类型(需与服务器约定)
|
||
shop_id: uni.getStorageSync("shopId"),
|
||
token: uni.getStorageSync("iToken").tokenValue || "",
|
||
timestamp: Date.now(), // 时间戳(可选,用于服务器校验)
|
||
}),
|
||
fail: (error) => {
|
||
console.log("心跳发送失败,触发重连", error);
|
||
this.reconnect();
|
||
},
|
||
});
|
||
},
|
||
|
||
// ========== 新增:启动心跳定时器 ==========
|
||
startHeartbeat() {
|
||
// 清除旧定时器,避免重复
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer);
|
||
}
|
||
|
||
// 每30s发送一次心跳
|
||
this.heartbeatTimer = setInterval(() => {
|
||
this.sendHeartbeat();
|
||
}, this.heartbeatInterval);
|
||
},
|
||
|
||
// ========== 新增:停止心跳定时器 ==========
|
||
stopHeartbeat() {
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer);
|
||
this.heartbeatTimer = null;
|
||
}
|
||
},
|
||
|
||
// ========== 新增:智能重连逻辑 ==========
|
||
reconnect() {
|
||
// 1. 主动关闭或已连接:不重连
|
||
if (this.isManualClose || this.isConnect) return;
|
||
|
||
// 2. 超过最大重连次数:停止重连
|
||
if (this.reconnectCount >= this.maxReconnectCount) {
|
||
console.log("已达最大重连次数,停止重连");
|
||
return;
|
||
}
|
||
|
||
// 3. 清除旧重连定时器
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer);
|
||
}
|
||
|
||
// 4. 计算重连延迟(退避算法:1s → 2s → 4s → 8s → 8s...)
|
||
const delay = Math.min(
|
||
this.reconnectDelay * Math.pow(2, this.reconnectCount),
|
||
this.maxReconnectDelay
|
||
);
|
||
|
||
console.log(`第${this.reconnectCount + 1}次重连,延迟${delay}ms`);
|
||
|
||
// 5. 延迟执行重连
|
||
this.reconnectTimer = setTimeout(() => {
|
||
this.connectSocket();
|
||
this.reconnectCount++;
|
||
}, delay);
|
||
},
|
||
|
||
// ========== 优化:连接Socket ==========
|
||
connectSocket() {
|
||
// 1. 避免重复连接
|
||
if (this.isConnect || this.socketTask) {
|
||
return;
|
||
}
|
||
|
||
// 2. 重置主动关闭标记
|
||
this.isManualClose = false;
|
||
|
||
// 3. 创建Socket连接
|
||
this.socketTask = uni.connectSocket({
|
||
url: this.socketUrl,
|
||
success: (res) => {
|
||
console.log("Socket连接请求发送成功");
|
||
},
|
||
fail: (res) => {
|
||
console.log("Socket连接请求失败", res);
|
||
// 请求失败,触发重连
|
||
this.reconnect();
|
||
},
|
||
});
|
||
|
||
// 4. 连接成功回调
|
||
this.socketTask.onOpen((res) => {
|
||
console.log("Socket连接成功");
|
||
this.isConnect = true;
|
||
this.reconnectCount = 0; // 重置重连次数
|
||
this.init(); // 初始化聊天
|
||
this.startHeartbeat(); // 启动心跳
|
||
});
|
||
|
||
// 5. 接收消息回调(添加容错处理)
|
||
this.socketTask.onMessage((res) => {
|
||
try {
|
||
const data = JSON.parse(res.data);
|
||
console.log("收到服务器消息", data);
|
||
|
||
if (data.msg) {
|
||
uni.showToast({
|
||
title: data.msg,
|
||
icon: "none",
|
||
});
|
||
}
|
||
|
||
// 处理发送消息
|
||
if (data?.operate_type === "sendMsg") {
|
||
this.chatList.unshift(data.data);
|
||
this.onReceiveMsg(data.data);
|
||
}
|
||
|
||
// 处理接收消息
|
||
if (data?.operate_type === "receive_msg") {
|
||
const msg = {
|
||
...data.data,
|
||
operate_type: "receive_msg",
|
||
};
|
||
this.chatList.unshift(msg);
|
||
this.onReceiveMsg(msg);
|
||
}
|
||
|
||
// 处理心跳响应(如果服务器返回)
|
||
if (data?.operate_type === "heartbeat_ack") {
|
||
console.log("心跳响应正常");
|
||
}
|
||
} catch (error) {
|
||
console.error("消息解析失败", error);
|
||
}
|
||
});
|
||
|
||
// 6. 连接错误回调
|
||
this.socketTask.onError((res) => {
|
||
console.log("Socket连接错误", res);
|
||
this.isConnect = false;
|
||
// 错误触发重连
|
||
this.reconnect();
|
||
});
|
||
|
||
// 7. 连接关闭回调(区分主动/意外关闭)
|
||
this.socketTask.onClose(() => {
|
||
console.log("Socket连接已关闭");
|
||
this.isConnect = false;
|
||
this.stopHeartbeat(); // 停止心跳
|
||
|
||
// 只有非主动关闭时才重连
|
||
if (!this.isManualClose) {
|
||
console.log("意外断开,触发重连");
|
||
this.reconnect();
|
||
} else {
|
||
console.log("主动关闭,不重连");
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 优化:主动关闭Socket ==========
|
||
closeSocket() {
|
||
// 1. 设置主动关闭标记(关键:避免自动重连)
|
||
this.isManualClose = true;
|
||
|
||
// 2. 停止所有定时器,清理资源
|
||
this.stopHeartbeat();
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer);
|
||
this.reconnectTimer = null;
|
||
}
|
||
|
||
// 3. 关闭Socket连接
|
||
if (this.socketTask) {
|
||
this.socketTask.close();
|
||
this.socketTask = null; // 释放引用
|
||
}
|
||
|
||
// 4. 重置连接状态
|
||
this.isConnect = false;
|
||
this.reconnectCount = 0; // 重置重连次数
|
||
|
||
console.log("Socket已主动关闭");
|
||
},
|
||
},
|
||
|
||
unistorage: false, // 开启后对 state 的数据读写都将持久化
|
||
}); |