351 lines
9.6 KiB
JavaScript
351 lines
9.6 KiB
JavaScript
|
const Conversation = require('../models/Conversation');
|
|||
|
const Message = require('../models/Message');
|
|||
|
const cacheManager = require('../utils/cache');
|
|||
|
|
|||
|
class ConversationService {
|
|||
|
constructor() {
|
|||
|
this.cachePrefix = 'conv_service';
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 创建新对话
|
|||
|
*/
|
|||
|
async createConversation(title = '新对话') {
|
|||
|
try {
|
|||
|
const conversation = await Conversation.create(title);
|
|||
|
|
|||
|
// 清除对话列表缓存
|
|||
|
this.clearConversationListCache();
|
|||
|
|
|||
|
console.log('创建新对话:', conversation.id);
|
|||
|
return conversation;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.createConversation错误:', error);
|
|||
|
throw new Error('创建对话失败');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取对话列表
|
|||
|
*/
|
|||
|
async getConversations(limit = 50) {
|
|||
|
try {
|
|||
|
const cacheKey = `${this.cachePrefix}:list:${limit}`;
|
|||
|
|
|||
|
// 尝试从缓存获取
|
|||
|
let conversations = cacheManager.get(cacheKey);
|
|||
|
if (conversations) {
|
|||
|
console.log('从缓存获取对话列表');
|
|||
|
return conversations;
|
|||
|
}
|
|||
|
|
|||
|
// 从数据库获取
|
|||
|
conversations = await Conversation.findAll(limit);
|
|||
|
|
|||
|
// 缓存结果(5分钟)
|
|||
|
cacheManager.set(cacheKey, conversations, { ttl: 300000 });
|
|||
|
|
|||
|
console.log(`获取对话列表: ${conversations.length}条`);
|
|||
|
return conversations;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.getConversations错误:', error);
|
|||
|
throw new Error('获取对话列表失败');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 根据ID获取对话详情
|
|||
|
*/
|
|||
|
async getConversationById(id) {
|
|||
|
try {
|
|||
|
if (!id || isNaN(id)) {
|
|||
|
throw new Error('无效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
const cacheKey = `${this.cachePrefix}:detail:${id}`;
|
|||
|
|
|||
|
// 尝试从缓存获取
|
|||
|
let conversation = cacheManager.get(cacheKey);
|
|||
|
if (conversation) {
|
|||
|
console.log(`从缓存获取对话详情: ${id}`);
|
|||
|
return conversation;
|
|||
|
}
|
|||
|
|
|||
|
// 从数据库获取
|
|||
|
conversation = await Conversation.findById(id);
|
|||
|
if (!conversation) {
|
|||
|
throw new Error('对话不存在');
|
|||
|
}
|
|||
|
|
|||
|
// 缓存结果(10分钟)
|
|||
|
cacheManager.set(cacheKey, conversation, { ttl: 600000 });
|
|||
|
|
|||
|
console.log(`获取对话详情: ${id}`);
|
|||
|
return conversation;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.getConversationById错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取对话的消息列表
|
|||
|
*/
|
|||
|
async getConversationMessages(conversationId, limit = 100, offset = 0) {
|
|||
|
try {
|
|||
|
if (!conversationId || isNaN(conversationId)) {
|
|||
|
throw new Error('无效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
// 确保对话存在
|
|||
|
const conversation = await this.getConversationById(conversationId);
|
|||
|
if (!conversation) {
|
|||
|
throw new Error('对话不存在');
|
|||
|
}
|
|||
|
|
|||
|
const cacheKey = `${this.cachePrefix}:messages:${conversationId}:${limit}:${offset}`;
|
|||
|
|
|||
|
// 尝试从缓存获取
|
|||
|
let messages = cacheManager.get(cacheKey);
|
|||
|
if (messages) {
|
|||
|
console.log(`从缓存获取对话消息: ${conversationId}`);
|
|||
|
return messages;
|
|||
|
}
|
|||
|
|
|||
|
// 从数据库获取
|
|||
|
messages = await Message.findByConversationId(conversationId, limit, offset);
|
|||
|
|
|||
|
// 缓存结果(5分钟)
|
|||
|
cacheManager.set(cacheKey, messages, { ttl: 300000 });
|
|||
|
|
|||
|
console.log(`获取对话消息: ${conversationId}, ${messages.length}条`);
|
|||
|
return messages;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.getConversationMessages错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 更新对话标题
|
|||
|
*/
|
|||
|
async updateConversationTitle(id, title) {
|
|||
|
try {
|
|||
|
if (!id || isNaN(id)) {
|
|||
|
throw new Error('无效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
if (!title || title.trim().length === 0) {
|
|||
|
throw new Error('标题不能为空');
|
|||
|
}
|
|||
|
|
|||
|
const conversation = await Conversation.updateTitle(id, title.trim());
|
|||
|
|
|||
|
// 清除相关缓存
|
|||
|
this.clearConversationCache(id);
|
|||
|
this.clearConversationListCache();
|
|||
|
|
|||
|
console.log(`更新对话标题: ${id} -> ${title}`);
|
|||
|
return conversation;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.updateConversationTitle错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 删除对话
|
|||
|
*/
|
|||
|
async deleteConversation(id) {
|
|||
|
try {
|
|||
|
if (!id || isNaN(id)) {
|
|||
|
throw new Error('无效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
// 确保对话存在
|
|||
|
const conversation = await this.getConversationById(id);
|
|||
|
if (!conversation) {
|
|||
|
throw new Error('对话不存在');
|
|||
|
}
|
|||
|
|
|||
|
await Conversation.delete(id);
|
|||
|
|
|||
|
// 清除相关缓存
|
|||
|
this.clearConversationCache(id);
|
|||
|
this.clearConversationListCache();
|
|||
|
cacheManager.deleteConversationContext(id);
|
|||
|
|
|||
|
console.log(`删除对话: ${id}`);
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.deleteConversation错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 批量删除对话
|
|||
|
*/
|
|||
|
async deleteMultipleConversations(ids) {
|
|||
|
try {
|
|||
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|||
|
throw new Error('无效的对话ID列表');
|
|||
|
}
|
|||
|
|
|||
|
// 验证所有ID
|
|||
|
const validIds = ids.filter(id => id && !isNaN(id));
|
|||
|
if (validIds.length === 0) {
|
|||
|
throw new Error('没有有效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
const deletedCount = await Conversation.deleteMultiple(validIds);
|
|||
|
|
|||
|
// 清除相关缓存
|
|||
|
validIds.forEach(id => {
|
|||
|
this.clearConversationCache(id);
|
|||
|
cacheManager.deleteConversationContext(id);
|
|||
|
});
|
|||
|
this.clearConversationListCache();
|
|||
|
|
|||
|
console.log(`批量删除对话: ${deletedCount}条`);
|
|||
|
return deletedCount;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.deleteMultipleConversations错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取对话统计信息
|
|||
|
*/
|
|||
|
async getConversationStats(conversationId) {
|
|||
|
try {
|
|||
|
if (!conversationId || isNaN(conversationId)) {
|
|||
|
throw new Error('无效的对话ID');
|
|||
|
}
|
|||
|
|
|||
|
const conversation = await this.getConversationById(conversationId);
|
|||
|
if (!conversation) {
|
|||
|
throw new Error('对话不存在');
|
|||
|
}
|
|||
|
|
|||
|
const messageCount = await Message.getMessageCount(conversationId);
|
|||
|
|
|||
|
return {
|
|||
|
id: conversation.id,
|
|||
|
title: conversation.title,
|
|||
|
created_at: conversation.created_at,
|
|||
|
updated_at: conversation.updated_at,
|
|||
|
message_count: messageCount
|
|||
|
};
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.getConversationStats错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 搜索对话
|
|||
|
*/
|
|||
|
async searchConversations(query, limit = 20) {
|
|||
|
try {
|
|||
|
if (!query || query.trim().length === 0) {
|
|||
|
throw new Error('搜索关键词不能为空');
|
|||
|
}
|
|||
|
|
|||
|
// 搜索消息内容
|
|||
|
const messages = await Message.search(query.trim(), null, limit);
|
|||
|
|
|||
|
// 提取关联的对话ID
|
|||
|
const conversationIds = [...new Set(messages.map(msg => msg.conversation_id))];
|
|||
|
|
|||
|
// 获取对话详情
|
|||
|
const conversations = await Promise.all(
|
|||
|
conversationIds.map(id => this.getConversationById(id))
|
|||
|
);
|
|||
|
|
|||
|
// 过滤掉不存在的对话
|
|||
|
const validConversations = conversations.filter(conv => conv !== null);
|
|||
|
|
|||
|
console.log(`搜索对话: "${query}" -> ${validConversations.length}条结果`);
|
|||
|
return validConversations;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.searchConversations错误:', error);
|
|||
|
throw error;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 自动生成对话标题(基于第一条用户消息)
|
|||
|
*/
|
|||
|
async generateConversationTitle(conversationId) {
|
|||
|
try {
|
|||
|
const messages = await Message.findByConversationId(conversationId, 5);
|
|||
|
|
|||
|
// 找到第一条用户消息
|
|||
|
const firstUserMessage = messages.find(msg => msg.role === 'user');
|
|||
|
if (!firstUserMessage) {
|
|||
|
return '新对话';
|
|||
|
}
|
|||
|
|
|||
|
// 从第一条用户消息生成标题(取前20个字符)
|
|||
|
let title = firstUserMessage.content.trim();
|
|||
|
if (title.length > 20) {
|
|||
|
title = title.substring(0, 20) + '...';
|
|||
|
}
|
|||
|
|
|||
|
// 更新对话标题
|
|||
|
await this.updateConversationTitle(conversationId, title);
|
|||
|
|
|||
|
console.log(`自动生成对话标题: ${conversationId} -> ${title}`);
|
|||
|
return title;
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.generateConversationTitle错误:', error);
|
|||
|
return '新对话';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 清除对话详情缓存
|
|||
|
*/
|
|||
|
clearConversationCache(conversationId) {
|
|||
|
const detailKey = `${this.cachePrefix}:detail:${conversationId}`;
|
|||
|
cacheManager.delete(detailKey);
|
|||
|
|
|||
|
// 清除消息缓存(可能有多个分页)
|
|||
|
for (let i = 0; i < 10; i++) {
|
|||
|
const messageKey = `${this.cachePrefix}:messages:${conversationId}:100:${i * 100}`;
|
|||
|
cacheManager.delete(messageKey);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 清除对话列表缓存
|
|||
|
*/
|
|||
|
clearConversationListCache() {
|
|||
|
// 清除不同分页的列表缓存
|
|||
|
for (let limit = 10; limit <= 100; limit += 10) {
|
|||
|
const listKey = `${this.cachePrefix}:list:${limit}`;
|
|||
|
cacheManager.delete(listKey);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 更新对话活动时间
|
|||
|
*/
|
|||
|
async updateLastActivity(conversationId) {
|
|||
|
try {
|
|||
|
await Conversation.updateLastActivity(conversationId);
|
|||
|
|
|||
|
// 清除相关缓存
|
|||
|
this.clearConversationCache(conversationId);
|
|||
|
this.clearConversationListCache();
|
|||
|
|
|||
|
} catch (error) {
|
|||
|
console.error('ConversationService.updateLastActivity错误:', error);
|
|||
|
// 这是一个辅助操作,失败不应该影响主流程
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
module.exports = ConversationService;
|