Browse Source

实现实时聊天功能,添加WebSocket支持

pull/12/head
徐飞洋 2 months ago
parent
commit
2486b3a4c0
  1. 172
      pages/chat-detail/index.js
  2. 2
      pages/cooperation/index.js
  3. 2
      pages/cooperation/index.wxml
  4. 246
      utils/socket.js

172
pages/chat-detail/index.js

@ -1,5 +1,6 @@
// pages/chat-detail/index.js
const API = require('../../utils/api.js');
const SocketIO = require('../../utils/socket.js');
Page({
// 分享给朋友/群聊
@ -65,7 +66,9 @@ Page({
console.error('聊天详情页面 - 未传递有效的聊天ID');
}
this.loadMessages(); // 使用默认值true,首次进入页面时自动滚动到底部
this.startTimer();
// 初始化Socket.IO连接
this.initSocketConnection();
},
onShow: function () {
@ -97,6 +100,120 @@ Page({
wx.navigateBack();
},
// 初始化Socket.IO连接
initSocketConnection: function () {
console.log('初始化Socket.IO连接');
// 获取当前用户的手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
if (userId && users[userId]) {
userPhone = users[userId].phoneNumber || users[userId].phone;
}
if (!userPhone) {
const userInfo = wx.getStorageSync('userInfo');
userPhone = userInfo ? (userInfo.phoneNumber || userInfo.phone) : null;
}
if (!userPhone) {
userPhone = wx.getStorageSync('phoneNumber') || wx.getStorageSync('phone');
}
if (!userPhone) {
console.warn('用户未登录,无法建立Socket.IO连接');
return;
}
const chatId = this.data.chatId;
if (!chatId) {
console.warn('聊天ID不存在,无法建立Socket.IO连接');
return;
}
// 初始化Socket.IO
SocketIO.initSocket(userPhone, chatId)
.then(() => {
console.log('Socket.IO连接成功');
// 添加消息接收事件监听器
SocketIO.on('message', (message) => {
this.handleSocketMessage(message);
});
})
.catch((error) => {
console.error('Socket.IO连接失败:', error);
// 连接失败时,回退到轮询模式
console.log('回退到轮询模式');
this.startTimer();
});
},
// 处理Socket.IO消息
handleSocketMessage: function (message) {
console.log('收到Socket.IO消息:', message);
// 获取当前用户的手机号
const users = wx.getStorageSync('users') || {};
const userId = wx.getStorageSync('userId');
let userPhone = null;
if (userId && users[userId]) {
userPhone = users[userId].phoneNumber || users[userId].phone;
}
if (!userPhone) {
const userInfo = wx.getStorageSync('userInfo');
userPhone = userInfo ? (userInfo.phoneNumber || userInfo.phone) : null;
}
if (!userPhone) {
userPhone = wx.getStorageSync('phoneNumber') || wx.getStorageSync('phone');
}
// 处理消息格式
const sender = (message.sender_phone === userPhone) ? 'me' : 'other';
const processedMessage = {
id: message.id || Date.now(),
content: message.content,
goodsData: message.goodsData,
sender: sender,
time: this.formatDateTime(message.created_at || new Date().toISOString()),
originalTime: message.created_at || new Date().toISOString(),
isNew: true
};
// 更新消息列表
const newMessages = [...this.data.messages, processedMessage];
this.setData({
messages: newMessages
}, () => {
// 检查是否需要滚动到底部
if (this.data.isAtBottom) {
this.scrollToBottom(true);
} else {
// 显示新消息提示
this.setData({
hasNewMessages: true,
newMessageCount: this.data.newMessageCount + 1
});
}
// 短暂延迟后移除isNew标记
setTimeout(() => {
if (this.isUnloaded) return;
const updatedMessages = this.data.messages.map(msg => ({
...msg,
isNew: false
}));
this.setData({ messages: updatedMessages });
}, 300);
});
},
// 格式化时间显示
formatDateTime: function (dateString) {
if (!dateString) return '刚刚';
@ -459,7 +576,46 @@ Page({
}, 300);
});
// 发送到服务器
// 消息数据
const messageData = {
sender_phone: userPhone,
receiver_phone: managerPhone,
content: content
};
// 优先使用Socket.IO发送消息
if (SocketIO.isConnected()) {
console.log('使用Socket.IO发送消息');
SocketIO.sendMessage(messageData)
.then(res => {
// 检查页面是否已卸载
if (this.isUnloaded) {
console.log('页面已卸载,忽略消息发送结果');
return;
}
console.log('Socket.IO消息发送成功:', res);
})
.catch(error => {
// 检查页面是否已卸载
if (this.isUnloaded) {
console.log('页面已卸载,忽略消息发送错误');
return;
}
console.error('Socket.IO消息发送失败,回退到HTTP请求:', error);
// Socket.IO发送失败,回退到HTTP请求
this.sendHttpMessage(userPhone, managerPhone, content);
});
} else {
// Socket.IO未连接,使用HTTP请求
console.log('Socket.IO未连接,使用HTTP请求发送消息');
this.sendHttpMessage(userPhone, managerPhone, content);
}
},
// 使用HTTP请求发送消息(回退方法)
sendHttpMessage: function (userPhone, managerPhone, content) {
API.sendMessage(userPhone, managerPhone, content).then(res => {
// 检查页面是否已卸载
if (this.isUnloaded) {
@ -467,7 +623,7 @@ Page({
return;
}
console.log('消息发送成功:', res);
console.log('HTTP消息发送成功:', res);
// 发送消息成功后,立即将该聊天标记为已查看
const viewedChats = wx.getStorageSync('viewedChats') || {};
@ -489,7 +645,7 @@ Page({
return;
}
console.error('消息发送失败:', error);
console.error('HTTP消息发送失败:', error);
wx.showToast({
title: '消息发送失败',
icon: 'none'
@ -517,6 +673,10 @@ Page({
clearInterval(this.data.timer);
this.setData({ timer: null });
}
// 页面隐藏时,断开Socket.IO连接
console.log('页面隐藏,断开Socket.IO连接');
SocketIO.disconnect();
},
onUnload: function () {
@ -529,6 +689,10 @@ Page({
this.setData({ timer: null });
}
// 页面卸载时,断开Socket.IO连接
console.log('页面卸载,断开Socket.IO连接');
SocketIO.disconnect();
// 用户退出页面时,自动将该聊天标记为已查看
const managerPhone = this.data.managerPhone;
if (managerPhone) {

2
pages/cooperation/index.js

@ -86,7 +86,7 @@ Page({
*/
makePhoneCall: function () {
wx.makePhoneCall({
phoneNumber: '18280108971',
phoneNumber: '19381602346',
success: function () {
console.log('拨打电话成功');
},

2
pages/cooperation/index.wxml

@ -52,7 +52,7 @@
<view class="contact-info">
<view class="contact-item">
<view class="contact-label">联系电话:</view>
<view class="contact-value" bindtap="makePhoneCall">18280108971</view>
<view class="contact-value" bindtap="makePhoneCall">19381602346</view>
</view>
<view class="contact-item">
<view class="contact-label">联系地址:</view>

246
utils/socket.js

@ -0,0 +1,246 @@
// WebSocket实时通信工具
// 专为小程序设计的WebSocket封装,模拟Socket.IO功能
// 导入配置
const API = require('./api.js');
// WebSocket连接实例
let socket = null;
let isConnecting = false;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 3000; // 重连延迟时间(毫秒)
// 事件监听器存储
const eventListeners = {};
// 存储当前连接的用户信息
let currentUserPhone = null;
let currentChatId = null;
// 获取WebSocket服务器URL
function getSocketUrl() {
// 从API的BASE_URL获取服务器地址
// 注意:小程序中无法直接获取API的BASE_URL,需要根据环境设置
const baseUrl = API.isTestMode() ? API.SERVER_CONFIG.DEFAULT_LOCAL_IP : API.SERVER_CONFIG.PRODUCTION;
// 替换http为ws,https为wss
return baseUrl.replace(/^http/, 'ws');
}
// 初始化WebSocket连接
function initSocket(userPhone, chatId) {
return new Promise((resolve, reject) => {
// 存储当前用户信息
currentUserPhone = userPhone;
currentChatId = chatId;
// 如果已经连接,直接返回
if (socket && socket.connected) {
console.log('WebSocket已经连接');
resolve(socket);
return;
}
// 如果正在连接中,等待连接完成
if (isConnecting) {
console.log('WebSocket正在连接中...');
// 等待连接完成
const checkConnection = setInterval(() => {
if (socket && socket.connected) {
clearInterval(checkConnection);
resolve(socket);
} else if (!isConnecting) {
clearInterval(checkConnection);
reject(new Error('WebSocket连接失败'));
}
}, 100);
return;
}
isConnecting = true;
reconnectAttempts = 0;
try {
const socketUrl = getSocketUrl();
const fullUrl = `${socketUrl}?user_phone=${userPhone}&chat_id=${chatId}`;
console.log('正在连接WebSocket服务器:', fullUrl);
// 创建WebSocket连接
socket = wx.connectSocket({
url: fullUrl,
header: {
'content-type': 'application/json'
},
protocols: ['chat'],
success: () => {
console.log('WebSocket连接创建请求发送成功');
},
fail: (error) => {
console.error('WebSocket连接创建失败:', error);
isConnecting = false;
reject(error);
}
});
// 连接成功事件
socket.onOpen(() => {
console.log('WebSocket连接成功');
socket.connected = true;
isConnecting = false;
reconnectAttempts = 0;
resolve(socket);
});
// 连接错误事件
socket.onError((error) => {
console.error('WebSocket连接错误:', error);
socket.connected = false;
isConnecting = false;
reject(error);
});
// 断开连接事件
socket.onClose((res) => {
console.log('WebSocket断开连接:', res);
socket.connected = false;
isConnecting = false;
// 如果不是手动断开(code !== 1000),尝试重连
if (res.code !== 1000) {
reconnect();
}
});
// 接收消息事件
socket.onMessage((res) => {
console.log('收到WebSocket消息:', res);
try {
const message = JSON.parse(res.data);
// 触发对应类型的事件
if (message.type) {
emitEvent(message.type, message.data);
} else {
// 兼容旧格式消息
emitEvent('message', message);
}
} catch (error) {
console.error('消息解析失败:', error);
}
});
} catch (error) {
console.error('初始化WebSocket失败:', error);
isConnecting = false;
reject(error);
}
});
}
// 重连函数
function reconnect() {
if (reconnectAttempts >= maxReconnectAttempts) {
console.error('WebSocket重连次数超过限制,停止重连');
return;
}
reconnectAttempts++;
console.log(`WebSocket正在重连,尝试次数: ${reconnectAttempts}/${maxReconnectAttempts}`);
// 延迟重连
setTimeout(() => {
if (currentUserPhone && currentChatId) {
initSocket(currentUserPhone, currentChatId)
.then(() => {
console.log('WebSocket重连成功');
})
.catch((error) => {
console.error('WebSocket重连失败:', error);
});
}
}, reconnectDelay);
}
// 发送消息
function sendMessage(messageData) {
return new Promise((resolve, reject) => {
if (!socket || !socket.connected) {
reject(new Error('WebSocket未连接'));
return;
}
try {
// 构造消息格式,模拟Socket.IO的事件机制
const message = {
type: 'chat message',
data: messageData
};
socket.send({
data: JSON.stringify(message),
success: () => {
console.log('WebSocket消息发送成功');
resolve({ success: true });
},
fail: (error) => {
console.error('WebSocket消息发送失败:', error);
reject(error);
}
});
} catch (error) {
console.error('发送WebSocket消息失败:', error);
reject(error);
}
});
}
// 断开连接
function disconnect() {
if (socket) {
console.log('手动断开WebSocket连接');
socket.close(1000, 'normal closure');
socket = null;
}
isConnecting = false;
reconnectAttempts = 0;
}
// 注册事件监听器
function on(eventName, callback) {
if (!eventListeners[eventName]) {
eventListeners[eventName] = [];
}
eventListeners[eventName].push(callback);
}
// 移除事件监听器
function off(eventName, callback) {
if (eventListeners[eventName]) {
if (callback) {
eventListeners[eventName] = eventListeners[eventName].filter(cb => cb !== callback);
} else {
delete eventListeners[eventName];
}
}
}
// 触发事件
function emitEvent(eventName, data) {
if (eventListeners[eventName]) {
eventListeners[eventName].forEach(callback => {
try {
callback(data);
} catch (error) {
console.error('事件监听器执行错误:', error);
}
});
}
}
// 导出WebSocket工具
module.exports = {
initSocket,
sendMessage,
disconnect,
on,
off,
isConnected: () => socket && socket.connected
};
Loading…
Cancel
Save