Files
Neo-ZQYY/apps/backend/app/ai/conversation_service.py
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

179 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
对话记录持久化服务。
负责 biz.ai_conversations 和 biz.ai_messages 两张表的 CRUD。
所有 8 个 AI 应用的每次调用都通过本服务记录对话和消息。
"""
from __future__ import annotations
import json
import logging
from datetime import datetime
from app.database import get_connection
logger = logging.getLogger(__name__)
class ConversationService:
"""AI 对话记录持久化服务。"""
def create_conversation(
self,
user_id: int | str,
nickname: str,
app_id: str,
site_id: int,
source_page: str | None = None,
source_context: dict | None = None,
title: str | None = None,
) -> int:
"""创建对话记录,返回 conversation_id。
系统自动调用时 user_id 为 'system'
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO biz.ai_conversations
(user_id, nickname, app_id, site_id, source_page, source_context, title)
VALUES (%s, %s, %s, %s, %s, %s, %s)
RETURNING id
""",
(
str(user_id),
nickname,
app_id,
site_id,
source_page,
json.dumps(source_context, ensure_ascii=False) if source_context else None,
title,
),
)
row = cur.fetchone()
conn.commit()
return row[0]
except Exception:
conn.rollback()
raise
finally:
conn.close()
def add_message(
self,
conversation_id: int,
role: str,
content: str,
tokens_used: int | None = None,
) -> int:
"""添加消息记录,返回 message_id。"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO biz.ai_messages
(conversation_id, role, content, tokens_used)
VALUES (%s, %s, %s, %s)
RETURNING id
""",
(conversation_id, role, content, tokens_used),
)
row = cur.fetchone()
conn.commit()
return row[0]
except Exception:
conn.rollback()
raise
finally:
conn.close()
def update_title(self, conversation_id: int, title: str) -> None:
"""更新对话标题。"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"UPDATE biz.ai_conversations SET title = %s WHERE id = %s",
(title, conversation_id),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def get_conversations(
self,
user_id: int | str,
site_id: int,
page: int = 1,
page_size: int = 20,
) -> list[dict]:
"""查询用户历史对话列表,按 created_at 降序,分页。"""
offset = (page - 1) * page_size
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, user_id, nickname, app_id, site_id,
source_page, source_context, title, created_at
FROM biz.ai_conversations
WHERE user_id = %s AND site_id = %s
ORDER BY created_at DESC
LIMIT %s OFFSET %s
""",
(str(user_id), site_id, page_size, offset),
)
columns = [desc[0] for desc in cur.description]
rows = cur.fetchall()
return [
_row_to_dict(columns, row)
for row in rows
]
finally:
conn.close()
def get_messages(
self,
conversation_id: int,
) -> list[dict]:
"""查询对话的所有消息,按 created_at 升序。"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, conversation_id, role, content,
tokens_used, created_at
FROM biz.ai_messages
WHERE conversation_id = %s
ORDER BY created_at ASC
""",
(conversation_id,),
)
columns = [desc[0] for desc in cur.description]
rows = cur.fetchall()
return [
_row_to_dict(columns, row)
for row in rows
]
finally:
conn.close()
def _row_to_dict(columns: list[str], row: tuple) -> dict:
"""将数据库行转换为 dict处理特殊类型序列化。"""
result = {}
for col, val in zip(columns, row):
if isinstance(val, datetime):
result[col] = val.isoformat()
else:
result[col] = val
return result