Files
cashier_app/store/chat.js
2025-12-05 19:19:44 +08:00

589 lines
19 KiB
JavaScript
Raw 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: 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 的数据读写都将持久化
});