聊天问题修复优化

This commit is contained in:
2025-12-05 19:19:54 +08:00
parent b534344b25
commit 28041ddb09
6 changed files with 1322 additions and 382 deletions

View File

@@ -1,7 +1,4 @@
import {
defineStore
} from "pinia";
// import * as shopApi from "@/http/api/shop.js";
import { defineStore } from "pinia";
// #ifdef H5
const socketUrl = "http://192.168.1.42:2348";
@@ -12,115 +9,603 @@ const socketUrl = "ws://192.168.1.42:2348";
// 聊天
export const useChatStore = defineStore("chat", {
state: () => {
return {
socketUrl,
isConnect: false,
socketTask: null,
onReceiveMsg: () => {},
chatList: [],
};
},
actions: {
init() {
if (!this.isConnect) {
return uni.showToast({
title: "请先连接socket",
icon: "none",
});
}
this.sendMessage({
type: "OnbocChat",
operate_type: "init",
shop_id: uni.cache.get('shopInfo').id || '',
token: uni.cache.get('token'),
},
false
);
},
sendMessage(msg, isAutoAppend = true) {
if (!this.isConnect) {
return uni.showToast({
title: "请先连接socket",
icon: "none",
});
}
console.log(this.socketTask);
const message = isAutoAppend ? {
type: "OnbocChat",
operate_type: "sendMsg",
shop_id: uni.cache.get('shopInfo').id || '',
token: uni.cache.get('token'),
...msg,
} :
msg;
this.socketTask.send({
data: JSON.stringify(message),
success: (res) => {
console.log("发送成功", message);
},
fail: (error) => {
console.log("发送失败", error);
},
});
},
connectSocket() {
this.socketTask = uni.connectSocket({
url: socketUrl,
success: (res) => {},
fail: (res) => {
console.log(res);
},
});
state: () => {
return {
socketUrl,
isConnect: false,
socketTask: null,
onReceiveMsg: () => {},
chatList: [],
shop_id: "",
group_id:'',
this.socketTask.onOpen((res) => {
this.isConnect = true;
this.init();
});
// ========== Socket 保活与重连状态 ==========
isManualClose: false, // 是否主动关闭true主动关闭不重连false意外断开自动重连
heartbeatTimer: null, // 心跳定时器
reconnectTimer: null, // 重连定时器
connectionMonitorTimer: null, // 连接状态监控定时器
reconnectCount: 0, // 当前重连次数
maxReconnectCount: 8, // 最大重连次数
reconnectDelay: 1000, // 初始重连延迟ms
maxReconnectDelay: 8000, // 最大重连延迟ms
heartbeatInterval: 15000, // 心跳间隔15s
this.socketTask.onMessage((res) => {
const data = JSON.parse(res.data);
console.log("收到服务器消息", data);
if (data.msg) {
uni.showToast({
title: data.msg,
icon: "none",
});
}
// ========== 应用与网络状态监听 ==========
isAppActive: true, // 应用是否在前台
lastHeartbeatTime: 0, // 上次心跳时间戳
_listenersInitialized: false, // 状态监听器是否已初始化
if (data && data.operate_type == "sendMsg") {
this.chatList.unshift(data.data);
this.onReceiveMsg(data.data);
console.log(this.chatList);
}
if (data && data.operate_type == "receive_msg") {
const msg={
...data.data,
coupon:data.data.coupon?JSON.parse(data.data.coupon):{},
operate_type:"receive_msg",
}
this.chatList.unshift(msg);
this.onReceiveMsg(msg);
console.log(this.chatList);
}
});
// ========== 消息去重机制 ==========
recentMessages: new Map(), // 最近处理的消息映射表key=消息特征哈希value=处理时间戳
deduplicationWindow: 5000, // 去重时间窗口5秒内相同消息只处理一次
maxRecentMessages: 100, // 最近消息缓存最大数量
this.socketTask.onError((res) => {
this.isConnect = false;
console.log("连接错误", res);
});
// ========== 连接状态锁 ==========
isConnecting: false, // 是否正在建立连接(防止重复创建连接)
lastConnectTime: 0, // 上次连接时间(防止短时间内频繁连接)
this.socketTask.onClose(() => {
this.isConnect = false;
console.log("连接已关闭");
this.connectSocket();
});
},
// ========== 消息回调队列 ==========
receiveMsgCallbacks: [], // 消息接收回调队列,支持多页面同时监听
};
},
closeSocket() {
this.socketTask.close();
this.isConnect = false;
},
},
unistorage: false, // 开启后对 state 的数据读写都将持久化
});
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 的数据读写都将持久化
});