- 新增 messageStore.js 消息存储模块,支持自动保存所有收发消息 - 修改 main.js,在消息转发时自动记录到本地存储 - 修改 preload.js,暴露消息管理 IPC API - 修改 App.js,添加消息历史查看界面 - 统计信息面板(总数/接收/发送/会话数) - 会话列表和消息详情 - 搜索、过滤、分页功能 - 导出 JSON 和清空历史 - 新增完整文档(MESSAGE_HISTORY.md 等) - 新增测试脚本 test-message-history.js 版本:v1.0.1
265 lines
7.3 KiB
JavaScript
265 lines
7.3 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { app } = require('electron');
|
|
|
|
class MessageStore {
|
|
constructor() {
|
|
this.dataDir = path.join(app.getPath('userData'), 'messages');
|
|
this.messagesFile = path.join(this.dataDir, 'messages.json');
|
|
this.messages = [];
|
|
this.maxMessages = 10000; // 最多保留 10000 条消息
|
|
|
|
// 初始化存储
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// 创建目录
|
|
if (!fs.existsSync(this.dataDir)) {
|
|
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
}
|
|
|
|
// 加载现有消息
|
|
if (fs.existsSync(this.messagesFile)) {
|
|
try {
|
|
const data = fs.readFileSync(this.messagesFile, 'utf-8');
|
|
this.messages = JSON.parse(data);
|
|
console.log(`[MessageStore] 加载了 ${this.messages.length} 条历史消息`);
|
|
} catch (error) {
|
|
console.error('[MessageStore] 加载消息失败:', error.message);
|
|
this.messages = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
// 保存消息
|
|
saveMessage(message) {
|
|
const msg = {
|
|
id: message.id || `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
timestamp: message.timestamp || new Date().toISOString(),
|
|
direction: message.direction, // 'inbound' | 'outbound'
|
|
source: message.source, // 'wecom' | 'openclaw'
|
|
sessionId: message.sessionId || '',
|
|
chatId: message.chatId || '',
|
|
senderId: message.senderId || '',
|
|
senderName: message.senderName || '',
|
|
messageType: message.messageType || 'text', // 'text' | 'image' | 'file' | 'voice'
|
|
content: message.content || '',
|
|
attachments: message.attachments || [],
|
|
metadata: message.metadata || {},
|
|
delivered: message.delivered !== false,
|
|
read: message.read || false
|
|
};
|
|
|
|
this.messages.push(msg);
|
|
|
|
// 限制消息数量
|
|
if (this.messages.length > this.maxMessages) {
|
|
this.messages = this.messages.slice(-this.maxMessages);
|
|
}
|
|
|
|
// 异步保存到文件
|
|
this.persist();
|
|
|
|
return msg;
|
|
}
|
|
|
|
// 批量保存消息
|
|
saveMessages(messages) {
|
|
messages.forEach(msg => this.saveMessage(msg));
|
|
}
|
|
|
|
// 持久化到文件
|
|
persist() {
|
|
// 使用异步写入,避免阻塞
|
|
setImmediate(() => {
|
|
try {
|
|
const tempFile = this.messagesFile + '.tmp';
|
|
fs.writeFileSync(tempFile, JSON.stringify(this.messages, null, 2), 'utf-8');
|
|
fs.renameSync(tempFile, this.messagesFile);
|
|
console.log(`[MessageStore] 已保存 ${this.messages.length} 条消息`);
|
|
} catch (error) {
|
|
console.error('[MessageStore] 保存消息失败:', error.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 获取消息列表
|
|
getMessages(options = {}) {
|
|
let filtered = [...this.messages];
|
|
|
|
// 按会话过滤
|
|
if (options.sessionId) {
|
|
filtered = filtered.filter(m => m.sessionId === options.sessionId);
|
|
}
|
|
|
|
// 按方向过滤
|
|
if (options.direction) {
|
|
filtered = filtered.filter(m => m.direction === options.direction);
|
|
}
|
|
|
|
// 按来源过滤
|
|
if (options.source) {
|
|
filtered = filtered.filter(m => m.source === options.source);
|
|
}
|
|
|
|
// 按时间范围过滤
|
|
if (options.startTime) {
|
|
filtered = filtered.filter(m => new Date(m.timestamp) >= new Date(options.startTime));
|
|
}
|
|
if (options.endTime) {
|
|
filtered = filtered.filter(m => new Date(m.timestamp) <= new Date(options.endTime));
|
|
}
|
|
|
|
// 分页
|
|
const limit = options.limit || 100;
|
|
const offset = options.offset || 0;
|
|
const paginated = filtered.slice(offset, offset + limit);
|
|
|
|
// 按时间倒序(最新的在前)
|
|
paginated.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
|
|
return {
|
|
messages: paginated,
|
|
total: filtered.length,
|
|
limit,
|
|
offset
|
|
};
|
|
}
|
|
|
|
// 获取会话列表
|
|
getSessions() {
|
|
const sessionMap = new Map();
|
|
|
|
this.messages.forEach(msg => {
|
|
if (!msg.sessionId) return;
|
|
|
|
if (!sessionMap.has(msg.sessionId)) {
|
|
sessionMap.set(msg.sessionId, {
|
|
sessionId: msg.sessionId,
|
|
chatId: msg.chatId,
|
|
lastMessageTime: msg.timestamp,
|
|
messageCount: 0,
|
|
unreadCount: 0
|
|
});
|
|
}
|
|
|
|
const session = sessionMap.get(msg.sessionId);
|
|
session.messageCount++;
|
|
|
|
if (!msg.read) {
|
|
session.unreadCount++;
|
|
}
|
|
|
|
if (new Date(msg.timestamp) > new Date(session.lastMessageTime)) {
|
|
session.lastMessageTime = msg.timestamp;
|
|
}
|
|
});
|
|
|
|
// 按最后消息时间排序
|
|
return Array.from(sessionMap.values())
|
|
.sort((a, b) => new Date(b.lastMessageTime) - new Date(a.lastMessageTime));
|
|
}
|
|
|
|
// 标记消息为已读
|
|
markAsRead(messageIds) {
|
|
const ids = Array.isArray(messageIds) ? messageIds : [messageIds];
|
|
let updated = 0;
|
|
|
|
this.messages.forEach(msg => {
|
|
if (ids.includes(msg.id) && !msg.read) {
|
|
msg.read = true;
|
|
updated++;
|
|
}
|
|
});
|
|
|
|
if (updated > 0) {
|
|
this.persist();
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
// 搜索消息
|
|
searchMessages(query, options = {}) {
|
|
const lowerQuery = query.toLowerCase();
|
|
|
|
const filtered = this.messages.filter(msg => {
|
|
// 搜索内容
|
|
if (msg.content && msg.content.toLowerCase().includes(lowerQuery)) {
|
|
return true;
|
|
}
|
|
|
|
// 搜索发送者
|
|
if (msg.senderName && msg.senderName.toLowerCase().includes(lowerQuery)) {
|
|
return true;
|
|
}
|
|
|
|
// 搜索会话 ID
|
|
if (msg.sessionId && msg.sessionId.toLowerCase().includes(lowerQuery)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
// 按相关性排序(内容匹配优先)
|
|
filtered.sort((a, b) => {
|
|
const aScore = (a.content.toLowerCase().includes(lowerQuery) ? 2 : 0) +
|
|
(a.senderName.toLowerCase().includes(lowerQuery) ? 1 : 0);
|
|
const bScore = (b.content.toLowerCase().includes(lowerQuery) ? 2 : 0) +
|
|
(b.senderName.toLowerCase().includes(lowerQuery) ? 1 : 0);
|
|
return bScore - aScore;
|
|
});
|
|
|
|
const limit = options.limit || 50;
|
|
const offset = options.offset || 0;
|
|
|
|
return {
|
|
messages: filtered.slice(offset, offset + limit),
|
|
total: filtered.length,
|
|
query
|
|
};
|
|
}
|
|
|
|
// 导出消息
|
|
exportMessages(options = {}) {
|
|
const result = this.getMessages(options);
|
|
return JSON.stringify(result.messages, null, 2);
|
|
}
|
|
|
|
// 清空消息
|
|
clear(options = {}) {
|
|
if (options.sessionId) {
|
|
this.messages = this.messages.filter(m => m.sessionId !== options.sessionId);
|
|
} else {
|
|
this.messages = [];
|
|
}
|
|
this.persist();
|
|
}
|
|
|
|
// 获取统计数据
|
|
getStats() {
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
return {
|
|
total: this.messages.length,
|
|
today: this.messages.filter(m => new Date(m.timestamp) >= today).length,
|
|
yesterday: this.messages.filter(m =>
|
|
new Date(m.timestamp) >= yesterday && new Date(m.timestamp) < today
|
|
).length,
|
|
inbound: this.messages.filter(m => m.direction === 'inbound').length,
|
|
outbound: this.messages.filter(m => m.direction === 'outbound').length,
|
|
wecom: this.messages.filter(m => m.source === 'wecom').length,
|
|
openclaw: this.messages.filter(m => m.source === 'openclaw').length,
|
|
sessions: this.getSessions().length
|
|
};
|
|
}
|
|
}
|
|
|
|
// 导出单例
|
|
module.exports = new MessageStore();
|