671 lines
18 KiB
JavaScript
671 lines
18 KiB
JavaScript
// 消息历史记录管理器 - 微信小程序专用
|
|
// 处理消息历史记录的本地缓存、分页加载、存储优化等
|
|
|
|
const apiClient = require('./api-client.js');
|
|
|
|
/**
|
|
* 消息历史记录管理器
|
|
* 功能:
|
|
* 1. 消息历史记录缓存
|
|
* 2. 分页加载优化
|
|
* 3. 存储空间管理
|
|
* 4. 消息去重和排序
|
|
* 5. 离线消息处理
|
|
* 6. 数据压缩存储
|
|
*/
|
|
class MessageHistoryManager {
|
|
constructor() {
|
|
this.isInitialized = false;
|
|
|
|
// 历史记录配置
|
|
this.historyConfig = {
|
|
// 每页消息数量
|
|
pageSize: 20,
|
|
|
|
// 本地缓存的最大消息数量(每个会话)
|
|
maxCachedMessages: 500,
|
|
|
|
// 缓存过期时间(毫秒)
|
|
cacheExpireTime: 24 * 60 * 60 * 1000, // 24小时
|
|
|
|
// 存储压缩阈值(字节)
|
|
compressionThreshold: 1024,
|
|
|
|
// 自动清理间隔(毫秒)
|
|
cleanupInterval: 60 * 60 * 1000, // 1小时
|
|
|
|
// 预加载页数
|
|
preloadPages: 2
|
|
};
|
|
|
|
// 消息缓存 Map<conversationId, MessageCache>
|
|
this.messageCache = new Map();
|
|
|
|
// 加载状态 Map<conversationId, LoadingState>
|
|
this.loadingStates = new Map();
|
|
|
|
// 存储统计
|
|
this.storageStats = {
|
|
totalSize: 0,
|
|
messageCount: 0,
|
|
conversationCount: 0,
|
|
lastCleanup: 0
|
|
};
|
|
|
|
// 清理定时器
|
|
this.cleanupTimer = null;
|
|
|
|
this.init();
|
|
}
|
|
|
|
// 初始化历史记录管理器
|
|
async init() {
|
|
if (this.isInitialized) return;
|
|
|
|
console.log('📚 初始化消息历史记录管理器...');
|
|
|
|
try {
|
|
// 加载缓存的消息
|
|
await this.loadCachedMessages();
|
|
|
|
// 计算存储统计
|
|
this.calculateStorageStats();
|
|
|
|
// 启动定时清理
|
|
this.startCleanupTimer();
|
|
|
|
this.isInitialized = true;
|
|
console.log('✅ 消息历史记录管理器初始化完成');
|
|
|
|
} catch (error) {
|
|
console.error('❌ 消息历史记录管理器初始化失败:', error);
|
|
}
|
|
}
|
|
|
|
// 获取会话消息历史
|
|
async getConversationHistory(conversationId, options = {}) {
|
|
try {
|
|
console.log('📚 获取会话消息历史:', conversationId);
|
|
|
|
const {
|
|
page = 1,
|
|
pageSize = this.historyConfig.pageSize,
|
|
forceRefresh = false,
|
|
loadDirection = 'up' // 'up' 向上加载更早的消息, 'down' 向下加载更新的消息
|
|
} = options;
|
|
|
|
// 获取或创建消息缓存
|
|
let messageCache = this.getMessageCache(conversationId);
|
|
if (!messageCache) {
|
|
messageCache = this.createMessageCache(conversationId);
|
|
}
|
|
|
|
// 检查是否需要从服务器加载
|
|
const needServerLoad = forceRefresh ||
|
|
this.shouldLoadFromServer(messageCache, page, pageSize, loadDirection);
|
|
|
|
if (needServerLoad) {
|
|
// 从服务器加载消息
|
|
const serverResult = await this.loadMessagesFromServer(
|
|
conversationId,
|
|
page,
|
|
pageSize,
|
|
loadDirection,
|
|
messageCache
|
|
);
|
|
|
|
if (!serverResult.success) {
|
|
return serverResult;
|
|
}
|
|
}
|
|
|
|
// 从缓存获取消息
|
|
const messages = this.getMessagesFromCache(messageCache, page, pageSize, loadDirection);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
messages: messages,
|
|
page: page,
|
|
pageSize: pageSize,
|
|
total: messageCache.totalCount,
|
|
hasMore: this.hasMoreMessages(messageCache, page, pageSize, loadDirection),
|
|
cached: !needServerLoad
|
|
}
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('❌ 获取会话消息历史失败:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// 预加载消息
|
|
async preloadMessages(conversationId, currentPage = 1) {
|
|
try {
|
|
console.log('📚 预加载消息:', conversationId);
|
|
|
|
const preloadPromises = [];
|
|
|
|
// 预加载后续页面
|
|
for (let i = 1; i <= this.historyConfig.preloadPages; i++) {
|
|
const nextPage = currentPage + i;
|
|
preloadPromises.push(
|
|
this.getConversationHistory(conversationId, {
|
|
page: nextPage,
|
|
loadDirection: 'up'
|
|
})
|
|
);
|
|
}
|
|
|
|
// 并行执行预加载
|
|
await Promise.allSettled(preloadPromises);
|
|
console.log('✅ 消息预加载完成');
|
|
|
|
} catch (error) {
|
|
console.error('❌ 消息预加载失败:', error);
|
|
}
|
|
}
|
|
|
|
// 添加新消息到缓存
|
|
addMessageToCache(conversationId, message) {
|
|
try {
|
|
let messageCache = this.getMessageCache(conversationId);
|
|
if (!messageCache) {
|
|
messageCache = this.createMessageCache(conversationId);
|
|
}
|
|
|
|
// 检查消息是否已存在
|
|
const existingIndex = messageCache.messages.findIndex(m => m.id === message.id);
|
|
if (existingIndex !== -1) {
|
|
// 更新现有消息
|
|
messageCache.messages[existingIndex] = message;
|
|
} else {
|
|
// 添加新消息(按时间排序)
|
|
const insertIndex = this.findInsertIndex(messageCache.messages, message);
|
|
messageCache.messages.splice(insertIndex, 0, message);
|
|
messageCache.totalCount++;
|
|
}
|
|
|
|
// 限制缓存大小
|
|
this.limitCacheSize(messageCache);
|
|
|
|
// 更新缓存时间
|
|
messageCache.lastUpdated = Date.now();
|
|
|
|
// 保存到本地存储
|
|
this.saveMessageCache(conversationId, messageCache);
|
|
|
|
console.log('📚 消息已添加到缓存:', message.id);
|
|
|
|
} catch (error) {
|
|
console.error('❌ 添加消息到缓存失败:', error);
|
|
}
|
|
}
|
|
|
|
// 更新消息状态
|
|
updateMessageInCache(conversationId, messageId, updates) {
|
|
try {
|
|
const messageCache = this.getMessageCache(conversationId);
|
|
if (!messageCache) return false;
|
|
|
|
const messageIndex = messageCache.messages.findIndex(m => m.id === messageId);
|
|
if (messageIndex === -1) return false;
|
|
|
|
// 更新消息
|
|
messageCache.messages[messageIndex] = {
|
|
...messageCache.messages[messageIndex],
|
|
...updates
|
|
};
|
|
|
|
// 更新缓存时间
|
|
messageCache.lastUpdated = Date.now();
|
|
|
|
// 保存到本地存储
|
|
this.saveMessageCache(conversationId, messageCache);
|
|
|
|
console.log('📚 消息状态已更新:', messageId);
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('❌ 更新消息状态失败:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 删除消息
|
|
deleteMessageFromCache(conversationId, messageId) {
|
|
try {
|
|
const messageCache = this.getMessageCache(conversationId);
|
|
if (!messageCache) return false;
|
|
|
|
const messageIndex = messageCache.messages.findIndex(m => m.id === messageId);
|
|
if (messageIndex === -1) return false;
|
|
|
|
// 删除消息
|
|
messageCache.messages.splice(messageIndex, 1);
|
|
messageCache.totalCount--;
|
|
|
|
// 更新缓存时间
|
|
messageCache.lastUpdated = Date.now();
|
|
|
|
// 保存到本地存储
|
|
this.saveMessageCache(conversationId, messageCache);
|
|
|
|
console.log('📚 消息已从缓存删除:', messageId);
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('❌ 删除消息失败:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 从服务器加载消息
|
|
async loadMessagesFromServer(conversationId, page, pageSize, loadDirection, messageCache) {
|
|
try {
|
|
// 设置加载状态
|
|
this.setLoadingState(conversationId, true);
|
|
|
|
// 计算请求参数
|
|
const requestParams = this.calculateRequestParams(
|
|
messageCache,
|
|
page,
|
|
pageSize,
|
|
loadDirection
|
|
);
|
|
|
|
// 调用API
|
|
const response = await apiClient.request({
|
|
url: '/api/v1/messages/history',
|
|
method: 'GET',
|
|
data: {
|
|
conversationId: conversationId,
|
|
...requestParams
|
|
}
|
|
});
|
|
|
|
if (response.success) {
|
|
// 处理返回的消息
|
|
const messages = response.data.messages || [];
|
|
const total = response.data.total || 0;
|
|
|
|
// 更新缓存
|
|
this.updateCacheWithServerData(messageCache, messages, total, loadDirection);
|
|
|
|
// 保存到本地存储
|
|
this.saveMessageCache(conversationId, messageCache);
|
|
|
|
console.log(`📚 从服务器加载了 ${messages.length} 条消息`);
|
|
return { success: true };
|
|
|
|
} else {
|
|
throw new Error(response.error || '加载消息失败');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ 从服务器加载消息失败:', error);
|
|
return { success: false, error: error.message };
|
|
|
|
} finally {
|
|
this.setLoadingState(conversationId, false);
|
|
}
|
|
}
|
|
|
|
// 计算请求参数
|
|
calculateRequestParams(messageCache, page, pageSize, loadDirection) {
|
|
const params = {
|
|
pageSize: pageSize
|
|
};
|
|
|
|
if (loadDirection === 'up') {
|
|
// 向上加载更早的消息
|
|
if (messageCache.messages.length > 0) {
|
|
const oldestMessage = messageCache.messages[messageCache.messages.length - 1];
|
|
params.beforeTimestamp = oldestMessage.timestamp;
|
|
}
|
|
} else {
|
|
// 向下加载更新的消息
|
|
if (messageCache.messages.length > 0) {
|
|
const newestMessage = messageCache.messages[0];
|
|
params.afterTimestamp = newestMessage.timestamp;
|
|
}
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
// 更新缓存数据
|
|
updateCacheWithServerData(messageCache, newMessages, total, loadDirection) {
|
|
if (loadDirection === 'up') {
|
|
// 向上加载:添加到数组末尾(更早的消息)
|
|
messageCache.messages.push(...newMessages);
|
|
} else {
|
|
// 向下加载:添加到数组开头(更新的消息)
|
|
messageCache.messages.unshift(...newMessages);
|
|
}
|
|
|
|
// 去重和排序
|
|
this.deduplicateAndSortMessages(messageCache);
|
|
|
|
// 更新总数
|
|
messageCache.totalCount = Math.max(total, messageCache.messages.length);
|
|
|
|
// 限制缓存大小
|
|
this.limitCacheSize(messageCache);
|
|
|
|
// 更新时间戳
|
|
messageCache.lastUpdated = Date.now();
|
|
}
|
|
|
|
// 消息去重和排序
|
|
deduplicateAndSortMessages(messageCache) {
|
|
// 去重
|
|
const uniqueMessages = new Map();
|
|
messageCache.messages.forEach(message => {
|
|
uniqueMessages.set(message.id, message);
|
|
});
|
|
|
|
// 排序(最新的在前面)
|
|
messageCache.messages = Array.from(uniqueMessages.values())
|
|
.sort((a, b) => b.timestamp - a.timestamp);
|
|
}
|
|
|
|
// 查找插入位置
|
|
findInsertIndex(messages, newMessage) {
|
|
for (let i = 0; i < messages.length; i++) {
|
|
if (newMessage.timestamp > messages[i].timestamp) {
|
|
return i;
|
|
}
|
|
}
|
|
return messages.length;
|
|
}
|
|
|
|
// 限制缓存大小
|
|
limitCacheSize(messageCache) {
|
|
if (messageCache.messages.length > this.historyConfig.maxCachedMessages) {
|
|
// 保留最新的消息
|
|
const keepCount = Math.floor(this.historyConfig.maxCachedMessages * 0.8);
|
|
messageCache.messages = messageCache.messages.slice(0, keepCount);
|
|
console.log(`📚 缓存大小已限制到 ${keepCount} 条消息`);
|
|
}
|
|
}
|
|
|
|
// 获取消息缓存
|
|
getMessageCache(conversationId) {
|
|
return this.messageCache.get(conversationId);
|
|
}
|
|
|
|
// 创建消息缓存
|
|
createMessageCache(conversationId) {
|
|
const cache = {
|
|
conversationId: conversationId,
|
|
messages: [],
|
|
totalCount: 0,
|
|
lastUpdated: Date.now(),
|
|
loadedPages: new Set(),
|
|
hasMoreUp: true,
|
|
hasMoreDown: false
|
|
};
|
|
|
|
this.messageCache.set(conversationId, cache);
|
|
return cache;
|
|
}
|
|
|
|
// 从缓存获取消息
|
|
getMessagesFromCache(messageCache, page, pageSize, loadDirection) {
|
|
const startIndex = (page - 1) * pageSize;
|
|
const endIndex = startIndex + pageSize;
|
|
|
|
return messageCache.messages.slice(startIndex, endIndex);
|
|
}
|
|
|
|
// 检查是否有更多消息
|
|
hasMoreMessages(messageCache, page, pageSize, loadDirection) {
|
|
if (loadDirection === 'up') {
|
|
return messageCache.hasMoreUp &&
|
|
(page * pageSize) < messageCache.totalCount;
|
|
} else {
|
|
return messageCache.hasMoreDown;
|
|
}
|
|
}
|
|
|
|
// 检查是否需要从服务器加载
|
|
shouldLoadFromServer(messageCache, page, pageSize, loadDirection) {
|
|
// 如果缓存为空,需要加载
|
|
if (messageCache.messages.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
// 如果请求的页面超出缓存范围,需要加载
|
|
const startIndex = (page - 1) * pageSize;
|
|
const endIndex = startIndex + pageSize;
|
|
|
|
if (endIndex > messageCache.messages.length && messageCache.hasMoreUp) {
|
|
return true;
|
|
}
|
|
|
|
// 如果缓存过期,需要刷新
|
|
const cacheAge = Date.now() - messageCache.lastUpdated;
|
|
if (cacheAge > this.historyConfig.cacheExpireTime) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 设置加载状态
|
|
setLoadingState(conversationId, loading) {
|
|
this.loadingStates.set(conversationId, {
|
|
loading: loading,
|
|
timestamp: Date.now()
|
|
});
|
|
}
|
|
|
|
// 获取加载状态
|
|
getLoadingState(conversationId) {
|
|
const state = this.loadingStates.get(conversationId);
|
|
return state ? state.loading : false;
|
|
}
|
|
|
|
// 保存消息缓存到本地存储
|
|
saveMessageCache(conversationId, messageCache) {
|
|
try {
|
|
const cacheKey = `message_cache_${conversationId}`;
|
|
const cacheData = this.compressMessageCache(messageCache);
|
|
|
|
wx.setStorageSync(cacheKey, cacheData);
|
|
|
|
} catch (error) {
|
|
console.error('❌ 保存消息缓存失败:', error);
|
|
}
|
|
}
|
|
|
|
// 加载缓存的消息
|
|
async loadCachedMessages() {
|
|
try {
|
|
const storageInfo = wx.getStorageInfoSync();
|
|
const cacheKeys = storageInfo.keys.filter(key => key.startsWith('message_cache_'));
|
|
|
|
for (const key of cacheKeys) {
|
|
try {
|
|
const conversationId = key.replace('message_cache_', '');
|
|
const cacheData = wx.getStorageSync(key);
|
|
|
|
if (cacheData) {
|
|
const messageCache = this.decompressMessageCache(cacheData);
|
|
this.messageCache.set(conversationId, messageCache);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error(`❌ 加载缓存失败 [${key}]:`, error);
|
|
// 删除损坏的缓存
|
|
wx.removeStorageSync(key);
|
|
}
|
|
}
|
|
|
|
console.log(`📚 加载了 ${this.messageCache.size} 个会话的消息缓存`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ 加载缓存消息失败:', error);
|
|
}
|
|
}
|
|
|
|
// 压缩消息缓存
|
|
compressMessageCache(messageCache) {
|
|
try {
|
|
const data = JSON.stringify(messageCache);
|
|
|
|
// 如果数据较大,可以考虑压缩
|
|
if (data.length > this.historyConfig.compressionThreshold) {
|
|
// 这里可以实现压缩算法
|
|
// 目前直接返回原数据
|
|
return { compressed: false, data: messageCache };
|
|
}
|
|
|
|
return { compressed: false, data: messageCache };
|
|
|
|
} catch (error) {
|
|
console.error('❌ 压缩消息缓存失败:', error);
|
|
return { compressed: false, data: messageCache };
|
|
}
|
|
}
|
|
|
|
// 解压消息缓存
|
|
decompressMessageCache(cacheData) {
|
|
try {
|
|
if (cacheData.compressed) {
|
|
// 这里可以实现解压算法
|
|
return cacheData.data;
|
|
}
|
|
|
|
return cacheData.data;
|
|
|
|
} catch (error) {
|
|
console.error('❌ 解压消息缓存失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 计算存储统计
|
|
calculateStorageStats() {
|
|
let totalSize = 0;
|
|
let messageCount = 0;
|
|
|
|
for (const [conversationId, cache] of this.messageCache) {
|
|
messageCount += cache.messages.length;
|
|
|
|
// 估算缓存大小
|
|
const cacheSize = JSON.stringify(cache).length;
|
|
totalSize += cacheSize;
|
|
}
|
|
|
|
this.storageStats = {
|
|
totalSize: totalSize,
|
|
messageCount: messageCount,
|
|
conversationCount: this.messageCache.size,
|
|
lastCleanup: Date.now()
|
|
};
|
|
}
|
|
|
|
// 启动定时清理
|
|
startCleanupTimer() {
|
|
if (this.cleanupTimer) {
|
|
clearInterval(this.cleanupTimer);
|
|
}
|
|
|
|
this.cleanupTimer = setInterval(() => {
|
|
this.performCleanup();
|
|
}, this.historyConfig.cleanupInterval);
|
|
}
|
|
|
|
// 执行清理
|
|
performCleanup() {
|
|
try {
|
|
console.log('📚 执行消息缓存清理...');
|
|
|
|
const now = Date.now();
|
|
let cleanedCount = 0;
|
|
|
|
for (const [conversationId, cache] of this.messageCache) {
|
|
// 清理过期缓存
|
|
const cacheAge = now - cache.lastUpdated;
|
|
if (cacheAge > this.historyConfig.cacheExpireTime * 2) {
|
|
this.messageCache.delete(conversationId);
|
|
wx.removeStorageSync(`message_cache_${conversationId}`);
|
|
cleanedCount++;
|
|
}
|
|
}
|
|
|
|
// 更新统计
|
|
this.calculateStorageStats();
|
|
|
|
console.log(`📚 清理完成,删除了 ${cleanedCount} 个过期缓存`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ 消息缓存清理失败:', error);
|
|
}
|
|
}
|
|
|
|
// 清除会话缓存
|
|
clearConversationCache(conversationId) {
|
|
this.messageCache.delete(conversationId);
|
|
wx.removeStorageSync(`message_cache_${conversationId}`);
|
|
console.log('📚 已清除会话缓存:', conversationId);
|
|
}
|
|
|
|
// 清除所有缓存
|
|
clearAllCache() {
|
|
for (const conversationId of this.messageCache.keys()) {
|
|
wx.removeStorageSync(`message_cache_${conversationId}`);
|
|
}
|
|
this.messageCache.clear();
|
|
this.calculateStorageStats();
|
|
console.log('📚 已清除所有消息缓存');
|
|
}
|
|
|
|
// 获取存储统计
|
|
getStorageStats() {
|
|
this.calculateStorageStats();
|
|
return { ...this.storageStats };
|
|
}
|
|
|
|
// 获取会话统计
|
|
getConversationStats(conversationId) {
|
|
const cache = this.getMessageCache(conversationId);
|
|
if (!cache) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
messageCount: cache.messages.length,
|
|
totalCount: cache.totalCount,
|
|
lastUpdated: cache.lastUpdated,
|
|
cacheAge: Date.now() - cache.lastUpdated,
|
|
hasMoreUp: cache.hasMoreUp,
|
|
hasMoreDown: cache.hasMoreDown
|
|
};
|
|
}
|
|
|
|
// 重置管理器
|
|
reset() {
|
|
this.clearAllCache();
|
|
this.loadingStates.clear();
|
|
|
|
if (this.cleanupTimer) {
|
|
clearInterval(this.cleanupTimer);
|
|
this.cleanupTimer = null;
|
|
}
|
|
}
|
|
|
|
// 销毁管理器
|
|
destroy() {
|
|
this.reset();
|
|
this.isInitialized = false;
|
|
}
|
|
}
|
|
|
|
// 创建全局实例
|
|
const messageHistoryManager = new MessageHistoryManager();
|
|
|
|
module.exports = messageHistoryManager;
|