589 lines
19 KiB
JavaScript
589 lines
19 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: [],
|
||
shop_id: uni.getStorageSync("shopId"),
|
||
token: uni.getStorageSync("iToken").tokenValue || "",
|
||
group_id:'',
|
||
isManualClose: false, // 是否主动关闭(true:不重连;false:自动重连)
|
||
heartbeatTimer: null, // 心跳定时器
|
||
reconnectTimer: null, // 重连定时器
|
||
connectionMonitorTimer: null, // 连接状态监控定时器
|
||
reconnectCount: 0, // 当前重连次数
|
||
maxReconnectCount: 5, // 最大重连次数(避免无限重连)
|
||
reconnectDelay: 1000, // 初始重连延迟(ms)
|
||
maxReconnectDelay: 8000, // 最大重连延迟(ms)
|
||
heartbeatInterval: 30000, // 心跳间隔(30s,根据服务器调整)
|
||
|
||
isAppActive: true, // 应用是否在前台
|
||
lastHeartbeatTime: 0, // 上次心跳时间戳
|
||
lastConnectTime: 0, // 上次连接时间(防止短时间内频繁连接)
|
||
|
||
// ========== 新增:修复未定义的状态 ==========
|
||
_listenersInitialized: false, // 状态监听器是否已初始化
|
||
|
||
// ========== 新增:连接状态管理 ==========
|
||
isConnecting: false, // 是否正在建立连接(防止重复调用connectSocket)
|
||
|
||
// ========== 新增:消息去重机制 ==========
|
||
recentMessages: new Map(), // 最近处理的消息映射表:key=消息特征哈希,value=处理时间戳
|
||
deduplicationWindow: 5000, // 去重时间窗口(5秒内相同消息只处理一次)
|
||
maxRecentMessages: 100, // 最近消息缓存最大数量
|
||
|
||
// ========== 新增:多页面消息监听支持 ==========
|
||
receiveMsgCallbacks: [], // 消息接收回调队列,支持多页面同时监听
|
||
};
|
||
},
|
||
|
||
actions: {
|
||
// ========== 初始化状态监听 ==========
|
||
initStateListeners() {
|
||
// 避免重复初始化
|
||
if (this._listenersInitialized) return;
|
||
|
||
console.log("初始化状态监听器");
|
||
|
||
// 监听应用前后台切换
|
||
uni.onAppShow(() => {
|
||
console.log("应用切前台,检查Socket连接");
|
||
this.isAppActive = true;
|
||
// 应用切前台,强制重连(无论当前状态如何)
|
||
this.forceReconnect();
|
||
});
|
||
|
||
uni.onAppHide(() => {
|
||
console.log("应用切后台");
|
||
this.isAppActive = false;
|
||
// 后台可选择暂停心跳(根据实际需求调整)
|
||
// this.stopHeartbeat();
|
||
});
|
||
|
||
// 监听网络状态变化
|
||
uni.onNetworkStatusChange((res) => {
|
||
console.log("网络状态变化", res);
|
||
if (res.isConnected) {
|
||
// 网络恢复,自动重连
|
||
console.log("网络恢复,触发重连");
|
||
this.forceReconnect();
|
||
}
|
||
});
|
||
|
||
this._listenersInitialized = true;
|
||
},
|
||
|
||
// ========== 强制重连方法 ==========
|
||
forceReconnect() {
|
||
// 主动关闭状态下不重连
|
||
if (this.isManualClose) return;
|
||
|
||
console.log("执行强制重连");
|
||
|
||
// 1. 清理现有连接资源
|
||
this.cleanupSocket();
|
||
|
||
// 2. 重置重连状态
|
||
this.reconnectCount = 0;
|
||
this.stopHeartbeat();
|
||
this.stopConnectionMonitor();
|
||
|
||
// 3. 立即重新连接
|
||
setTimeout(() => {
|
||
this.connectSocket();
|
||
}, 100);
|
||
},
|
||
|
||
// ========== 新增:清理Socket资源 ==========
|
||
cleanupSocket() {
|
||
if (this.socketTask) {
|
||
try {
|
||
this.socketTask.close({
|
||
code: 1000, // 正常关闭
|
||
reason: 'force reconnect'
|
||
});
|
||
} catch (error) {
|
||
console.error("关闭Socket失败", error);
|
||
} finally {
|
||
// 强制重置状态
|
||
this.socketTask = null;
|
||
this.isConnect = false;
|
||
this.isConnecting = false;
|
||
}
|
||
}
|
||
},
|
||
|
||
init() {
|
||
if (!this.isConnect) {
|
||
return uni.showToast({
|
||
title: "请先连接socket",
|
||
icon: "none",
|
||
});
|
||
}
|
||
this.sendMessage(
|
||
{
|
||
type: "OnbocChat",
|
||
operate_type: "init",
|
||
shop_id: this.shop_id,
|
||
token: this.token,
|
||
},
|
||
false
|
||
);
|
||
},
|
||
|
||
// ========== 增强发送消息容错性 ==========
|
||
sendMessage(msg, isAutoAppend = true) {
|
||
// 1. 主动关闭状态:提示用户
|
||
if (this.isManualClose) {
|
||
return uni.showToast({
|
||
title: "Socket已主动关闭,请重新连接",
|
||
icon: "none",
|
||
});
|
||
}
|
||
|
||
// 2. 连接未建立或SocketTask失效:尝试重连后发送
|
||
if (!this.isConnect || !this.socketTask) {
|
||
console.warn("Socket连接未建立,尝试重连...");
|
||
this.reconnect();
|
||
// 延长延迟时间,确保重连完成
|
||
setTimeout(() => {
|
||
this.sendMessage(msg, isAutoAppend);
|
||
}, 1500);
|
||
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;
|
||
|
||
try {
|
||
this.socketTask.send({
|
||
data: JSON.stringify(message),
|
||
success: (res) => {
|
||
console.log("发送成功", res);
|
||
},
|
||
fail: (error) => {
|
||
console.error("发送失败,触发重连", error);
|
||
// 发送失败可能是连接断开,触发重连
|
||
if (!this.isManualClose) {
|
||
this.forceReconnect();
|
||
// 重连后重试发送
|
||
setTimeout(() => {
|
||
this.sendMessage(msg, isAutoAppend);
|
||
}, 1500);
|
||
}
|
||
},
|
||
});
|
||
} catch (error) {
|
||
console.error("发送消息异常,触发重连", error);
|
||
// 捕获异常,立即重连
|
||
if (!this.isManualClose) {
|
||
this.forceReconnect();
|
||
// 重连后重试发送
|
||
setTimeout(() => {
|
||
this.sendMessage(msg, isAutoAppend);
|
||
}, 1500);
|
||
}
|
||
}
|
||
},
|
||
|
||
// ========== 发送心跳包 ==========
|
||
sendHeartbeat() {
|
||
// 应用在后台时可调整心跳策略(如降低频率)
|
||
if (!this.isConnect || this.isManualClose || !this.socketTask) return;
|
||
|
||
const now = Date.now();
|
||
// 记录心跳时间
|
||
this.lastHeartbeatTime = now;
|
||
|
||
this.socketTask.send({
|
||
data: JSON.stringify({
|
||
type: "ping_interval",
|
||
}),
|
||
fail: (error) => {
|
||
console.log("心跳发送失败,触发重连", error);
|
||
this.forceReconnect();
|
||
},
|
||
});
|
||
},
|
||
|
||
// ========== 启动心跳定时器 ==========
|
||
startHeartbeat() {
|
||
// 清除旧定时器
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer);
|
||
}
|
||
|
||
// 立即发送一次心跳,确保连接活跃
|
||
this.sendHeartbeat();
|
||
|
||
// 启动心跳定时器,间隔15s
|
||
this.heartbeatTimer = setInterval(() => {
|
||
this.sendHeartbeat();
|
||
}, this.heartbeatInterval);
|
||
},
|
||
|
||
// ========== 停止心跳定时器 ==========
|
||
stopHeartbeat() {
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer);
|
||
this.heartbeatTimer = null;
|
||
}
|
||
},
|
||
|
||
// ========== 新增:启动连接状态监控 ==========
|
||
startConnectionMonitor() {
|
||
// 清除旧定时器
|
||
if (this.connectionMonitorTimer) {
|
||
clearInterval(this.connectionMonitorTimer);
|
||
}
|
||
|
||
// 每30秒检查一次连接状态
|
||
this.connectionMonitorTimer = setInterval(() => {
|
||
// 如果应该连接但实际未连接,触发重连
|
||
if (!this.isManualClose && this.isConnect && !this.socketTask) {
|
||
console.warn("连接监控:发现连接状态异常,强制重置");
|
||
this.forceReconnect();
|
||
}
|
||
}, 30000);
|
||
},
|
||
|
||
// ========== 新增:停止连接状态监控 ==========
|
||
stopConnectionMonitor() {
|
||
if (this.connectionMonitorTimer) {
|
||
clearInterval(this.connectionMonitorTimer);
|
||
this.connectionMonitorTimer = null;
|
||
}
|
||
},
|
||
|
||
// ========== 智能重连逻辑 ==========
|
||
reconnect() {
|
||
// 1. 主动关闭或已连接:不重连
|
||
if (this.isManualClose || this.isConnect) return;
|
||
|
||
// 2. 正在连接中:不重连
|
||
if (this.isConnecting) {
|
||
console.log("正在建立连接中,跳过重连");
|
||
return;
|
||
}
|
||
|
||
// 3. 短时间内已尝试连接:不重连(防止频繁连接)
|
||
const now = Date.now();
|
||
if (now - this.lastConnectTime < 1000) {
|
||
console.log("短时间内已尝试连接,跳过重连");
|
||
return;
|
||
}
|
||
|
||
// 4. 超过最大重连次数:停止重连
|
||
if (this.reconnectCount >= this.maxReconnectCount) {
|
||
console.log("已达最大重连次数,停止重连");
|
||
return;
|
||
}
|
||
|
||
// 5. 清除旧重连定时器
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer);
|
||
}
|
||
|
||
// 6. 计算重连延迟(退避算法: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`);
|
||
|
||
// 7. 延迟执行重连
|
||
this.reconnectTimer = setTimeout(() => {
|
||
this.connectSocket();
|
||
this.reconnectCount++;
|
||
}, delay);
|
||
},
|
||
|
||
// ========== 优化:连接Socket ==========
|
||
connectSocket() {
|
||
// 1. 严格检查:避免重复连接
|
||
if (this.isConnect || this.isConnecting || this.isManualClose) {
|
||
console.warn("跳过连接:当前状态不允许建立新连接", {
|
||
isConnect: this.isConnect,
|
||
isConnecting: this.isConnecting,
|
||
isManualClose: this.isManualClose
|
||
});
|
||
// 关键:如果 socketTask 已失效但 isConnect 为 true,强制重置状态
|
||
if (this.isConnect && !this.socketTask) {
|
||
console.warn("Socket连接状态异常,强制重置");
|
||
this.isConnect = false;
|
||
this.forceReconnect();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 2. 设置连接中状态
|
||
this.isConnecting = true;
|
||
this.lastConnectTime = Date.now();
|
||
|
||
// 3. 重置主动关闭标记
|
||
this.isManualClose = false;
|
||
|
||
console.log("开始创建新的Socket连接");
|
||
|
||
// 4. 创建Socket连接
|
||
this.socketTask = uni.connectSocket({
|
||
url: this.socketUrl,
|
||
success: (res) => {
|
||
console.log("Socket连接请求发送成功");
|
||
},
|
||
fail: (res) => {
|
||
console.log("Socket连接请求失败", res);
|
||
// 请求失败,重置状态
|
||
this.isConnecting = false;
|
||
// 触发重连
|
||
this.reconnect();
|
||
},
|
||
});
|
||
|
||
// 5. 连接成功回调
|
||
this.socketTask.onOpen((res) => {
|
||
console.log("Socket连接成功");
|
||
// 重置连接状态
|
||
this.isConnect = true;
|
||
this.isConnecting = false;
|
||
this.reconnectCount = 0;
|
||
this.init();
|
||
|
||
// 缩短心跳间隔到15s(更频繁的心跳维持连接活跃)
|
||
this.heartbeatInterval = 15000;
|
||
this.startHeartbeat();
|
||
this.startConnectionMonitor(); // 启动连接状态监控
|
||
|
||
// 初始化状态监听(仅在首次连接时执行)
|
||
if (!this._listenersInitialized) {
|
||
this.initStateListeners();
|
||
this._listenersInitialized = true;
|
||
}
|
||
});
|
||
|
||
// 6. 接收消息回调(添加容错处理和消息去重)
|
||
this.socketTask.onMessage((res) => {
|
||
try {
|
||
const data = JSON.parse(res.data);
|
||
console.log("收到服务器消息", data);
|
||
|
||
// 处理发送消息
|
||
if (data?.operate_type === "sendMsg") {
|
||
this.processMessage({
|
||
...data.data,
|
||
direction: 'send' // 添加消息方向标识
|
||
});
|
||
}
|
||
|
||
// 处理接收消息
|
||
if (data?.operate_type === "receive_msg") {
|
||
const msg = {
|
||
...data.data,
|
||
operate_type: "receive_msg",
|
||
direction: 'receive' // 添加消息方向标识
|
||
};
|
||
this.processMessage(msg);
|
||
}
|
||
|
||
// 处理心跳响应(如果服务器返回)
|
||
if (data?.operate_type === "heartbeat_ack") {
|
||
console.log("心跳响应正常");
|
||
}
|
||
} catch (error) {
|
||
console.error("消息解析失败", error);
|
||
}
|
||
});
|
||
|
||
// 7. 连接错误回调
|
||
this.socketTask.onError((res) => {
|
||
console.log("Socket连接错误", res);
|
||
// 重置连接状态
|
||
this.isConnect = false;
|
||
this.isConnecting = false;
|
||
// 错误触发重连
|
||
this.forceReconnect();
|
||
});
|
||
|
||
// 8. 连接关闭回调(区分主动/意外关闭)
|
||
this.socketTask.onClose(() => {
|
||
console.log("Socket连接已关闭");
|
||
// 重置连接状态
|
||
this.isConnect = false;
|
||
this.isConnecting = false;
|
||
this.stopHeartbeat(); // 停止心跳
|
||
this.stopConnectionMonitor(); // 停止连接状态监控
|
||
|
||
// 只有非主动关闭时才重连
|
||
if (!this.isManualClose) {
|
||
console.log("意外断开,触发重连");
|
||
this.reconnect();
|
||
} else {
|
||
console.log("主动关闭,不重连");
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 新增:消息处理与去重 ==========
|
||
processMessage(msg) {
|
||
// 1. 生成消息特征哈希
|
||
const msgHash = this.generateMessageHash(msg);
|
||
const now = Date.now();
|
||
|
||
// 2. 清理过期消息
|
||
this.cleanupExpiredMessages(now);
|
||
|
||
// 3. 去重逻辑:只对接收的消息应用严格去重
|
||
if (msg.direction === 'receive') {
|
||
// 接收消息:5秒内相同特征的消息只处理一次
|
||
const lastProcessTime = this.recentMessages.get(msgHash);
|
||
if (lastProcessTime && (now - lastProcessTime) < this.deduplicationWindow) {
|
||
console.log("重复接收消息,已忽略(特征哈希:", msgHash, ")");
|
||
return; // 重复消息,直接返回
|
||
}
|
||
// 记录接收消息的处理时间
|
||
this.recentMessages.set(msgHash, now);
|
||
} else {
|
||
// 发送消息:直接通过,不做严格去重
|
||
console.log("发送消息,直接处理(特征哈希:", msgHash, ")");
|
||
}
|
||
|
||
// 4. 限制最近消息数量,避免内存泄漏
|
||
if (this.recentMessages.size > this.maxRecentMessages) {
|
||
// 移除最早的消息
|
||
const firstKey = this.recentMessages.keys().next().value;
|
||
this.recentMessages.delete(firstKey);
|
||
}
|
||
|
||
// 5. 正常处理消息
|
||
if(this.group_id==msg.to_id){
|
||
this.chatList.unshift(msg);
|
||
}
|
||
// 触发所有消息回调(支持多页面同时监听)
|
||
this.triggerReceiveMsgCallbacks(msg);
|
||
},
|
||
|
||
// ========== 新增:触发消息回调 ==========
|
||
triggerReceiveMsgCallbacks(msg) {
|
||
// 调用旧的单个回调(兼容原有代码)
|
||
if (typeof this.onReceiveMsg === 'function') {
|
||
this.onReceiveMsg(msg);
|
||
}
|
||
|
||
// 调用所有注册的回调
|
||
this.receiveMsgCallbacks.forEach(callback => {
|
||
if (typeof callback === 'function') {
|
||
callback(msg);
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 新增:注册消息回调 ==========
|
||
registerReceiveMsgCallback(callback) {
|
||
if (typeof callback === 'function' && !this.receiveMsgCallbacks.includes(callback)) {
|
||
this.receiveMsgCallbacks.push(callback);
|
||
}
|
||
},
|
||
|
||
// ========== 新增:移除消息回调 ==========
|
||
removeReceiveMsgCallback(callback) {
|
||
const index = this.receiveMsgCallbacks.indexOf(callback);
|
||
if (index > -1) {
|
||
this.receiveMsgCallbacks.splice(index, 1);
|
||
}
|
||
},
|
||
|
||
// ========== 新增:生成消息特征哈希 ==========
|
||
generateMessageHash(msg) {
|
||
// 基于消息的核心字段生成哈希,确保相同内容的消息哈希一致
|
||
const {
|
||
content, // 消息内容
|
||
msg_type, // 消息类型
|
||
timestamp, // 时间戳
|
||
operate_type, // 操作类型
|
||
from_user_id, // 发送者ID(如果有)
|
||
to_user_id, // 接收者ID(如果有)
|
||
image_url, // 图片URL(如果有)
|
||
direction // 消息方向
|
||
} = msg;
|
||
|
||
// 组合核心字段,生成唯一字符串
|
||
// 发送消息添加随机数,确保每次发送的消息哈希唯一
|
||
const msgFeatures = JSON.stringify({
|
||
content: content || '',
|
||
msg_type: msg_type || '',
|
||
operate_type: operate_type || '',
|
||
from_user_id: from_user_id || '',
|
||
to_user_id: to_user_id || '',
|
||
image_url: image_url || '',
|
||
direction: direction || '',
|
||
// 发送消息添加随机数,确保每次发送的消息哈希唯一
|
||
random: direction === 'send' ? Math.random().toString(36).substr(2, 9) : ''
|
||
});
|
||
|
||
// 使用简单的哈希算法生成32位哈希值
|
||
let hash = 0;
|
||
for (let i = 0; i < msgFeatures.length; i++) {
|
||
const char = msgFeatures.charCodeAt(i);
|
||
hash = ((hash << 5) - hash) + char;
|
||
hash = hash & hash; // 转换为32位整数
|
||
}
|
||
return Math.abs(hash).toString(16); // 转换为16进制字符串
|
||
},
|
||
|
||
// ========== 新增:清理过期消息 ==========
|
||
cleanupExpiredMessages(currentTime) {
|
||
for (const [hash, time] of this.recentMessages.entries()) {
|
||
if (currentTime - time > this.deduplicationWindow) {
|
||
this.recentMessages.delete(hash);
|
||
}
|
||
}
|
||
},
|
||
|
||
// ========== 优化:主动关闭Socket ==========
|
||
closeSocket() {
|
||
// 1. 设置主动关闭标记(关键:避免自动重连)
|
||
this.isManualClose = true;
|
||
|
||
// 2. 停止所有定时器,清理资源
|
||
this.stopHeartbeat();
|
||
this.stopConnectionMonitor();
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer);
|
||
this.reconnectTimer = null;
|
||
}
|
||
|
||
// 3. 清理Socket资源
|
||
this.cleanupSocket();
|
||
|
||
// 4. 重置连接状态
|
||
this.isConnect = false;
|
||
this.reconnectCount = 0; // 重置重连次数
|
||
|
||
console.log("Socket已主动关闭");
|
||
},
|
||
},
|
||
|
||
unistorage: false, // 开启后对 state 的数据读写都将持久化
|
||
}); |