391 lines
11 KiB
JavaScript
391 lines
11 KiB
JavaScript
![]() |
import store from '@/config/store';
|
||
|
import base from '@/config/baseUrl';
|
||
|
|
||
|
class IMWebSocket {
|
||
|
constructor() {
|
||
|
this.socketUrl = base.socketUrl || "ws://q.sjqqzc.top:8586";
|
||
|
this.socket = null;
|
||
|
this.isConnected = false;
|
||
|
this.reconnectCount = 0;
|
||
|
this.maxReconnectCount = 5;
|
||
|
this.reconnectTimer = null;
|
||
|
this.heartbeatTimer = null;
|
||
|
this.messageHandlers = new Set();
|
||
|
|
||
|
// 存储联系人列表
|
||
|
this.contactList = new Map(); // key: contact_id, value: contact_info
|
||
|
// 存储未读消息数
|
||
|
this.unreadCount = new Map(); // key: contact_id, value: unread_count
|
||
|
// 存储最后一条消息
|
||
|
this.lastMessage = new Map(); // key: contact_id, value: last_message
|
||
|
|
||
|
// 绑定方法到实例
|
||
|
this.connect = this.connect.bind(this);
|
||
|
this.disconnect = this.disconnect.bind(this);
|
||
|
this.reconnect = this.reconnect.bind(this);
|
||
|
this.send = this.send.bind(this);
|
||
|
this.startHeartbeat = this.startHeartbeat.bind(this);
|
||
|
this.stopHeartbeat = this.stopHeartbeat.bind(this);
|
||
|
}
|
||
|
|
||
|
// 初始化连接
|
||
|
init() {
|
||
|
if (!this.socketUrl) {
|
||
|
console.warn('WebSocket URL not configured');
|
||
|
return;
|
||
|
}
|
||
|
this.connect();
|
||
|
}
|
||
|
|
||
|
// 建立连接
|
||
|
connect() {
|
||
|
if (this.socket) {
|
||
|
this.disconnect();
|
||
|
}
|
||
|
|
||
|
this.socket = uni.connectSocket({
|
||
|
url: this.socketUrl,
|
||
|
success: () => {
|
||
|
console.log('WebSocket连接创建成功');
|
||
|
},
|
||
|
fail: (err) => {
|
||
|
console.error('WebSocket连接创建失败:', err);
|
||
|
this.reconnect();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 监听连接打开
|
||
|
uni.onSocketOpen(() => {
|
||
|
console.log('WebSocket连接已打开');
|
||
|
this.isConnected = true;
|
||
|
this.reconnectCount = 0;
|
||
|
this.sendAuth();
|
||
|
this.startHeartbeat();
|
||
|
});
|
||
|
|
||
|
// 监听消息
|
||
|
uni.onSocketMessage((res) => {
|
||
|
try {
|
||
|
const message = JSON.parse(res.data);
|
||
|
this.handleMessage(message);
|
||
|
} catch (e) {
|
||
|
console.error('消息解析错误:', e);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// 监听连接关闭
|
||
|
uni.onSocketClose(() => {
|
||
|
console.log('WebSocket连接已关闭');
|
||
|
this.isConnected = false;
|
||
|
this.stopHeartbeat();
|
||
|
this.reconnect();
|
||
|
});
|
||
|
|
||
|
// 监听错误
|
||
|
uni.onSocketError((err) => {
|
||
|
console.error('WebSocket错误:', err);
|
||
|
this.isConnected = false;
|
||
|
this.reconnect();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 断开连接
|
||
|
disconnect() {
|
||
|
this.stopHeartbeat();
|
||
|
if (this.socket) {
|
||
|
uni.closeSocket();
|
||
|
this.socket = null;
|
||
|
}
|
||
|
this.isConnected = false;
|
||
|
}
|
||
|
|
||
|
// 重连机制
|
||
|
reconnect() {
|
||
|
if (this.reconnectCount >= this.maxReconnectCount) {
|
||
|
console.log('达到最大重连次数');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.reconnectTimer) {
|
||
|
clearTimeout(this.reconnectTimer);
|
||
|
}
|
||
|
|
||
|
this.reconnectTimer = setTimeout(() => {
|
||
|
this.reconnectCount++;
|
||
|
console.log(`第${this.reconnectCount}次重连`);
|
||
|
this.connect();
|
||
|
}, 3000);
|
||
|
}
|
||
|
|
||
|
// 发送认证信息
|
||
|
sendAuth() {
|
||
|
if (store.state.userInfo.token) {
|
||
|
this.send({
|
||
|
type: 'auth',
|
||
|
token: store.state.userInfo.token
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 发送消息
|
||
|
send(data) {
|
||
|
if (!this.isConnected) {
|
||
|
console.warn('WebSocket未连接');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (typeof data === 'object') {
|
||
|
data = JSON.stringify(data);
|
||
|
}
|
||
|
|
||
|
return new Promise((resolve, reject) => {
|
||
|
uni.sendSocketMessage({
|
||
|
data,
|
||
|
success: () => resolve(true),
|
||
|
fail: (err) => {
|
||
|
console.error('发送消息失败:', err);
|
||
|
reject(err);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 启动心跳
|
||
|
startHeartbeat() {
|
||
|
this.stopHeartbeat();
|
||
|
this.heartbeatTimer = setInterval(() => {
|
||
|
this.send({ type: 'ping' }).catch(() => {
|
||
|
this.reconnect();
|
||
|
});
|
||
|
}, 30000);
|
||
|
}
|
||
|
|
||
|
// 停止心跳
|
||
|
stopHeartbeat() {
|
||
|
if (this.heartbeatTimer) {
|
||
|
clearInterval(this.heartbeatTimer);
|
||
|
this.heartbeatTimer = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 处理接收到的消息
|
||
|
handleMessage(message) {
|
||
|
if(!message.type || message.type == "ok" || message.type == "pong"){
|
||
|
return ;
|
||
|
}
|
||
|
console.log(message)
|
||
|
// 更新联系人列表
|
||
|
this.updateContactList(message);
|
||
|
|
||
|
// 更新未读消息数
|
||
|
this.updateUnreadCount(message);
|
||
|
|
||
|
// 更新最后一条消息
|
||
|
this.updateLastMessage(message);
|
||
|
|
||
|
// 发送通知
|
||
|
this.sendNotification(message);
|
||
|
|
||
|
// 通知所有注册的消息处理器
|
||
|
this.messageHandlers.forEach(handler => {
|
||
|
try {
|
||
|
handler(message);
|
||
|
} catch (e) {
|
||
|
console.error('消息处理错误:', e);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 更新联系人列表
|
||
|
async updateContactList(message) {
|
||
|
const contactId = message.from_user.uid;
|
||
|
const contactType = message.target_type;
|
||
|
const contactKey = `${contactType}_${contactId}`;
|
||
|
|
||
|
// 如果联系人不在列表中,获取联系人信息
|
||
|
if (!this.contactList.has(contactKey)) {
|
||
|
try {
|
||
|
const res = await uni.$http.get('/api/chat/contact_info', {
|
||
|
contact_id: contactId,
|
||
|
contact_type: contactType
|
||
|
});
|
||
|
|
||
|
if (res.data) {
|
||
|
this.contactList.set(contactKey, {
|
||
|
...res.data,
|
||
|
type: contactType,
|
||
|
id: contactId
|
||
|
});
|
||
|
|
||
|
// 触发联系人列表更新事件
|
||
|
this.emitContactListUpdate();
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('获取联系人信息失败:', error);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 更新未读消息数
|
||
|
updateUnreadCount(message) {
|
||
|
const contactId = message.from_user.id;
|
||
|
const contactType = message.target_type;
|
||
|
const contactKey = `${contactType}_${contactId}`;
|
||
|
|
||
|
// 如果当前不在聊天页面,增加未读数
|
||
|
const currentPage = getCurrentPages();
|
||
|
const isInChatPage = currentPage.some(page =>
|
||
|
page.route === 'pages/im/chat' &&
|
||
|
page.$page.options.target_id === contactId+'' &&
|
||
|
page.$page.options.target_type === contactType
|
||
|
);
|
||
|
console.warn(isInChatPage);
|
||
|
|
||
|
if (!isInChatPage) {
|
||
|
const currentCount = this.unreadCount.get(contactKey) || 0;
|
||
|
this.unreadCount.set(contactKey, currentCount + 1);
|
||
|
|
||
|
// 触发未读消息更新事件
|
||
|
this.emitUnreadCountUpdate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 更新最后一条消息
|
||
|
updateLastMessage(message) {
|
||
|
const contactId = message.from_user.uid;
|
||
|
const contactType = message.target_type;
|
||
|
const contactKey = `${contactType}_${contactId}`;
|
||
|
|
||
|
this.lastMessage.set(contactKey, {
|
||
|
content: message.content,
|
||
|
type: message.type,
|
||
|
create_time: message.create_time
|
||
|
});
|
||
|
|
||
|
// 触发最后消息更新事件
|
||
|
this.emitLastMessageUpdate();
|
||
|
}
|
||
|
|
||
|
// 发送通知
|
||
|
sendNotification(message) {
|
||
|
// #ifdef APP-PLUS
|
||
|
const currentPage = getCurrentPages();
|
||
|
const isInChatPage = currentPage.some(page =>
|
||
|
page.route === 'pages/im/chat' &&
|
||
|
page.$page.options.target_id === message.from_user.uid.toString() &&
|
||
|
page.$page.options.target_type === message.target_type
|
||
|
);
|
||
|
|
||
|
// 如果不在聊天页面,发送通知
|
||
|
if (!isInChatPage) {
|
||
|
const contact = this.contactList.get(`${message.target_type}_${message.from_user.uid}`);
|
||
|
const title = contact ? contact.username : '新消息';
|
||
|
let content = '';
|
||
|
|
||
|
// 根据消息类型设置通知内容
|
||
|
switch (message.type) {
|
||
|
case 'text':
|
||
|
content = message.content;
|
||
|
break;
|
||
|
case 'image':
|
||
|
content = '[图片]';
|
||
|
break;
|
||
|
default:
|
||
|
content = '[新消息]';
|
||
|
}
|
||
|
|
||
|
// 创建通知
|
||
|
plus.push.createMessage(content, {
|
||
|
title: title,
|
||
|
payload: {
|
||
|
type: 'chat',
|
||
|
target_id: message.from_user.uid,
|
||
|
target_type: message.target_type
|
||
|
}
|
||
|
}, {
|
||
|
sound: 'system',
|
||
|
cover: true
|
||
|
});
|
||
|
}
|
||
|
// #endif
|
||
|
}
|
||
|
|
||
|
// 获取联系人列表
|
||
|
getContactList() {
|
||
|
return Array.from(this.contactList.values());
|
||
|
}
|
||
|
|
||
|
// 获取未读消息数
|
||
|
getUnreadCount(contactId, contactType) {
|
||
|
return this.unreadCount.get(`${contactType}_${contactId}`) || 0;
|
||
|
}
|
||
|
|
||
|
// 获取最后一条消息
|
||
|
getLastMessage(contactId, contactType) {
|
||
|
return this.lastMessage.get(`${contactType}_${contactId}`);
|
||
|
}
|
||
|
|
||
|
// 清除未读消息数
|
||
|
clearUnreadCount(contactId, contactType) {
|
||
|
const contactKey = `${contactType}_${contactId}`;
|
||
|
this.unreadCount.delete(contactKey);
|
||
|
this.emitUnreadCountUpdate();
|
||
|
}
|
||
|
|
||
|
// 事件发射器
|
||
|
emitContactListUpdate() {
|
||
|
uni.$emit('im:contactListUpdate', this.getContactList());
|
||
|
}
|
||
|
|
||
|
emitUnreadCountUpdate() {
|
||
|
uni.$emit('im:unreadCountUpdate', Object.fromEntries(this.unreadCount));
|
||
|
}
|
||
|
|
||
|
emitLastMessageUpdate() {
|
||
|
uni.$emit('im:lastMessageUpdate', Object.fromEntries(this.lastMessage));
|
||
|
}
|
||
|
|
||
|
// 注册联系人列表更新监听
|
||
|
onContactListUpdate(callback) {
|
||
|
uni.$on('im:contactListUpdate', callback);
|
||
|
}
|
||
|
|
||
|
// 注册未读消息数更新监听
|
||
|
onUnreadCountUpdate(callback) {
|
||
|
uni.$on('im:unreadCountUpdate', callback);
|
||
|
}
|
||
|
|
||
|
// 注册最后消息更新监听
|
||
|
onLastMessageUpdate(callback) {
|
||
|
uni.$on('im:lastMessageUpdate', callback);
|
||
|
}
|
||
|
|
||
|
// 移除监听器
|
||
|
offContactListUpdate(callback) {
|
||
|
uni.$off('im:contactListUpdate', callback);
|
||
|
}
|
||
|
|
||
|
offUnreadCountUpdate(callback) {
|
||
|
uni.$off('im:unreadCountUpdate', callback);
|
||
|
}
|
||
|
|
||
|
offLastMessageUpdate(callback) {
|
||
|
uni.$off('im:lastMessageUpdate', callback);
|
||
|
}
|
||
|
|
||
|
// 注册消息处理器
|
||
|
onMessage(handler) {
|
||
|
if (typeof handler === 'function') {
|
||
|
this.messageHandlers.add(handler);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 移除消息处理器
|
||
|
offMessage(handler) {
|
||
|
this.messageHandlers.delete(handler);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 创建单例
|
||
|
const im = new IMWebSocket();
|
||
|
export default im;
|