init
This commit is contained in:
371
app/backend-python/services/conversation_service.py
Normal file
371
app/backend-python/services/conversation_service.py
Normal file
@@ -0,0 +1,371 @@
|
||||
"""
|
||||
对话列表管理服务
|
||||
负责管理用户的对话列表(会话列表)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
|
||||
from database import query, insert, update, get_db_type
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConversationService:
|
||||
"""对话列表管理服务"""
|
||||
|
||||
@staticmethod
|
||||
def generate_id() -> int:
|
||||
"""生成唯一ID(使用时间戳+随机数)"""
|
||||
import time
|
||||
import random
|
||||
return int(time.time() * 1000000) + random.randint(1000, 9999)
|
||||
|
||||
@staticmethod
|
||||
async def create_conversation(
|
||||
system_user_id: int,
|
||||
context_id: str,
|
||||
title: str = "新对话",
|
||||
description: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
创建新对话
|
||||
|
||||
Args:
|
||||
system_user_id: 系统用户ID
|
||||
context_id: 会话上下文ID
|
||||
title: 对话标题
|
||||
description: 对话描述(可选)
|
||||
|
||||
Returns:
|
||||
创建的对话信息
|
||||
"""
|
||||
try:
|
||||
conv_id = ConversationService.generate_id()
|
||||
|
||||
sql = """
|
||||
INSERT INTO conversation_list
|
||||
(id, context_id, system_user_id, title, description,
|
||||
message_count, created_at, updated_at, is_active)
|
||||
VALUES (%s, %s, %s, %s, %s, 0, NOW(), NOW(), TRUE)
|
||||
"""
|
||||
|
||||
params = (conv_id, context_id, system_user_id, title, description)
|
||||
|
||||
insert(sql, params)
|
||||
logger.info(f"✅ 创建对话成功: user={system_user_id}, context={context_id}, title={title}")
|
||||
|
||||
return {
|
||||
"id": conv_id,
|
||||
"context_id": context_id,
|
||||
"system_user_id": system_user_id,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"message_count": 0,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 创建对话失败: {e}", exc_info=True)
|
||||
raise Exception(f"创建对话失败: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_conversation_by_context(
|
||||
context_id: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据context_id获取对话信息
|
||||
|
||||
Args:
|
||||
context_id: 会话上下文ID
|
||||
|
||||
Returns:
|
||||
对话信息,不存在则返回None
|
||||
"""
|
||||
try:
|
||||
sql = """
|
||||
SELECT id, context_id, system_user_id, title, description,
|
||||
message_count, last_message, created_at, updated_at, is_active
|
||||
FROM conversation_list
|
||||
WHERE context_id = %s
|
||||
LIMIT 1
|
||||
"""
|
||||
|
||||
results = query(sql, (context_id,))
|
||||
|
||||
if results:
|
||||
return results[0]
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 获取对话信息失败: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def update_conversation(
|
||||
context_id: str,
|
||||
title: Optional[str] = None,
|
||||
last_message: Optional[str] = None,
|
||||
increment_message_count: bool = False
|
||||
) -> bool:
|
||||
"""
|
||||
更新对话信息
|
||||
|
||||
Args:
|
||||
context_id: 会话上下文ID
|
||||
title: 新标题(可选)
|
||||
last_message: 最后一条消息(可选)
|
||||
increment_message_count: 是否增加消息计数
|
||||
|
||||
Returns:
|
||||
是否成功更新
|
||||
"""
|
||||
try:
|
||||
db_type = get_db_type()
|
||||
|
||||
# 对于StarRocks,使用DELETE+INSERT策略
|
||||
if db_type == 'starrocks':
|
||||
# 先获取当前记录
|
||||
conv = await ConversationService.get_conversation_by_context(context_id)
|
||||
if not conv:
|
||||
logger.warning(f"⚠️ 对话不存在: context={context_id}")
|
||||
return False
|
||||
|
||||
# 更新字段
|
||||
if title is not None:
|
||||
conv['title'] = title
|
||||
if last_message is not None:
|
||||
preview = last_message[:200] if len(last_message) > 200 else last_message
|
||||
conv['last_message'] = preview
|
||||
if increment_message_count:
|
||||
conv['message_count'] = conv.get('message_count', 0) + 1
|
||||
|
||||
# 删除旧记录
|
||||
delete_sql = "DELETE FROM conversation_list WHERE context_id = %s"
|
||||
update(delete_sql, (context_id,))
|
||||
|
||||
# 插入新记录
|
||||
insert_sql = """
|
||||
INSERT INTO conversation_list
|
||||
(id, system_user_id, updated_at, context_id, title, description,
|
||||
message_count, last_message, created_at, is_active)
|
||||
VALUES (%s, %s, NOW(), %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
insert(insert_sql, (
|
||||
conv['id'],
|
||||
conv['system_user_id'],
|
||||
conv['context_id'],
|
||||
conv['title'],
|
||||
conv.get('description'),
|
||||
conv['message_count'],
|
||||
conv.get('last_message'),
|
||||
conv['created_at'],
|
||||
conv.get('is_active', True)
|
||||
))
|
||||
|
||||
logger.info(f"✅ 更新对话成功(StarRocks): context={context_id}")
|
||||
return True
|
||||
|
||||
# 对于MySQL,使用正常的UPDATE
|
||||
else:
|
||||
# 构建动态更新SQL
|
||||
update_fields = []
|
||||
params = []
|
||||
|
||||
if title is not None:
|
||||
update_fields.append("title = %s")
|
||||
params.append(title)
|
||||
|
||||
if last_message is not None:
|
||||
# 限制预览长度为200字符
|
||||
preview = last_message[:200] if len(last_message) > 200 else last_message
|
||||
update_fields.append("last_message = %s")
|
||||
params.append(preview)
|
||||
|
||||
if increment_message_count:
|
||||
update_fields.append("message_count = message_count + 1")
|
||||
|
||||
# 总是更新 updated_at
|
||||
update_fields.append("updated_at = NOW()")
|
||||
|
||||
if not update_fields:
|
||||
return True # 没有字段需要更新
|
||||
|
||||
params.append(context_id)
|
||||
|
||||
sql = f"""
|
||||
UPDATE conversation_list
|
||||
SET {', '.join(update_fields)}
|
||||
WHERE context_id = %s
|
||||
"""
|
||||
|
||||
affected = update(sql, tuple(params))
|
||||
|
||||
if affected > 0:
|
||||
logger.info(f"✅ 更新对话成功: context={context_id}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"⚠️ 对话不存在: context={context_id}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 更新对话失败: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def get_user_conversations(
|
||||
system_user_id: int,
|
||||
limit: int = 50,
|
||||
only_active: bool = True
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取用户的对话列表
|
||||
|
||||
Args:
|
||||
system_user_id: 系统用户ID
|
||||
limit: 返回数量限制
|
||||
only_active: 是否只返回活跃对话
|
||||
|
||||
Returns:
|
||||
对话列表(按更新时间倒序)
|
||||
"""
|
||||
try:
|
||||
if only_active:
|
||||
sql = """
|
||||
SELECT id, context_id, system_user_id, title, description,
|
||||
message_count, last_message, created_at, updated_at, is_active
|
||||
FROM conversation_list
|
||||
WHERE system_user_id = %s AND is_active = TRUE
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT %s
|
||||
"""
|
||||
else:
|
||||
sql = """
|
||||
SELECT id, context_id, system_user_id, title, description,
|
||||
message_count, last_message, created_at, updated_at, is_active
|
||||
FROM conversation_list
|
||||
WHERE system_user_id = %s
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT %s
|
||||
"""
|
||||
|
||||
results = query(sql, (system_user_id, limit))
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 获取对话列表失败: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
async def delete_conversation(
|
||||
context_id: str,
|
||||
system_user_id: int,
|
||||
soft_delete: bool = True
|
||||
) -> bool:
|
||||
"""
|
||||
删除对话
|
||||
|
||||
Args:
|
||||
context_id: 会话上下文ID
|
||||
system_user_id: 系统用户ID(用于权限验证)
|
||||
soft_delete: 是否软删除(仅标记为不活跃)
|
||||
|
||||
Returns:
|
||||
是否成功删除
|
||||
"""
|
||||
try:
|
||||
db_type = get_db_type()
|
||||
|
||||
# StarRocks只支持硬删除(DELETE),不支持UPDATE
|
||||
if db_type == 'starrocks' or not soft_delete:
|
||||
# 硬删除:物理删除记录
|
||||
sql = """
|
||||
DELETE FROM conversation_list
|
||||
WHERE context_id = %s AND system_user_id = %s
|
||||
"""
|
||||
affected = update(sql, (context_id, system_user_id))
|
||||
|
||||
if affected > 0:
|
||||
logger.info(f"✅ 删除对话成功: context={context_id}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"⚠️ 对话不存在或无权限: context={context_id}")
|
||||
return False
|
||||
|
||||
# MySQL支持软删除
|
||||
else:
|
||||
# 软删除:仅标记为不活跃(需要先获取记录再重新插入)
|
||||
conv = await ConversationService.get_conversation_by_context(context_id)
|
||||
if not conv or conv['system_user_id'] != system_user_id:
|
||||
logger.warning(f"⚠️ 对话不存在或无权限: context={context_id}")
|
||||
return False
|
||||
|
||||
# 删除旧记录
|
||||
delete_sql = """
|
||||
DELETE FROM conversation_list
|
||||
WHERE context_id = %s AND system_user_id = %s
|
||||
"""
|
||||
update(delete_sql, (context_id, system_user_id))
|
||||
|
||||
# 插入标记为不活跃的记录
|
||||
insert_sql = """
|
||||
INSERT INTO conversation_list
|
||||
(id, system_user_id, updated_at, context_id, title, description,
|
||||
message_count, last_message, created_at, is_active)
|
||||
VALUES (%s, %s, NOW(), %s, %s, %s, %s, %s, %s, FALSE)
|
||||
"""
|
||||
insert(insert_sql, (
|
||||
conv['id'],
|
||||
conv['system_user_id'],
|
||||
conv['context_id'],
|
||||
conv['title'],
|
||||
conv.get('description'),
|
||||
conv['message_count'],
|
||||
conv.get('last_message'),
|
||||
conv['created_at']
|
||||
))
|
||||
|
||||
logger.info(f"✅ 软删除对话成功: context={context_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 删除对话失败: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def ensure_conversation_exists(
|
||||
system_user_id: int,
|
||||
context_id: str,
|
||||
title: str = "新对话"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
确保对话存在,不存在则创建
|
||||
|
||||
Args:
|
||||
system_user_id: 系统用户ID
|
||||
context_id: 会话上下文ID
|
||||
title: 对话标题
|
||||
|
||||
Returns:
|
||||
对话信息
|
||||
"""
|
||||
# 先尝试获取
|
||||
conv = await ConversationService.get_conversation_by_context(context_id)
|
||||
|
||||
if conv:
|
||||
return conv
|
||||
|
||||
# 不存在则创建
|
||||
return await ConversationService.create_conversation(
|
||||
system_user_id=system_user_id,
|
||||
context_id=context_id,
|
||||
title=title
|
||||
)
|
||||
|
||||
|
||||
# 创建全局服务实例
|
||||
conversation_service = ConversationService()
|
||||
|
||||
Reference in New Issue
Block a user