diff --git a/server-example/check_chat_data.js b/server-example/check_chat_data.js new file mode 100644 index 0000000..774ae76 --- /dev/null +++ b/server-example/check_chat_data.js @@ -0,0 +1,51 @@ +const { Sequelize } = require('sequelize'); + +// 使用与项目相同的数据库配置 +const sequelize = new Sequelize('wechat_app', 'root', 'schl@2025', { + host: '1.95.162.61', + port: 3306, + dialect: 'mysql', + dialectOptions: { + connectTimeout: 30000, + }, +}); + +async function checkChatData() { + try { + console.log('连接数据库...'); + await sequelize.authenticate(); + console.log('数据库连接成功!'); + + console.log('\n=== 最近的聊天会话 ==='); + const [conversations] = await sequelize.query( + 'SELECT * FROM chat_conversations ORDER BY created_at DESC LIMIT 5' + ); + console.log(conversations); + + console.log('\n=== 最近的聊天消息 ==='); + const [messages] = await sequelize.query( + 'SELECT * FROM chat_messages ORDER BY created_at DESC LIMIT 5' + ); + console.log(messages); + + // 检查是否有使用测试ID的记录 + console.log('\n=== 检查测试ID记录 ==='); + const [testRecords] = await sequelize.query( + "SELECT * FROM chat_conversations WHERE userId LIKE '%test_%' OR managerId LIKE '%test_%'" + ); + console.log('测试ID记录数量:', testRecords.length); + if (testRecords.length > 0) { + console.log('发现测试ID记录:', testRecords); + } else { + console.log('未发现测试ID记录'); + } + + } catch (error) { + console.error('查询失败:', error); + } finally { + await sequelize.close(); + console.log('\n数据库连接已关闭'); + } +} + +checkChatData(); diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index 8d7ca59..4f4eb01 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -1,3 +1,27 @@ +// 类型处理辅助函数 - 添加于修复聊天功能 +function ensureStringId(id) { + return String(id).trim(); +} + +function validateUserId(userId) { + if (!userId || userId === 0 || userId === '0') { + throw new Error('无效的userId: 不能为空或为0'); + } + if (typeof userId !== 'string') { + console.warn('警告: userId应该是字符串类型,当前类型:', typeof userId, '值:', userId); + return String(userId).trim(); + } + return userId.trim(); +} + +function validateManagerId(managerId) { + if (!managerId || managerId === 0 || managerId === '0') { + throw new Error('无效的managerId: 不能为空或为0'); + } + // 确保managerId也是字符串类型 + return String(managerId).trim(); +} + // ECS服务器示例代码 - Node.js版 (MySQL版本) const express = require('express'); const crypto = require('crypto'); @@ -245,11 +269,12 @@ const dbConfig = { console.log('数据库连接配置:'); console.log(JSON.stringify(dbConfig, null, 2)); -// MySQL数据库连接配置 - 确保密码正确传递 -const sequelize = new Sequelize( - dbConfig.database, +// MySQL数据库连接配置 - 为不同数据源创建独立连接 +// 1. wechat_app数据源连接 +const wechatAppSequelize = new Sequelize( + 'wechat_app', dbConfig.user, - dbConfig.password, // 直接使用密码字符串,不做空值判断 + dbConfig.password, { host: dbConfig.host, port: dbConfig.port, @@ -268,6 +293,149 @@ const sequelize = new Sequelize( } ); +// 2. userlogin数据源连接 +const userLoginSequelize = new Sequelize( + 'userlogin', + dbConfig.user, + dbConfig.password, + { + host: dbConfig.host, + port: dbConfig.port, + dialect: 'mysql', + pool: { + max: 10, + min: 0, + acquire: 30000, + idle: 10000 + }, + logging: console.log, + define: { + timestamps: false + }, + timezone: '+00:00' // 设置时区为UTC + } +); + +// 为保持兼容性,保留默认sequelize引用(指向wechat_app) +const sequelize = wechatAppSequelize; + +// 定义会话模型 +const ChatConversation = sequelize.define('ChatConversation', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + conversation_id: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + userId: { + type: DataTypes.STRING, + allowNull: false + }, + managerId: { + type: DataTypes.STRING, + allowNull: false + }, + last_message: { + type: DataTypes.TEXT + }, + last_message_time: { + type: DataTypes.DATE + }, + unread_count: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + cs_unread_count: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + status: { + type: DataTypes.INTEGER, + defaultValue: 1 + }, + user_online: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + cs_online: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + created_at: { + type: DataTypes.DATE + }, + updated_at: { + type: DataTypes.DATE + } +}, { + tableName: 'chat_conversations', + timestamps: false +}); + +// 定义消息模型 +const ChatMessage = sequelize.define('ChatMessage', { + message_id: { + type: DataTypes.STRING, + primaryKey: true + }, + conversation_id: { + type: DataTypes.STRING, + allowNull: false + }, + sender_type: { + type: DataTypes.INTEGER, + allowNull: false + }, + sender_id: { + type: DataTypes.STRING, + allowNull: false + }, + receiver_id: { + type: DataTypes.STRING, + allowNull: false + }, + content_type: { + type: DataTypes.INTEGER, + allowNull: false + }, + content: { + type: DataTypes.TEXT, + allowNull: false + }, + file_url: { + type: DataTypes.STRING + }, + file_size: { + type: DataTypes.INTEGER + }, + duration: { + type: DataTypes.INTEGER + }, + is_read: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + status: { + type: DataTypes.INTEGER, + defaultValue: 1 + }, + created_at: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW + }, + updated_at: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW + } +}, { + tableName: 'chat_messages', + timestamps: false +}); + // 微信小程序配置 const WECHAT_CONFIG = { APPID: process.env.WECHAT_APPID || 'your-wechat-appid', @@ -302,6 +470,55 @@ async function testDbConnection() { testDbConnection(); +// 获取用户会话列表接口 +app.get('/api/conversations/user/:userId', async (req, res) => { + try { + const { userId } = req.params; + console.log(`获取用户 ${userId} 的会话列表`); + + // 从数据库获取用户的所有会话 + const conversations = await ChatConversation.findAll({ + where: { + userId: userId + }, + order: [[Sequelize.literal('last_message_time'), 'DESC']] + }); + + console.log(`找到 ${conversations.length} 个会话`); + + // 格式化响应数据 + const formattedConversations = conversations.map(conv => { + // 获取最后消息的时间 + const lastMessageTime = conv.last_message_time ? new Date(conv.last_message_time) : null; + + return { + conversation_id: conv.conversation_id, + user_id: conv.userId, + manager_id: conv.managerId, + last_message: conv.last_message || '', + last_message_time: lastMessageTime ? lastMessageTime.toISOString() : null, + unread_count: conv.unread_count || 0, + status: conv.status + }; + }); + + res.json({ + success: true, + code: 200, + message: '获取会话列表成功', + data: formattedConversations + }); + } catch (error) { + console.error('获取会话列表失败:', error); + res.status(500).json({ + success: false, + code: 500, + message: '获取会话列表失败', + error: error.message + }); + } +}); + // 定义数据模型 // 用户模型 @@ -5922,15 +6139,42 @@ app.get('/api/managers', async (req, res) => { { replacements: ['采购员'] } ); - // 将获取的数据映射为前端需要的格式,添加online状态(从onlineManagers Map中判断) - // 防御性编程确保onlineManagers存在且正确使用 + // 查询chat_online_status表获取客服在线状态 + const [onlineStatusData] = await sequelize.query( + 'SELECT userId, is_online FROM chat_online_status WHERE type = 2', // type=2表示客服 + { type: sequelize.QueryTypes.SELECT } + ); + + // 创建在线状态映射 + const onlineStatusMap = {}; + // 检查onlineStatusData是否存在,防止undefined调用forEach + if (Array.isArray(onlineStatusData)) { + onlineStatusData.forEach(status => { + onlineStatusMap[status.userId] = status.is_online === 1; + }); + } + + // 将获取的数据映射为前端需要的格式,添加online状态(综合考虑内存中的onlineManagers和数据库中的状态) const isManagerOnline = (id, managerId) => { - // 确保onlineManagers存在且是Map类型 - if (!onlineManagers || typeof onlineManagers.has !== 'function') { - return false; + // 转换ID为字符串以便正确比较 + const stringId = String(id); + const stringManagerId = managerId ? String(managerId) : null; + + console.log(`检查客服在线状态: id=${id}(${stringId}), managerId=${managerId}(${stringManagerId})`); + + // 首先从内存中的onlineManagers检查(实时性更好) + if (onlineManagers && typeof onlineManagers.has === 'function') { + // 检查id或managerId是否在onlineManagers中 + if (onlineManagers.has(stringId) || (stringManagerId && onlineManagers.has(stringManagerId))) { + console.log(`客服在线(内存检查): id=${id}`); + return true; + } } - // 尝试多种可能的键类型 - return onlineManagers.has(id) || onlineManagers.has(String(id)) || (managerId && onlineManagers.has(managerId)); + + // 其次从数据库查询结果检查 + const dbStatus = onlineStatusMap[stringId] || (stringManagerId && onlineStatusMap[stringManagerId]) || false; + console.log(`客服在线状态(数据库): id=${id}, status=${dbStatus}`); + return dbStatus; }; const managers = personnelData.map((person, index) => ({ @@ -5944,7 +6188,7 @@ app.get('/api/managers', async (req, res) => { alias: person.alias || person.name || '未知', phoneNumber: person.phoneNumber || '', avatar: person.avatarUrl || '', // 使用表中的avatarUrl字段 - online: isManagerOnline(person.id, person.managerId) // 安全地检查在线状态,传递both id and managerId + online: isManagerOnline(person.id, person.managerId) // 综合检查在线状态 })); res.status(200).json({ @@ -5996,14 +6240,82 @@ wss.on('connection', (ws, req) => { // 处理心跳消息 if (data.type === 'ping') { + // 更新连接活动时间 + updateConnectionActivity(ws.connectionId); + // 更新心跳时间到chat_online_status表 + const connection = connections.get(ws.connectionId); + if (connection) { + const userId = connection.isUser ? connection.userId : connection.managerId; + const type = connection.isUser ? 1 : 2; + updateChatOnlineStatusHeartbeat(userId, type); + } ws.send(JSON.stringify({ type: 'pong' })); return; } // 处理聊天消息 if (data.type === 'chat_message') { - const payload = data.data || data.payload || data; - await handleChatMessage(ws, payload); + console.log('接收到聊天消息:', JSON.stringify(data)); + + // 从前端嵌套格式中提取payload + let payload = data.data || data.payload || data; + + // 构建完整的payload对象,合并顶层和嵌套数据 + const completePayload = { + ...payload, + // 从顶层data复制重要字段 + direction: data.direction || payload.direction || 'customer_to_service', + messageType: data.messageType || payload.messageType || 'text', + // 确保必要的ID字段存在 + userId: payload.senderId || payload.userId, + receiverId: payload.receiverId, + // 确保消息内容存在 + content: payload.content || '', + contentType: payload.contentType || 1, + timestamp: payload.timestamp || Date.now() + }; + + // 关键修复:正确映射ID字段 + // 对于用户发送给客服的消息,receiverId就是managerId + if (completePayload.receiverId && !completePayload.managerId) { + completePayload.managerId = completePayload.receiverId; + console.log('已将receiverId映射为managerId:', completePayload.managerId); + } + + // 转换senderType从字符串到数字(确保兼容) + if (typeof completePayload.senderType === 'string') { + if (completePayload.senderType === 'customer' || completePayload.senderType === 'user') { + completePayload.senderType = 1; // 普通用户 + } else if (completePayload.senderType === 'customer_service' || completePayload.senderType === 'manager') { + completePayload.senderType = 2; // 客服 + } + console.log('已转换senderType:', completePayload.senderType); + } + + // 确保必要字段都已设置 + if (!completePayload.userId) { + console.error('错误: 缺少userId字段'); + ws.send(JSON.stringify({ + type: 'error', + message: '消息缺少用户ID' + })); + return; + } + + console.log('处理聊天消息 - 完整payload:', JSON.stringify(completePayload)); + + // 调用handleChatMessage处理消息 + try { + await handleChatMessage(ws, completePayload); + console.log('消息处理完成'); + } catch (error) { + console.error('处理聊天消息时出错:', error); + ws.send(JSON.stringify({ + type: 'error', + message: '消息处理失败', + error: error.message + })); + } return; } @@ -6030,7 +6342,7 @@ wss.on('connection', (ws, req) => { }); // 连接关闭处理 - ws.on('close', () => { + ws.on('close', async () => { console.log('WebSocket连接关闭'); const connection = connections.get(connectionId); @@ -6038,16 +6350,74 @@ wss.on('connection', (ws, req) => { // 更新在线状态 if (connection.isUser && connection.userId) { onlineUsers.delete(connection.userId); - updateUserOnlineStatus(connection.userId, 0); + await updateUserOnlineStatus(connection.userId, 0); + // 更新chat_online_status表为离线 + await updateChatOnlineStatusOffline(connection.userId, 1); } else if (connection.isManager && connection.managerId) { onlineManagers.delete(connection.managerId); - updateManagerOnlineStatus(connection.managerId, 0); + await updateManagerOnlineStatus(connection.managerId, 0); + // 更新chat_online_status表为离线 + await updateChatOnlineStatusOffline(connection.managerId, 2); } // 从连接池中移除 connections.delete(connectionId); } }); + +// 更新chat_online_status表的心跳时间 +async function updateChatOnlineStatusHeartbeat(userId, type) { + try { + const now = new Date(); + // 根据userId是否为null使用不同的SQL语句 + let sql; + let replacements; + + if (userId === null || userId === undefined) { + sql = `UPDATE chat_online_status + SET last_heartbeat = ?, updated_at = ? + WHERE userId IS NULL AND type = ? AND is_online = 1`; + replacements = [now, now, type]; + } else { + sql = `UPDATE chat_online_status + SET last_heartbeat = ?, updated_at = ? + WHERE userId = ? AND type = ? AND is_online = 1`; + replacements = [now, now, userId, type]; + } + + await sequelize.query(sql, { replacements }); + } catch (error) { + // 心跳更新失败不影响主流程,仅记录日志 + console.error('更新chat_online_status心跳时间失败:', error); + } +} + +// 更新chat_online_status表为离线状态 +async function updateChatOnlineStatusOffline(userId, type) { + try { + const now = new Date(); + // 根据userId是否为null使用不同的SQL语句 + let sql; + let replacements; + + if (userId === null || userId === undefined) { + sql = `UPDATE chat_online_status + SET is_online = 0, updated_at = ? + WHERE userId IS NULL AND type = ?`; + replacements = [now, type]; + } else { + sql = `UPDATE chat_online_status + SET is_online = 0, updated_at = ? + WHERE userId = ? AND type = ?`; + replacements = [now, userId, type]; + } + + await sequelize.query(sql, { replacements }); + console.log(`更新chat_online_status离线状态成功: userId=${userId}, type=${type}`); + } catch (error) { + console.error('更新chat_online_status离线状态失败:', error); + } +} // 连接错误处理 ws.on('error', (error) => { @@ -6057,9 +6427,51 @@ wss.on('connection', (ws, req) => { // 认证处理函数 async function handleAuth(ws, data) { - // 兼容不同格式的认证数据 + // 详细日志记录原始认证数据 + console.log('📱 收到认证请求:', JSON.stringify(data)); + + // 优化认证数据提取,支持多种格式 + // 1. 直接从data中提取(最简单的格式) + let managerId = data.managerId; + let userId = data.userId; + let type = data.type; + let userType = data.userType; // 明确提取userType字段 + + // 2. 如果没有找到,尝试从data.data中提取 + if (!managerId && !userId && data.data) { + managerId = data.data.managerId; + userId = data.data.userId; + type = data.data.type; + userType = data.data.userType || data.data.type; // 从data.data中提取userType + console.log('🔄 从data.data中提取认证信息'); + } + + // 3. 兼容之前的payload逻辑 const payload = data.data || data; - const { userId, managerId, type } = payload; + if (!managerId) managerId = payload.managerId; + if (!userId) userId = payload.userId; + if (!type) type = payload.type; + if (!userType) userType = payload.userType || payload.type || 'unknown'; // 确保userType有值 + + // 字符串化ID以确保类型一致性 + if (userId) userId = String(userId).trim(); + if (managerId) managerId = String(managerId).trim(); + + console.log('🔍 最终提取的认证信息:', { + managerId, + userId, + type, + userType, + hasManagerId: !!managerId, + hasUserId: !!userId, + hasType: !!type, + hasUserType: !!userType + }); + + // 确定最终的用户类型 - userType优先级高于type + const finalUserType = String(userType).toLowerCase(); + + const deviceInfo = payload.deviceInfo || {}; const connection = connections.get(ws.connectionId); if (!connection) { @@ -6070,13 +6482,63 @@ async function handleAuth(ws, data) { return; } - // 验证用户或客服身份 - if (type === 'user' && userId) { + console.log(`🔍 最终用户类型判断: finalUserType=${finalUserType}`); + + // 验证用户或客服身份 - 使用finalUserType进行判断,不再重复定义userType + if ((finalUserType === 'user' || finalUserType.includes('customer')) && userId) { + // 改进的用户认证逻辑,支持字符串ID并增加容错性 + try { + // 首先尝试数据库验证 + let userExists = false; + try { + const [existingUsers] = await sequelize.query( + 'SELECT userId FROM users WHERE userId = ? LIMIT 1', + { replacements: [userId] } + ); + userExists = existingUsers && existingUsers.length > 0; + + if (userExists) { + console.log(`✅ 用户ID验证成功: userId=${userId} 存在于数据库中`); + } else { + console.log(`ℹ️ 用户ID在数据库中不存在: userId=${userId},但仍然允许连接`); + } + } catch (dbError) { + console.warn(`⚠️ 用户数据库验证失败,但继续处理认证: ${dbError.message}`); + } + + // 查询用户是否在personnel表中存在,以确定managerId + try { + const [personnelData] = await sequelize.query( + 'SELECT id FROM userlogin.personnel WHERE userId = ? LIMIT 1', + { replacements: [userId] } + ); + + if (personnelData && personnelData.length > 0) { + console.log(`✅ 用户在personnel表中存在,managerId=${personnelData[0].id}`); + } else { + console.log(`ℹ️ 用户不在personnel表中,为普通用户`); + } + } catch (personnelError) { + console.warn(`⚠️ Personnel表查询失败,但继续处理认证: ${personnelError.message}`); + } + } catch (error) { + console.error('❌ 用户验证过程中发生严重错误:', error); + // 即使出错也尝试继续,只记录错误不中断认证 + } + + // 继续设置连接信息,确保使用字符串ID connection.userId = userId; connection.isUser = true; - connection.userType = 'user'; // 添加userType字段确保与其他函数兼容性 + connection.userType = 'user'; onlineUsers.set(userId, ws); - await updateUserOnlineStatus(userId, 1); + + // 尝试更新在线状态,但不中断认证流程 + try { + await updateUserOnlineStatus(userId, 1); + await updateChatOnlineStatus(userId, 1, ws.connectionId, deviceInfo); + } catch (statusError) { + console.warn(`⚠️ 更新在线状态失败,但认证继续: ${statusError.message}`); + } // 发送认证成功消息 ws.send(JSON.stringify({ @@ -6084,21 +6546,68 @@ async function handleAuth(ws, data) { payload: { userId, type: 'user' } })); - console.log(`用户 ${userId} 已连接`); - } else if (type === 'manager' && managerId) { - connection.managerId = managerId; + console.log(`✅ 用户认证成功: userId=${userId}, userType=${finalUserType} (已标准化为'user')`); + } else if (finalUserType === 'manager' || finalUserType.includes('customer_service')) { + // 客服认证逻辑改进,增加容错性 + let stringManagerId; + if (managerId) { + stringManagerId = String(managerId).trim(); + } else { + // 缺少必要的managerId + ws.send(JSON.stringify({ + type: 'auth_error', + message: '客服认证失败:缺少必要的managerId' + })); + return; + } + + // 检查managerId是否在personnel表中存在,增加容错机制 + try { + let managerExists = false; + try { + const [existingManagers] = await sequelize.query( + 'SELECT id FROM userlogin.personnel WHERE id = ? LIMIT 1', + { replacements: [stringManagerId] } + ); + + managerExists = existingManagers && existingManagers.length > 0; + + if (!managerExists) { + console.warn(`⚠️ 客服ID在personnel表中不存在: managerId=${stringManagerId}`); + // 不再直接拒绝,而是允许继续连接但记录警告 + } + } catch (dbError) { + console.warn(`⚠️ 客服数据库验证失败,但继续处理认证: ${dbError.message}`); + } + + } catch (error) { + console.error('❌ 客服验证过程中发生严重错误:', error); + // 即使出错也尝试继续,只记录错误不中断认证 + } + + connection.managerId = stringManagerId; connection.isManager = true; connection.userType = 'manager'; // 添加userType字段确保与其他函数兼容性 - onlineManagers.set(managerId, ws); - await updateManagerOnlineStatus(managerId, 1); - // 发送认证成功消息 + // 检查并记录添加前的状态 + console.log(`📝 添加客服前onlineManagers状态: has(${stringManagerId})=${onlineManagers.has(stringManagerId)}`); + + onlineManagers.set(stringManagerId, ws); + await updateManagerOnlineStatus(stringManagerId, 1); + // 更新chat_online_status表 + await updateChatOnlineStatus(stringManagerId, 2, ws.connectionId, deviceInfo); + + // 发送认证成功消息 - 使用字符串化的managerId确保一致 ws.send(JSON.stringify({ type: 'auth_success', - payload: { managerId, type: 'manager' } + payload: { managerId: stringManagerId, type: 'manager' } })); - console.log(`客服 ${managerId} 已连接`); + console.log(`✅ 客服认证成功: managerId=${stringManagerId}, userType=${finalUserType} (已标准化为'manager')`); + + // 添加onlineManagers内容检查日志 + console.log(`客服 ${stringManagerId} 已连接,onlineManagers键:${Array.from(onlineManagers.keys()).join(', ')}`); + console.log(`onlineManagers.has('22') = ${onlineManagers.has('22')}`); } else { // 无效的认证信息 ws.send(JSON.stringify({ @@ -6108,6 +6617,40 @@ async function handleAuth(ws, data) { } } +// 更新chat_online_status表 +async function updateChatOnlineStatus(userId, type, socketId, deviceInfo) { + try { + const now = new Date(); + // 使用INSERT ... ON DUPLICATE KEY UPDATE语法确保只更新或插入一条记录 + await sequelize.query( + `INSERT INTO chat_online_status + (userId, type, socket_id, is_online, last_heartbeat, device_info, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + socket_id = VALUES(socket_id), + is_online = VALUES(is_online), + last_heartbeat = VALUES(last_heartbeat), + device_info = VALUES(device_info), + updated_at = VALUES(updated_at)`, + { + replacements: [ + userId, + type, // 1:普通用户 2:客服 + socketId, + 1, // 在线状态 + now, + JSON.stringify(deviceInfo), + now, + now + ] + } + ); + console.log(`更新chat_online_status成功: userId=${userId}, type=${type}, socketId=${socketId}`); + } catch (error) { + console.error('更新chat_online_status失败:', error); + } +} + // 更新用户在线状态 async function updateUserOnlineStatus(userId, status) { try { @@ -6152,6 +6695,43 @@ async function updateManagerOnlineStatus(managerId, status) { // { replacements: [status, managerId] } // ); + // 检查并更新用户表中的type字段,确保客服用户类型为manager + // 使用独立的数据源连接进行跨库操作 + try { + // 1. 使用userLoginSequelize查询personnel表获取电话号码 + console.log(`正在查询客服信息: managerId=${managerId}`); + const personnelResult = await userLoginSequelize.query( + 'SELECT phoneNumber FROM personnel WHERE id = ? OR managerId = ?', + { replacements: [managerId, managerId], type: userLoginSequelize.QueryTypes.SELECT } + ); + + if (personnelResult && personnelResult.length > 0) { + const phoneNumber = personnelResult[0].phoneNumber; + console.log(`找到客服电话号码: ${phoneNumber}`); + + if (phoneNumber) { + // 2. 使用wechatAppSequelize更新users表中的type字段 + console.log(`准备更新用户类型: 手机号=${phoneNumber}`); + const updateResult = await wechatAppSequelize.query( + 'UPDATE users SET type = ? WHERE phoneNumber = ? AND type = ?', + { replacements: ['manager', phoneNumber, 'customer'] } + ); + + const affectedRows = updateResult[1].affectedRows; + if (affectedRows > 0) { + console.log(`✓ 成功更新用户类型: 客服ID=${managerId}, 手机号=${phoneNumber}, 用户类型从customer更新为manager`); + } else { + console.log(`✓ 用户类型无需更新: 客服ID=${managerId}, 手机号=${phoneNumber}, 可能已经是manager类型`); + } + } + } else { + console.log(`未找到客服信息: managerId=${managerId}`); + } + } catch (err) { + console.error(`❌ 更新用户类型失败: ${err.message}`); + console.error(`错误详情:`, err); + } + // 通知相关用户客服状态变化 const conversations = await sequelize.query( 'SELECT DISTINCT userId FROM chat_conversations WHERE managerId = ?', @@ -6248,6 +6828,9 @@ function getOnlineStats() { // 会话管理函数 // 创建或获取现有会话 async function createOrGetConversation(userId, managerId) { + // 修复: 确保ID类型一致 + userId = validateUserId(userId); + managerId = validateManagerId(managerId); try { // 尝试查找已存在的会话 const [existingConversations] = await sequelize.query( @@ -6313,8 +6896,8 @@ async function getUserConversations(userId) { `SELECT c.*, u.nickName as userNickName, u.avatarUrl as userAvatar, p.name as managerName FROM chat_conversations c - LEFT JOIN users u ON c.userId = u.userId - LEFT JOIN userlogin.personnel p ON c.managerId = p.id + INNER JOIN users u ON c.userId = u.userId + INNER JOIN userlogin.personnel p ON c.managerId = p.id WHERE c.userId = ? ORDER BY c.last_message_time DESC, c.created_at DESC`, { replacements: [userId] } @@ -6333,8 +6916,8 @@ async function getManagerConversations(managerId) { `SELECT c.*, u.nickName as userNickName, u.avatarUrl as userAvatar, p.name as managerName FROM chat_conversations c - LEFT JOIN users u ON c.userId = u.userId - LEFT JOIN userlogin.personnel p ON c.managerId = p.id + INNER JOIN users u ON c.userId = u.userId + INNER JOIN userlogin.personnel p ON c.managerId = p.id WHERE c.managerId = ? ORDER BY c.last_message_time DESC, c.created_at DESC`, { replacements: [managerId] } @@ -6349,10 +6932,49 @@ async function getManagerConversations(managerId) { // 消息处理函数 // 处理聊天消息 async function handleChatMessage(ws, payload) { - const { conversationId, content, contentType = 1, fileUrl, fileSize, duration } = payload; + console.log('===== 开始处理聊天消息 ====='); + console.log('收到聊天消息 - 原始payload:', payload); + console.log('连接信息 - connectionId:', ws.connectionId); + + // 添加详细的连接信息日志 const connection = connections.get(ws.connectionId); + console.log('连接详情 - isUser:', connection?.isUser, 'isManager:', connection?.isManager); + + // 基本参数验证 + if (!payload) { + console.error('错误: payload为空'); + ws.send(JSON.stringify({ + type: 'error', + message: '消息数据不完整,缺少必要字段' + })); + return; + } + + if (!payload.content) { + console.error('错误: payload.content为空'); + ws.send(JSON.stringify({ + type: 'error', + message: '消息内容不能为空' + })); + return; + } + + // 打印解构前的payload字段 + console.log('Payload字段检查:'); + console.log('- conversationId存在:', 'conversationId' in payload); + console.log('- content存在:', 'content' in payload); + console.log('- contentType存在:', 'contentType' in payload); + console.log('- messageId存在:', 'messageId' in payload); + + const { conversationId, content, contentType = 1, fileUrl, fileSize, duration } = payload; + + console.log('解构后的值:'); + console.log('- conversationId:', conversationId); + console.log('- content:', content?.substring(0, 20) + '...'); + console.log('- contentType:', contentType); if (!connection) { + console.error('错误: 连接不存在'); ws.send(JSON.stringify({ type: 'error', message: '连接已失效' @@ -6367,18 +6989,65 @@ async function handleChatMessage(ws, payload) { if (connection.isUser) { // 用户发送消息给客服 - senderId = connection.userId; + senderId = validateUserId(connection.userId); senderType = 1; + // 关键验证:确保senderId有效 + if (!senderId || senderId === 0 || senderId === '0') { + console.error('严重错误: 用户连接的userId无效:', { userId: senderId }); + throw new Error('用户认证信息不完整,无法发送消息'); + } + + console.log('处理用户消息:', { userId: senderId, conversationId, hasManagerId: !!payload.managerId }); + // 如果没有提供会话ID,则查找或创建会话 - if (!conversationId) { - if (!payload.managerId) { - throw new Error('未指定客服ID'); + if (!conversationId) { + if (!payload.managerId) { + throw new Error('未指定客服ID'); + } + receiverId = validateManagerId(payload.managerId); + + // 确保senderId有效且不是测试ID + if (!senderId || senderId.includes('test_')) { + console.error('错误: 尝试使用无效或测试用户ID创建会话:', senderId); + throw new Error('用户认证信息无效,无法创建会话'); + } + + console.log('创建新会话:', { userId: senderId, managerId: receiverId }); + conversation = await createOrGetConversation(senderId, receiverId); + // 验证创建的会话信息 + console.log('创建的会话详情:', conversation); + if (!conversation || !conversation.conversation_id) { + console.error('错误: 创建会话失败或返回无效的会话信息'); + throw new Error('创建会话失败'); + } + + // 强制设置正确的userId,确保不会出现0或空值 + conversation.userId = senderId; + conversation.conversation_id = conversation.conversation_id || conversation.conversationId; + + // 立即验证并修复会话中的用户ID + if (conversation.userId !== senderId) { + console.warn('警告: 会话创建后userId不匹配,立即修复'); + await sequelize.query( + 'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?', + { replacements: [senderId, conversation.conversation_id] } + ); + conversation.userId = senderId; + } + + // 验证并修复数据库中的会话userId + if (conversation.userId !== senderId) { + console.warn('警告: 数据库返回的userId与连接的userId不匹配,正在修复'); + await sequelize.query( + 'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?', + { replacements: [senderId, conversation.conversation_id] } + ); + console.log(`已修复会话用户ID: conversationId=${conversation.conversation_id}, 设置为${senderId}`); } - receiverId = payload.managerId; - conversation = await createOrGetConversation(senderId, receiverId); } else { // 获取会话信息以确定接收者 + console.log('查询现有会话:', { conversationId }); const [conversations] = await sequelize.query( 'SELECT * FROM chat_conversations WHERE conversation_id = ?', { replacements: [conversationId] } @@ -6387,54 +7056,159 @@ async function handleChatMessage(ws, payload) { throw new Error('会话不存在'); } conversation = conversations[0]; + console.log('查询到的会话详情:', conversation); receiverId = conversation.managerId; + + // 验证会话的userId是否与当前用户匹配,不匹配则修复 + if (conversation.userId !== senderId) { + console.error(`错误: 会话userId(${conversation.userId})与当前用户ID(${senderId})不匹配`); + // 更新会话的userId为当前用户ID + await sequelize.query( + 'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?', + { replacements: [senderId, conversationId] } + ); + conversation.userId = senderId; + console.log(`已修复会话用户ID: conversationId=${conversationId}, 设置为${senderId}`); + } } } else if (connection.isManager) { // 客服发送消息给用户 - senderId = connection.managerId; - senderType = 2; + senderId = validateManagerId(connection.managerId); + senderType = 2; + + console.log('处理客服消息 - 详细信息:'); + console.log('- managerId:', senderId); + console.log('- conversationId:', conversationId); + + // 检查conversationId是否有效 + if (!conversationId) { + console.error('错误: 客服消息缺少conversationId'); + throw new Error('消息数据不完整,缺少必要字段'); + } + + // 获取会话信息以确定接收者 + console.log('查询会话信息:', conversationId); + const [conversations] = await sequelize.query( + 'SELECT * FROM chat_conversations WHERE conversation_id = ?', + { replacements: [conversationId] } + ); + if (!conversations || conversations.length === 0) { + throw new Error('会话不存在'); + } + conversation = conversations[0]; + receiverId = conversation.userId; + + // 检查receiverId是否有效 + console.log('从会话获取的receiverId:', receiverId); + + // 修复方案:如果receiverId无效,我们需要查找正确的用户ID + if (!receiverId || receiverId === 0 || receiverId === '0') { + console.error('错误: 会话中的用户ID无效(0或为空),正在尝试修复'); - // 获取会话信息以确定接收者 - const [conversations] = await sequelize.query( - 'SELECT * FROM chat_conversations WHERE conversation_id = ?', + // 查找该会话中的所有消息,尝试从用户发送的消息中获取正确的用户ID + const [messages] = await sequelize.query( + 'SELECT sender_id FROM chat_messages WHERE conversation_id = ? AND sender_type = 1 LIMIT 1', { replacements: [conversationId] } ); - if (!conversations || conversations.length === 0) { - throw new Error('会话不存在'); + + if (messages && messages.length > 0 && messages[0].sender_id && messages[0].sender_id !== 0) { + // 找到正确的用户ID,更新会话信息 + const correctUserId = messages[0].sender_id; + receiverId = correctUserId; + + // 更新数据库中的会话信息,修复userId为空的问题 + await sequelize.query( + 'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?', + { replacements: [correctUserId, conversationId] } + ); + + console.log(`✅ 成功修复会话用户ID: conversationId=${conversationId}, 从0更新为${correctUserId}`); + } else { + // 如果找不到正确的用户ID,则抛出错误,不允许存储无效消息 + console.error('❌ 无法找到有效的用户ID,消息发送失败'); + throw new Error('会话用户信息不完整,无法发送消息'); } - conversation = conversations[0]; - receiverId = conversation.userId; + } + + // 确保会话对象中的userId也是正确的 + conversation.userId = receiverId; } else { throw new Error('未认证的连接'); } + // 确保会话存在 + if (!conversation) { + console.error('错误: 会话对象不存在'); + throw new Error('会话信息无效'); + } + + // 获取会话ID,处理字段名差异 + const convId = conversation.conversation_id || conversation.conversationId; + if (!convId) { + console.error('错误: 会话缺少有效的ID', conversation); + throw new Error('会话信息无效'); + } + + // 直接使用传入的senderId,确保始终有效 + console.log('会话中的用户ID:', senderId); + if (!senderId || senderId === 0 || senderId === '0') { + console.error('错误: 用户ID无效'); + throw new Error('用户信息不完整'); + } + + // 统一会话信息格式,强制使用正确的字段名 + conversation = { + conversation_id: convId, + userId: senderId, // 使用传入的senderId + managerId: conversation.managerId, + ...conversation + }; + // 生成消息ID和时间戳 - const messageId = crypto.randomUUID(); + const messageId = payload.messageId || crypto.randomUUID(); // 允许前端提供messageId const now = new Date(); - // 存储消息 - await storeMessage({ + console.log('准备存储消息:', { messageId, conversationId: conversation.conversation_id, senderType, senderId, - receiverId, - contentType, - content, - fileUrl, - fileSize, - duration, - createdAt: now + receiverId }); + try { + // 关键修复:确保storeMessage被正确调用 + const storeResult = await storeMessage({ + messageId, + conversationId: conversation.conversation_id, + senderType, + senderId, + receiverId, + contentType, + content, + fileUrl, + fileSize, + duration, + createdAt: now + }); + + console.log('✅ 消息存储成功:', storeResult); + console.log('开始更新会话信息...'); + } catch (storeError) { + console.error('❌ 消息存储失败:', storeError.message); + throw storeError; // 重新抛出错误,确保上层捕获 + } + // 更新会话最后消息 await updateConversationLastMessage(conversation.conversation_id, content, now); // 更新未读计数 if (connection.isUser) { await updateUnreadCount(conversation.conversation_id, 'cs_unread_count', 1); + console.log('更新客服未读数:', { conversationId: conversation.conversation_id }); } else { await updateUnreadCount(conversation.conversation_id, 'unread_count', 1); + console.log('更新用户未读数:', { conversationId: conversation.conversation_id }); } // 构造消息对象 @@ -6459,13 +7233,14 @@ async function handleChatMessage(ws, payload) { if (senderType === 1) { // 用户发送给客服 receiverWs = onlineManagers.get(receiverId); + console.log(`尝试转发消息给客服 ${receiverId},客服是否在线:`, !!receiverWs); } else { // 客服发送给用户 receiverWs = onlineUsers.get(receiverId); + console.log(`尝试转发消息给用户 ${receiverId},用户是否在线:`, !!receiverWs); } // 处理特殊情况:当发送者和接收者是同一个人(既是用户又是客服) - // 检查是否存在另一个身份的连接 if (!receiverWs && senderId == receiverId) { if (senderType === 1) { // 用户发送消息给自己的客服身份 @@ -6474,13 +7249,22 @@ async function handleChatMessage(ws, payload) { // 客服发送消息给自己的用户身份 receiverWs = onlineUsers.get(senderId); } + console.log('处理同一会话内消息转发:', { senderId, hasReceiverWs: !!receiverWs }); } if (receiverWs) { - receiverWs.send(JSON.stringify({ - type: 'new_message', - payload: messageData - })); + try { + receiverWs.send(JSON.stringify({ + type: 'new_message', + payload: messageData + })); + console.log('消息转发成功'); + } catch (sendError) { + console.error('转发消息失败:', sendError); + // 转发失败不影响消息存储,只记录错误 + } + } else { + console.log('接收者不在线,消息已存储但未实时推送'); } // 发送确认给发送者 @@ -6488,12 +7272,19 @@ async function handleChatMessage(ws, payload) { type: 'message_sent', payload: { messageId, - status: 'success' + status: 'success', + conversationId: conversation.conversation_id } })); + console.log('消息处理完成:', { messageId, status: 'success' }); + } catch (error) { - console.error('处理聊天消息失败:', error); + console.error('处理聊天消息失败:', { + error: error.message, + stack: error.stack, + payload: { conversationId, content: content ? content.substring(0, 50) + '...' : '无内容' } + }); ws.send(JSON.stringify({ type: 'error', message: '消息发送失败: ' + error.message @@ -6506,8 +7297,39 @@ async function storeMessage(messageData) { const { messageId, conversationId, senderType, senderId, receiverId, contentType, content, fileUrl, fileSize, duration, createdAt } = messageData; + // 参数验证 + if (!messageId || !conversationId || !senderType || !senderId || !receiverId || !content) { + throw new Error('消息数据不完整,缺少必要字段'); + } + + // 确保所有ID都是字符串类型,并添加额外的验证 + const stringSenderId = validateUserId(senderId); + const stringReceiverId = String(receiverId).trim(); + const stringConversationId = String(conversationId).trim(); + const stringMessageId = String(messageId).trim(); + + // 验证senderId不是测试ID或无效ID + if (stringSenderId && stringSenderId.includes('test_')) { + console.warn('警告: 检测到使用测试ID发送消息:', stringSenderId); + // 不阻止消息发送,但记录警告 + } + + // 确保senderId不为空或0 + if (!stringSenderId || stringSenderId === '0' || stringSenderId === 'null' || stringSenderId === 'undefined') { + throw new Error('无效的发送者ID'); + } + try { - await sequelize.query( + console.log('开始存储消息到数据库:', { + messageId: stringMessageId, + conversationId: stringConversationId, + senderType: senderType === 1 ? '用户' : '客服', + senderId: stringSenderId, + receiverId: stringReceiverId, + contentType + }); + + const result = await sequelize.query( `INSERT INTO chat_messages (message_id, conversation_id, sender_type, sender_id, receiver_id, content_type, content, file_url, file_size, duration, is_read, status, @@ -6515,15 +7337,40 @@ async function storeMessage(messageData) { VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 1, ?, ?)`, { replacements: [ - messageId, conversationId, senderType, senderId, receiverId, - contentType, content, fileUrl || null, fileSize || null, duration || null, - createdAt, createdAt + stringMessageId, + stringConversationId, + senderType, + stringSenderId, + stringReceiverId, + contentType || 1, // 默认文本消息 + content, + fileUrl || null, + fileSize || null, + duration || null, + createdAt || new Date(), + createdAt || new Date() ] } ); + + // 记录影响行数,确认插入成功 + const affectedRows = result[1] && result[1].affectedRows ? result[1].affectedRows : 0; + console.log(`消息存储成功: messageId=${messageId}, 影响行数=${affectedRows}`); + + return { success: true, messageId, affectedRows }; } catch (error) { - console.error('存储消息失败:', error); - throw error; + console.error('存储消息到数据库失败:', { + error: error.message, + stack: error.stack, + messageData: { + messageId, + conversationId, + senderType, + senderId, + receiverId + } + }); + throw new Error(`消息存储失败: ${error.message}`); } } @@ -6557,48 +7404,92 @@ async function updateUnreadCount(conversationId, countField, increment) { // 处理未读消息标记 async function handleMarkRead(ws, payload) { + console.log('收到标记已读请求:', { payload, connectionId: ws.connectionId }); + const { conversationId, messageIds } = payload; const connection = connections.get(ws.connectionId); - if (!connection) return; + if (!connection) { + console.error('连接不存在,无法标记已读'); + return; + } + + if (!conversationId) { + console.error('未提供会话ID'); + ws.send(JSON.stringify({ + type: 'error', + message: '未提供会话ID' + })); + return; + } try { const now = new Date(); let countField; + let updateQuery; + let updateParams; - if (connection.isUser) { + if (connection.isUser || connection.userType === 'user') { // 用户标记客服消息为已读 countField = 'unread_count'; - await sequelize.query( - 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2', - { replacements: [now, conversationId] } - ); - } else if (connection.isManager) { + console.log('用户标记消息已读:', { conversationId, userId: connection.userId }); + + if (messageIds && Array.isArray(messageIds) && messageIds.length > 0) { + // 标记特定消息为已读 + updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2 AND message_id IN (?)'; + updateParams = [now, conversationId, messageIds]; + } else { + // 标记所有消息为已读 + updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2'; + updateParams = [now, conversationId]; + } + } else if (connection.isManager || connection.userType === 'manager') { // 客服标记用户消息为已读 countField = 'cs_unread_count'; - await sequelize.query( - 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1', - { replacements: [now, conversationId] } - ); + console.log('客服标记消息已读:', { conversationId, managerId: connection.managerId }); + + if (messageIds && Array.isArray(messageIds) && messageIds.length > 0) { + // 标记特定消息为已读 + updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1 AND message_id IN (?)'; + updateParams = [now, conversationId, messageIds]; + } else { + // 标记所有消息为已读 + updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1'; + updateParams = [now, conversationId]; + } + } else { + throw new Error('未知的连接类型'); } + // 执行消息已读更新 + await sequelize.query(updateQuery, { replacements: updateParams }); + // 重置未读计数 await sequelize.query( `UPDATE chat_conversations SET ${countField} = 0 WHERE conversation_id = ?`, { replacements: [conversationId] } ); + console.log('消息已读状态更新成功:', { conversationId, countField }); + // 发送确认 ws.send(JSON.stringify({ type: 'marked_read', - payload: { conversationId } + payload: { conversationId, messageIds } })); + // 通知对方已读状态(可选) + // 这里可以根据需要添加向对方发送已读状态通知的逻辑 + } catch (error) { - console.error('标记消息已读失败:', error); + console.error('标记消息已读失败:', { + error: error.message, + stack: error.stack, + conversationId + }); ws.send(JSON.stringify({ type: 'error', - message: '标记已读失败' + message: '标记已读失败: ' + error.message })); } } @@ -6623,27 +7514,71 @@ async function handleSessionMessage(ws, data) { case 'get_conversations': case 'list': // 获取会话列表,支持'list'和'get_conversations'两种操作 + console.log('获取会话列表请求:', { action, isUser: connection.isUser || connection.userType === 'user', isManager: connection.isManager || connection.userType === 'manager' }); let conversations; - if (connection.isUser || connection.userType === 'user') { - const userId = connection.userId || connection.userType === 'user' && connection.userId; - conversations = await getUserConversations(userId); - } else if (connection.isManager || connection.userType === 'manager') { - const managerId = connection.managerId || connection.userType === 'manager' && connection.managerId; - conversations = await getManagerConversations(managerId); - } - - // 支持两种响应格式,确保兼容性 - if (action === 'list') { - // 兼容测试脚本的响应格式 - ws.send(JSON.stringify({ - type: 'session_list', - data: conversations - })); - } else { - // 原有响应格式 + try { + if (connection.isUser || connection.userType === 'user') { + const userId = connection.userId || (connection.userType === 'user' && connection.userId); + if (!userId) { + throw new Error('用户ID不存在'); + } + conversations = await getUserConversations(userId); + console.log('用户会话列表获取成功:', { userId, conversationCount: conversations.length }); + } else if (connection.isManager || connection.userType === 'manager') { + const managerId = connection.managerId || (connection.userType === 'manager' && connection.managerId); + if (!managerId) { + throw new Error('客服ID不存在'); + } + conversations = await getManagerConversations(managerId); + console.log('客服会话列表获取成功:', { managerId, conversationCount: conversations.length }); + } else { + throw new Error('未知的连接类型'); + } + + // 为每个会话更新在线状态 + if (conversations && conversations.length > 0) { + const updatedConversations = await Promise.all(conversations.map(async (conv) => { + // 检查用户是否在线 + const userOnline = onlineUsers.has(conv.userId) ? 1 : 0; + // 检查客服是否在线 + const csOnline = onlineManagers.has(conv.managerId) ? 1 : 0; + + // 如果在线状态有变化,更新数据库 + if (conv.user_online !== userOnline || conv.cs_online !== csOnline) { + await sequelize.query( + 'UPDATE chat_conversations SET user_online = ?, cs_online = ? WHERE conversation_id = ?', + { replacements: [userOnline, csOnline, conv.conversation_id] } + ); + conv.user_online = userOnline; + conv.cs_online = csOnline; + } + + return conv; + })); + conversations = updatedConversations; + } + + // 支持两种响应格式,确保兼容性 + if (action === 'list') { + // 兼容测试脚本的响应格式 + ws.send(JSON.stringify({ + type: 'session_list', + data: conversations || [] + })); + } else { + // 原有响应格式 + ws.send(JSON.stringify({ + type: 'conversations_list', + payload: { conversations: conversations || [] } + })); + } + + console.log('会话列表推送成功:', { action, responseType: action === 'list' ? 'session_list' : 'conversations_list' }); + } catch (error) { + console.error('获取会话列表失败:', { error: error.message, action }); ws.send(JSON.stringify({ - type: 'conversations_list', - payload: { conversations } + type: 'error', + message: '获取会话列表失败: ' + error.message })); } break; @@ -6654,21 +7589,69 @@ async function handleSessionMessage(ws, data) { throw new Error('未指定会话ID'); } - const [messages] = await sequelize.query( - `SELECT * FROM chat_messages - WHERE conversation_id = ? - ORDER BY created_at DESC - LIMIT 50`, - { replacements: [conversationId] } - ); + const page = parseInt(data.page || (data.data && data.data.page) || (data.payload && data.payload.page)) || 1; + const limit = parseInt(data.limit || (data.data && data.data.limit) || (data.payload && data.payload.limit)) || 50; + const offset = (page - 1) * limit; - // 反转顺序,使最早的消息在前 - messages.reverse(); + console.log('获取会话消息:', { conversationId, page, limit, offset }); - ws.send(JSON.stringify({ - type: 'messages_list', - payload: { messages, conversationId } - })); + try { + // 查询消息 + const [messages] = await sequelize.query( + `SELECT * FROM chat_messages + WHERE conversation_id = ? + ORDER BY created_at DESC + LIMIT ? OFFSET ?`, + { replacements: [conversationId, limit, offset] } + ); + + // 反转顺序,使最早的消息在前 + messages.reverse(); + + // 获取消息总数 + const [[totalCount]] = await sequelize.query( + 'SELECT COUNT(*) as count FROM chat_messages WHERE conversation_id = ?', + { replacements: [conversationId] } + ); + + ws.send(JSON.stringify({ + type: 'messages_list', + payload: { + messages, + conversationId, + pagination: { + page, + limit, + total: totalCount.count, + totalPages: Math.ceil(totalCount.count / limit) + } + } + })); + + console.log('消息获取成功:', { conversationId, messageCount: messages.length }); + + // 如果是客服查看消息,自动将未读消息标记为已读 + if (connection.isManager) { + await sequelize.query( + 'UPDATE chat_messages SET is_read = 1, read_time = NOW() WHERE conversation_id = ? AND is_read = 0 AND sender_type = 1', + { replacements: [conversationId] } + ); + + // 更新会话未读数 + await sequelize.query( + 'UPDATE chat_conversations SET cs_unread_count = 0 WHERE conversation_id = ?', + { replacements: [conversationId] } + ); + + console.log('客服查看后更新未读状态:', { conversationId }); + } + } catch (error) { + console.error('获取消息失败:', { conversationId, error: error.message }); + ws.send(JSON.stringify({ + type: 'error', + message: '获取消息失败: ' + error.message + })); + } break; case 'close_conversation':