Files
cashier_wx/stores/chat.js
2025-12-05 19:19:54 +08:00

612 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: "",
group_id:'',
// ========== Socket 保活与重连状态 ==========
isManualClose: false, // 是否主动关闭true主动关闭不重连false意外断开自动重连
heartbeatTimer: null, // 心跳定时器
reconnectTimer: null, // 重连定时器
connectionMonitorTimer: null, // 连接状态监控定时器
reconnectCount: 0, // 当前重连次数
maxReconnectCount: 8, // 最大重连次数
reconnectDelay: 1000, // 初始重连延迟ms
maxReconnectDelay: 8000, // 最大重连延迟ms
heartbeatInterval: 15000, // 心跳间隔15s
// ========== 应用与网络状态监听 ==========
isAppActive: true, // 应用是否在前台
lastHeartbeatTime: 0, // 上次心跳时间戳
_listenersInitialized: false, // 状态监听器是否已初始化
// ========== 消息去重机制 ==========
recentMessages: new Map(), // 最近处理的消息映射表key=消息特征哈希value=处理时间戳
deduplicationWindow: 5000, // 去重时间窗口5秒内相同消息只处理一次
maxRecentMessages: 100, // 最近消息缓存最大数量
// ========== 连接状态锁 ==========
isConnecting: false, // 是否正在建立连接(防止重复创建连接)
lastConnectTime: 0, // 上次连接时间(防止短时间内频繁连接)
// ========== 消息回调队列 ==========
receiveMsgCallbacks: [], // 消息接收回调队列,支持多页面同时监听
};
},
actions: {
// ========== 初始化 ==========
init() {
if (!this.isConnect) {
return uni.showToast({
title: "请先连接socket",
icon: "none",
});
}
this.sendMessage(
{
type: "OnbocChat",
operate_type: "init",
shop_id: this.shop_id,
token: uni.cache.get("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.forceReconnect();
// 延迟发送,确保重连成功
setTimeout(() => {
this.sendMessage(msg, isAutoAppend);
}, 1500); // 延长延迟时间,确保重连完成
return;
}
// 3. 正常发送消息
console.log("发送的消息", msg);
const message = isAutoAppend
? {
type: "OnbocChat",
operate_type: "sendMsg",
shop_id: this.shop_id,
token:
uni.getStorageSync("iToken")?.tokenValue ||
uni.cache.get("token") ||
"",
...msg,
}
: msg;
try {
this.socketTask.send({
data: JSON.stringify(message),
success: (res) => {
console.log("发送成功", res);
},
fail: (error) => {
console.error("发送失败,触发重连", error);
// 发送失败,立即重连
this.forceReconnect();
// 重连后重试发送
setTimeout(() => {
this.sendMessage(msg, isAutoAppend);
}, 1500);
},
});
} catch (error) {
console.error("发送消息异常,触发重连", error);
// 捕获异常,立即重连
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. 计算重连延迟(退避算法)
const baseDelay = this.isAppActive ? 500 : this.reconnectDelay;
const delay = Math.min(
baseDelay * Math.pow(2, this.reconnectCount),
this.maxReconnectDelay
);
console.log(`${this.reconnectCount + 1}次重连,延迟${delay}ms`);
// 7. 延迟执行重连
this.reconnectTimer = setTimeout(() => {
this.connectSocket();
this.reconnectCount++;
}, delay);
},
// ========== 强制重连方法 ==========
forceReconnect() {
// 主动关闭状态下不重连
if (this.isManualClose) return;
console.log("执行强制重连");
// 1. 清理现有连接资源,强制重置所有状态
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;
}
}
// 2. 重置重连状态
this.reconnectCount = 0;
this.stopHeartbeat();
// 3. 立即重新连接
setTimeout(() => {
this.connectSocket();
}, 100);
},
// ========== 内部关闭Socket方法不设置手动关闭标记 ==========
closeSocketInternal() {
// 1. 停止所有定时器
this.stopHeartbeat();
this.stopConnectionMonitor();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
// 2. 关闭Socket连接Uniapp会自动清理监听器
if (this.socketTask) {
try {
this.socketTask.close({
code: 1000, // 正常关闭
reason: "internal close",
});
} catch (error) {
console.error("关闭Socket失败", error);
} finally {
// 3. 重置状态
this.socketTask = null;
this.isConnect = false;
this.isConnecting = false;
}
}
},
// ========== 初始化状态监听器 ==========
initStateListeners() {
// 避免重复初始化
if (this._listenersInitialized) return;
console.log("初始化状态监听器");
// 监听应用前后台切换
uni.onAppShow(() => {
console.log("应用切前台检查Socket连接");
this.isAppActive = true;
// 应用切前台,强制重连
this.forceReconnect();
});
uni.onAppHide(() => {
console.log("应用切后台");
this.isAppActive = false;
});
// 监听网络状态变化
uni.onNetworkStatusChange((res) => {
console.log("网络状态变化", res);
if (res.isConnected) {
// 网络恢复,自动重连
console.log("网络恢复,触发重连");
this.forceReconnect();
}
});
this._listenersInitialized = true;
},
// ========== 连接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(); // 初始化聊天
this.startHeartbeat(); // 启动心跳
this.startConnectionMonitor(); // 启动连接状态监控
// 初始化状态监听器
this.initStateListeners();
});
// 6. 接收消息回调 - 支持多回调
this.socketTask.onMessage((res) => {
try {
const data = JSON.parse(res.data);
console.log("收到服务器消息", data);
// 处理发送消息(服务器转发的自己发送的消息)
if (data?.operate_type === "sendMsg") {
// 添加方向标识send
const sendMsg = {
...data.data,
direction: "send",
};
this.processMessage(sendMsg);
}
// 处理接收消息(他人发送的消息)
if (data?.operate_type === "receive_msg") {
// 添加方向标识receive
const receiveMsg = {
...data.data,
coupon: data.data.coupon ? JSON.parse(data.data.coupon) : {},
operate_type: "receive_msg",
direction: "receive",
};
this.processMessage(receiveMsg);
}
// 处理心跳响应
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);
}
// 6. 触发所有消息回调(支持多页面同时监听)
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
coupon, // 优惠券信息
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 || "",
coupon_id: coupon?.id || "",
direction: direction || "",
// 发送消息添加随机数,确保每次发送的消息哈希唯一
random:
direction === "send" ? Math.random().toString(36).substr(2, 9) : "",
});
// 使用简单的哈希算法生成32位哈希值
return this.simpleHash(msgFeatures);
},
// ========== 简单哈希算法 ==========
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.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. 关闭Socket连接内部方法会处理状态重置
this.closeSocketInternal();
console.log("Socket已主动关闭");
},
},
unistorage: false, // 开启后对 state 的数据读写都将持久化
});