更新后台管理消息列表;
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from pymongo import AsyncMongoClient
|
||||
@@ -6,10 +9,16 @@ from app.models import models
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import os
|
||||
from app.routes import auth_routes, wework_routes, glm_routes, message_routes
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -24,27 +33,8 @@ async def lifespan(app: FastAPI):
|
||||
) # 批量注册模型
|
||||
print(f"🗄️ 数据库连接成功: {database_name}")
|
||||
|
||||
# 初始化GLM服务,确保默认角色存在
|
||||
try:
|
||||
from app.services.glm_service import get_glm_service
|
||||
|
||||
glm_service = get_glm_service()
|
||||
await glm_service.ensure_default_role()
|
||||
print(f"✅ GLM角色配置初始化完成")
|
||||
except Exception as e:
|
||||
print(f"⚠️ GLM角色配置初始化失败: {e}")
|
||||
|
||||
yield # 应用在此处进入正常运行状态,接收请求
|
||||
|
||||
# 关闭GLM服务
|
||||
try:
|
||||
from app.services.glm_service import close_glm_service
|
||||
|
||||
close_glm_service()
|
||||
print(f"GLM服务已关闭")
|
||||
except Exception as e:
|
||||
print(f"关闭GLM服务时出错: {e}")
|
||||
|
||||
# 关闭数据库连接
|
||||
await client.close()
|
||||
print(f"数据库连接关闭")
|
||||
@@ -85,50 +75,4 @@ async def root():
|
||||
"service": "企业微信认证与会话存档服务",
|
||||
"version": "1.0.0",
|
||||
"description": "获取和管理企业微信access_token,会话存档功能,以及DeepSeek AI聊天服务",
|
||||
"endpoints": {
|
||||
"docs": "/api/docs",
|
||||
"redoc": "/api/redoc",
|
||||
"auth": {"token": "/api/auth/token", "token_info": "/api/auth/token/info"},
|
||||
"wework": {
|
||||
"health": "/api/wework/health",
|
||||
"chat": "/api/wework/chat",
|
||||
"media": "/api/wework/media",
|
||||
"decrypt": "/api/wework/decrypt",
|
||||
"config": "/api/wework/config",
|
||||
},
|
||||
"glm": {
|
||||
"root": "/api/glm/",
|
||||
"role": "/api/glm/role",
|
||||
"chat": "/api/glm/chat",
|
||||
"conversations": "/api/glm/conversations/{conversation_id}",
|
||||
},
|
||||
"messages": {
|
||||
"load": "/api/messages/load",
|
||||
"list": "/api/messages/",
|
||||
"stats": "/api/messages/stats",
|
||||
"detail": "/api/messages/{msgid}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
主函数,启动FastAPI服务器
|
||||
"""
|
||||
# 从环境变量获取主机和端口,使用默认值
|
||||
host = os.getenv("APP_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("APP_PORT", "8000"))
|
||||
|
||||
print("启动企业微信认证与会话存档服务...")
|
||||
print(f"API文档地址: http://{host}:{port}/docs")
|
||||
print(f"API根地址: http://{host}:{port}")
|
||||
print("企业微信会话存档API: /api/wework/*")
|
||||
print("DeepSeek AI聊天API: /api/glm/*")
|
||||
print("消息管理API: /api/messages/*")
|
||||
|
||||
uvicorn.run("app.main:app", host=host, port=port, reload=True, log_level="info")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
import httpx
|
||||
import os
|
||||
import logging
|
||||
@@ -23,6 +23,10 @@ class Message(BaseModel):
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
conversation_id: Optional[str] = None
|
||||
enhanced_prompt: Optional[str] = None # 前端生成的完整提示词
|
||||
customer_tags: Optional[List[Dict[str, Any]]] = None # 客户标签
|
||||
message_history: Optional[List[Dict[str, Any]]] = None # 企业微信历史消息
|
||||
workflow_context: Optional[Dict[str, Any]] = None # 工作流上下文
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
@@ -194,44 +198,233 @@ async def update_role_config(
|
||||
@router.post("/chat", response_model=ChatResponse)
|
||||
async def chat(request: ChatRequest, service: GLMService = Depends(get_glm_service)):
|
||||
"""处理聊天请求"""
|
||||
# 创建或获取对话历史
|
||||
# 步骤1: 记录请求开始
|
||||
logger.info(
|
||||
f"收到聊天请求: conversation_id={request.conversation_id or 'None'}, "
|
||||
f"message_length={len(request.message)}, "
|
||||
f"message_preview='{request.message[:50]}{'...' if len(request.message) > 50 else ''}'"
|
||||
)
|
||||
|
||||
# 记录增强参数信息
|
||||
has_enhanced_prompt = request.enhanced_prompt is not None
|
||||
has_customer_tags = (
|
||||
request.customer_tags is not None and len(request.customer_tags) > 0
|
||||
)
|
||||
has_message_history = (
|
||||
request.message_history is not None and len(request.message_history) > 0
|
||||
)
|
||||
has_workflow_context = request.workflow_context is not None
|
||||
|
||||
logger.info(
|
||||
f"增强参数状态: enhanced_prompt={has_enhanced_prompt}, "
|
||||
f"customer_tags={has_customer_tags} ({len(request.customer_tags) if has_customer_tags else 0}个标签), "
|
||||
f"message_history={has_message_history} ({len(request.message_history) if has_message_history else 0}条历史), "
|
||||
f"workflow_context={has_workflow_context}"
|
||||
)
|
||||
|
||||
# 记录客户标签摘要
|
||||
if has_customer_tags:
|
||||
tag_categories = {}
|
||||
for tag in request.customer_tags:
|
||||
category = tag.get("TagCategory", "未分类")
|
||||
name = tag.get("TagName", "未命名")
|
||||
if category not in tag_categories:
|
||||
tag_categories[category] = []
|
||||
tag_categories[category].append(name)
|
||||
|
||||
tag_summary = []
|
||||
for category, names in tag_categories.items():
|
||||
tag_summary.append(
|
||||
f"{category}: {', '.join(names[:3])}{'...' if len(names) > 3 else ''}"
|
||||
)
|
||||
|
||||
# 检查是否使用mock数据
|
||||
is_mock_data = False
|
||||
for tag in request.customer_tags:
|
||||
if tag.get("TagName", "").startswith("mock_") or tag.get(
|
||||
"TagCategory", ""
|
||||
).startswith("mock_"):
|
||||
is_mock_data = True
|
||||
break
|
||||
|
||||
mock_note = " [mock数据]" if is_mock_data else ""
|
||||
logger.info(f"客户标签摘要{mock_note}: {', '.join(tag_summary)}")
|
||||
|
||||
# 记录历史消息摘要
|
||||
if has_message_history:
|
||||
# 按时间排序,获取最近的消息
|
||||
sorted_history = sorted(
|
||||
request.message_history, key=lambda x: x.get("msgtime", ""), reverse=True
|
||||
)[
|
||||
:5
|
||||
] # 只显示最近5条
|
||||
|
||||
history_summary = []
|
||||
for i, msg in enumerate(sorted_history):
|
||||
from_user = msg.get("from_user", "未知用户")
|
||||
user_id = (
|
||||
msg.get("user_id")
|
||||
or msg.get("userId")
|
||||
or msg.get("from_user_id")
|
||||
or "无ID"
|
||||
)
|
||||
content = msg.get("text", {}).get("content", "") or msg.get("content", "")
|
||||
content_preview = content[:30] + ("..." if len(content) > 30 else "")
|
||||
history_summary.append(f"{from_user}(ID:{user_id}): '{content_preview}'")
|
||||
|
||||
# 检查是否使用mock数据
|
||||
is_mock_data = False
|
||||
for msg in sorted_history:
|
||||
if msg.get("from_user", "").startswith("mock_") or msg.get(
|
||||
"user_id", ""
|
||||
).startswith("mock_"):
|
||||
is_mock_data = True
|
||||
break
|
||||
|
||||
mock_note = " [mock数据]" if is_mock_data else ""
|
||||
logger.info(f"历史消息摘要{mock_note}: {', '.join(history_summary)}")
|
||||
|
||||
# 步骤2: 创建或获取对话历史
|
||||
if request.conversation_id and request.conversation_id in conversations:
|
||||
conversation_history = conversations[request.conversation_id]
|
||||
logger.info(
|
||||
f"使用现有对话: {request.conversation_id}, 历史消息数: {len(conversation_history)}"
|
||||
)
|
||||
# 记录历史消息摘要
|
||||
if conversation_history:
|
||||
last_messages = conversation_history[-3:] # 显示最近3条消息
|
||||
history_summary = []
|
||||
for i, msg in enumerate(last_messages, 1):
|
||||
role = msg["role"]
|
||||
content_preview = msg["content"][:30] + (
|
||||
"..." if len(msg["content"]) > 30 else ""
|
||||
)
|
||||
history_summary.append(f"{role}: '{content_preview}'")
|
||||
logger.debug(f"最近历史消息摘要: {', '.join(history_summary)}")
|
||||
else:
|
||||
conversation_history = []
|
||||
request.conversation_id = f"conv_{len(conversations) + 1}"
|
||||
conversations[request.conversation_id] = conversation_history
|
||||
logger.info(f"新建对话: {request.conversation_id}")
|
||||
|
||||
# 获取当前角色配置
|
||||
# 步骤3: 构建系统提示词
|
||||
try:
|
||||
role = await service.get_current_role()
|
||||
system_prompt = build_system_prompt(role.to_role_config_dict())
|
||||
except Exception as e:
|
||||
logger.error(f"获取角色配置失败: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取角色配置失败: {str(e)}")
|
||||
if has_enhanced_prompt:
|
||||
# 使用前端生成的增强提示词
|
||||
system_prompt = request.enhanced_prompt
|
||||
logger.info(f"使用前端增强提示词, 长度: {len(system_prompt)} 字符")
|
||||
logger.debug(
|
||||
f"增强提示词预览: '{system_prompt[:200]}{'...' if len(system_prompt) > 200 else ''}'"
|
||||
)
|
||||
else:
|
||||
# 获取当前角色配置并构建系统提示词
|
||||
role = await service.get_current_role()
|
||||
role_config = role.to_role_config_dict()
|
||||
logger.info(
|
||||
f"获取角色配置: name='{role.name}', "
|
||||
f"技能数: {len(role_config['skills'].split('\\n')) if role_config['skills'] else 0}, "
|
||||
f"限制数: {len(role_config['restrictions'].split('\\n')) if role_config['restrictions'] else 0}"
|
||||
)
|
||||
|
||||
# 构建消息列表
|
||||
system_prompt = build_system_prompt(role_config)
|
||||
logger.debug(f"系统提示词构建完成, 长度: {len(system_prompt)} 字符")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"构建系统提示词失败: {e}, conversation_id={request.conversation_id}"
|
||||
)
|
||||
raise HTTPException(status_code=500, detail=f"构建系统提示词失败: {str(e)}")
|
||||
|
||||
# 步骤4: 构建消息列表
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
|
||||
# 添加历史消息
|
||||
for msg in conversation_history[-10:]: # 保留最近10条历史
|
||||
# 初始化企业微信历史消息变量
|
||||
selected_wework_messages = []
|
||||
|
||||
# 添加企业微信历史消息(如果提供)
|
||||
if has_message_history:
|
||||
logger.info(f"添加企业微信历史消息到上下文: {len(request.message_history)} 条")
|
||||
|
||||
# 将企业微信消息转换为大模型消息格式
|
||||
wework_messages = []
|
||||
for msg in request.message_history:
|
||||
from_user = msg.get("from_user", "未知用户")
|
||||
content = msg.get("text", {}).get("content", "") or msg.get("content", "")
|
||||
msgtime = msg.get("msgtime", "")
|
||||
|
||||
if content:
|
||||
# 确定角色:如果是客户发送的,则为user;否则为assistant
|
||||
# 这里需要根据实际业务逻辑判断,暂时假设外部用户是user
|
||||
role = (
|
||||
"user"
|
||||
if "customer" in from_user.lower() or "user" in from_user.lower()
|
||||
else "assistant"
|
||||
)
|
||||
|
||||
# 格式化消息内容
|
||||
formatted_content = f"[{msgtime}] {from_user}: {content}"
|
||||
wework_messages.append({"role": role, "content": formatted_content})
|
||||
|
||||
# 添加到消息列表(限制数量,避免过长)
|
||||
max_wework_messages = 10
|
||||
selected_wework_messages = wework_messages[
|
||||
-max_wework_messages:
|
||||
] # 取最近的消息
|
||||
messages.extend(selected_wework_messages)
|
||||
logger.info(
|
||||
f"已添加 {len(selected_wework_messages)} 条企业微信历史消息到上下文"
|
||||
)
|
||||
|
||||
# 添加与大模型的对话历史
|
||||
history_messages = conversation_history[-10:] # 保留最近10条历史
|
||||
for msg in history_messages:
|
||||
messages.append({"role": msg["role"], "content": msg["content"]})
|
||||
|
||||
# 添加当前用户消息
|
||||
messages.append({"role": "user", "content": request.message})
|
||||
conversation_history.append({"role": "user", "content": request.message})
|
||||
|
||||
# 调用DeepSeek API
|
||||
logger.info(
|
||||
f"构建消息列表完成: 系统消息1条, 企业微信历史{len(selected_wework_messages)}条, "
|
||||
f"对话历史{len(history_messages)}条, 用户消息1条, 总消息数: {len(messages)}"
|
||||
)
|
||||
|
||||
# 记录消息列表摘要
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
for i, msg in enumerate(messages):
|
||||
role = msg["role"]
|
||||
content_preview = msg["content"][:50] + (
|
||||
"..." if len(msg["content"]) > 50 else ""
|
||||
)
|
||||
logger.debug(f"消息{i+1}: {role} - '{content_preview}'")
|
||||
|
||||
# 步骤5: 调用DeepSeek API
|
||||
try:
|
||||
logger.info(
|
||||
f"调用DeepSeek API, conversation_id={request.conversation_id}, 总消息数: {len(messages)}"
|
||||
)
|
||||
response_text = await call_deepseek_api(messages)
|
||||
|
||||
# 步骤6: 记录API响应
|
||||
logger.info(
|
||||
f"DeepSeek API调用成功, conversation_id={request.conversation_id}, "
|
||||
f"响应长度: {len(response_text)} 字符, "
|
||||
f"响应预览: '{response_text[:100]}{'...' if len(response_text) > 100 else ''}'"
|
||||
)
|
||||
|
||||
# 添加助手回复到历史
|
||||
conversation_history.append({"role": "assistant", "content": response_text})
|
||||
|
||||
# 步骤7: 返回响应
|
||||
logger.info(
|
||||
f"返回聊天响应: conversation_id={request.conversation_id}, 响应长度: {len(response_text)} 字符"
|
||||
)
|
||||
return ChatResponse(
|
||||
response=response_text, conversation_id=request.conversation_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"DeepSeek API调用失败: {e}, conversation_id={request.conversation_id}, message_preview='{request.message[:50]}...'"
|
||||
)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
|
||||
@@ -135,6 +135,40 @@ async def get_message_stats(service: MessageService = Depends(get_message_servic
|
||||
raise HTTPException(status_code=500, detail=f"获取消息统计失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_message_config():
|
||||
"""
|
||||
获取消息服务配置信息
|
||||
|
||||
返回企业微信SDK配置、文件路径等基本信息
|
||||
"""
|
||||
try:
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 从环境变量获取配置
|
||||
corpid = os.getenv("CORPID", "")
|
||||
secret = os.getenv("WEWORK_SECRET", "")
|
||||
|
||||
# 构建文件路径
|
||||
backend_dir = Path(__file__).parent.parent.parent
|
||||
lib_path = backend_dir / "lib" / "sdk" / "libWeWorkFinanceSdk_C.so"
|
||||
data_dir = backend_dir / "data"
|
||||
|
||||
config = {
|
||||
"corpid": corpid,
|
||||
# "secret": secret[:10] + "..." if secret else "", # 只显示前10个字符
|
||||
"libPath": str(lib_path),
|
||||
"dataDir": str(data_dir),
|
||||
}
|
||||
|
||||
return {"success": True, "message": "配置获取成功", "data": config}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取配置信息失败: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"获取配置信息失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/{msgid}")
|
||||
async def get_message_by_id(
|
||||
msgid: str, service: MessageService = Depends(get_message_service)
|
||||
@@ -150,13 +184,3 @@ async def get_message_by_id(
|
||||
except Exception as e:
|
||||
logger.error(f"查询消息详情失败 (msgid: {msgid}): {e}")
|
||||
raise HTTPException(status_code=500, detail=f"查询消息详情失败: {str(e)}")
|
||||
|
||||
|
||||
# 应用关闭时的清理
|
||||
@router.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""应用关闭时清理资源"""
|
||||
from app.services.message_service import close_message_service
|
||||
|
||||
close_message_service()
|
||||
logger.info("消息服务已关闭")
|
||||
|
||||
@@ -3,8 +3,13 @@ GLM聊天服务,包含角色配置管理
|
||||
"""
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Optional
|
||||
from pymongo import AsyncMongoClient
|
||||
from beanie import init_beanie
|
||||
from app.models.glm_role import GLMRole
|
||||
from app.models import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -14,11 +19,19 @@ class GLMService:
|
||||
|
||||
def __init__(self):
|
||||
self._current_role: Optional[GLMRole] = None
|
||||
self._max_retries = 3
|
||||
self._retry_delay = 1 # 秒
|
||||
self._db_client: Optional[AsyncMongoClient] = None
|
||||
self._db_initialized = False
|
||||
|
||||
async def get_current_role(self) -> GLMRole:
|
||||
"""获取当前激活的角色配置"""
|
||||
if self._current_role is None:
|
||||
self._current_role = await self._load_or_create_role()
|
||||
logger.info("当前无缓存角色,开始加载角色配置")
|
||||
self._current_role = await self._load_or_create_role_with_retry()
|
||||
logger.info(f"角色加载完成: name='{self._current_role.name}'")
|
||||
else:
|
||||
logger.debug(f"使用缓存角色: name='{self._current_role.name}'")
|
||||
return self._current_role
|
||||
|
||||
async def update_role(self, role_data: dict) -> GLMRole:
|
||||
@@ -30,33 +43,153 @@ class GLMService:
|
||||
Returns:
|
||||
更新后的GLMRole实例
|
||||
"""
|
||||
logger.info(f"开始更新角色配置,更新字段: {list(role_data.keys())}")
|
||||
role = await self.get_current_role()
|
||||
|
||||
# 更新字段
|
||||
if "name" in role_data:
|
||||
# 记录更新前的值
|
||||
changes = []
|
||||
if "name" in role_data and role_data["name"] != role.name:
|
||||
changes.append(f"name: '{role.name}' -> '{role_data['name']}'")
|
||||
role.name = role_data["name"]
|
||||
if "description" in role_data:
|
||||
|
||||
if "description" in role_data and role_data["description"] != role.description:
|
||||
changes.append(
|
||||
f"description: {len(role.description)}字符 -> {len(role_data['description'])}字符"
|
||||
)
|
||||
role.description = role_data["description"]
|
||||
if "skills" in role_data:
|
||||
|
||||
if "skills" in role_data and role_data["skills"] != role.skills:
|
||||
old_skills_count = len(role.skills.split("\n")) if role.skills else 0
|
||||
new_skills_count = (
|
||||
len(role_data["skills"].split("\n")) if role_data["skills"] else 0
|
||||
)
|
||||
changes.append(f"skills: {old_skills_count}条 -> {new_skills_count}条")
|
||||
role.skills = role_data["skills"]
|
||||
if "restrictions" in role_data:
|
||||
|
||||
if (
|
||||
"restrictions" in role_data
|
||||
and role_data["restrictions"] != role.restrictions
|
||||
):
|
||||
old_restrictions_count = (
|
||||
len(role.restrictions.split("\n")) if role.restrictions else 0
|
||||
)
|
||||
new_restrictions_count = (
|
||||
len(role_data["restrictions"].split("\n"))
|
||||
if role_data["restrictions"]
|
||||
else 0
|
||||
)
|
||||
changes.append(
|
||||
f"restrictions: {old_restrictions_count}条 -> {new_restrictions_count}条"
|
||||
)
|
||||
role.restrictions = role_data["restrictions"]
|
||||
|
||||
role.update_timestamp()
|
||||
await role.save()
|
||||
|
||||
if changes:
|
||||
logger.info(f"角色配置变更: {', '.join(changes)}")
|
||||
else:
|
||||
logger.info("角色配置无实际变更")
|
||||
|
||||
# 使用重试机制保存
|
||||
await self._save_with_retry(role)
|
||||
|
||||
# 更新缓存
|
||||
self._current_role = role
|
||||
|
||||
logger.info(f"角色配置已更新: {role.name}")
|
||||
logger.info(f"角色配置更新完成: name='{role.name}'")
|
||||
return role
|
||||
|
||||
async def _load_or_create_role_with_retry(self) -> GLMRole:
|
||||
"""使用重试机制加载或创建默认角色"""
|
||||
for attempt in range(self._max_retries):
|
||||
try:
|
||||
return await self._load_or_create_role()
|
||||
except Exception as e:
|
||||
if attempt < self._max_retries - 1:
|
||||
logger.warning(f"加载角色配置失败,第{attempt + 1}次重试: {e}")
|
||||
await asyncio.sleep(self._retry_delay * (attempt + 1))
|
||||
else:
|
||||
logger.error(f"加载角色配置失败,已达到最大重试次数: {e}")
|
||||
raise
|
||||
|
||||
async def _ensure_db_connection(self) -> None:
|
||||
"""确保数据库连接正常,如果连接已关闭则重新连接"""
|
||||
if not self._db_initialized:
|
||||
await self._init_db_connection()
|
||||
|
||||
async def _init_db_connection(self) -> None:
|
||||
"""初始化数据库连接"""
|
||||
try:
|
||||
# 从环境变量获取数据库配置
|
||||
mongodb_url = os.getenv("MONGODB_URL", "mongodb://localhost:27017")
|
||||
database_name = os.getenv("DATABASE_NAME", "douba_chat_dev")
|
||||
|
||||
# 创建新的数据库连接
|
||||
if self._db_client:
|
||||
try:
|
||||
await self._db_client.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
self._db_client = AsyncMongoClient(mongodb_url)
|
||||
await init_beanie(
|
||||
database=self._db_client[database_name], document_models=models
|
||||
)
|
||||
self._db_initialized = True
|
||||
logger.info(f"数据库连接初始化成功: {database_name}")
|
||||
except Exception as e:
|
||||
logger.error(f"数据库连接初始化失败: {e}")
|
||||
self._db_initialized = False
|
||||
raise
|
||||
|
||||
async def _load_or_create_role(self) -> GLMRole:
|
||||
"""加载或创建默认角色"""
|
||||
try:
|
||||
# 确保数据库连接正常
|
||||
await self._ensure_db_connection()
|
||||
|
||||
# 1. 尝试获取激活的角色
|
||||
active_role = await GLMRole.find_one(GLMRole.is_active == True)
|
||||
if active_role:
|
||||
logger.info(f"加载激活角色: {active_role.name}")
|
||||
return active_role
|
||||
|
||||
# 2. 尝试获取默认角色
|
||||
default_role = await GLMRole.find_one(GLMRole.is_default == True)
|
||||
if default_role:
|
||||
default_role.is_active = True
|
||||
await default_role.save()
|
||||
logger.info(f"加载默认角色并激活: {default_role.name}")
|
||||
return default_role
|
||||
|
||||
# 3. 创建默认角色
|
||||
logger.info("创建默认角色配置")
|
||||
return await self._create_default_role()
|
||||
except Exception as e:
|
||||
logger.error(f"加载或创建角色失败: {e}")
|
||||
# 检查是否是数据库连接错误
|
||||
if (
|
||||
"Cannot use AsyncMongoClient after close" in str(e)
|
||||
or "not connected" in str(e).lower()
|
||||
):
|
||||
logger.error("数据库连接已关闭,尝试重新连接...")
|
||||
self._db_initialized = False
|
||||
# 尝试重新连接
|
||||
try:
|
||||
await self._init_db_connection()
|
||||
logger.info("数据库重新连接成功,重试操作...")
|
||||
# 重试操作
|
||||
return await self._retry_after_reconnect()
|
||||
except Exception as reconnect_error:
|
||||
logger.error(f"数据库重新连接失败: {reconnect_error}")
|
||||
raise
|
||||
|
||||
async def _retry_after_reconnect(self) -> GLMRole:
|
||||
"""重新连接后重试操作"""
|
||||
# 1. 尝试获取激活的角色
|
||||
active_role = await GLMRole.find_one(GLMRole.is_active == True)
|
||||
if active_role:
|
||||
logger.info(f"加载激活角色: {active_role.name}")
|
||||
logger.info(f"重新连接后加载激活角色: {active_role.name}")
|
||||
return active_role
|
||||
|
||||
# 2. 尝试获取默认角色
|
||||
@@ -64,11 +197,11 @@ class GLMService:
|
||||
if default_role:
|
||||
default_role.is_active = True
|
||||
await default_role.save()
|
||||
logger.info(f"加载默认角色并激活: {default_role.name}")
|
||||
logger.info(f"重新连接后加载默认角色并激活: {default_role.name}")
|
||||
return default_role
|
||||
|
||||
# 3. 创建默认角色
|
||||
logger.info("创建默认角色配置")
|
||||
logger.info("重新连接后创建默认角色配置")
|
||||
return await self._create_default_role()
|
||||
|
||||
async def _create_default_role(self) -> GLMRole:
|
||||
@@ -89,10 +222,40 @@ class GLMService:
|
||||
logger.info(f"默认角色创建成功: {role.name}")
|
||||
return role
|
||||
|
||||
async def _save_with_retry(self, role: GLMRole) -> None:
|
||||
"""使用重试机制保存角色"""
|
||||
for attempt in range(self._max_retries):
|
||||
try:
|
||||
# 确保数据库连接正常
|
||||
await self._ensure_db_connection()
|
||||
await role.save()
|
||||
return
|
||||
except Exception as e:
|
||||
if attempt < self._max_retries - 1:
|
||||
logger.warning(f"保存角色配置失败,第{attempt + 1}次重试: {e}")
|
||||
# 检查是否是数据库连接错误
|
||||
if (
|
||||
"Cannot use AsyncMongoClient after close" in str(e)
|
||||
or "not connected" in str(e).lower()
|
||||
):
|
||||
logger.warning("检测到数据库连接问题,尝试重新连接...")
|
||||
self._db_initialized = False
|
||||
try:
|
||||
await self._init_db_connection()
|
||||
logger.info("数据库重新连接成功,继续重试...")
|
||||
except Exception as reconnect_error:
|
||||
logger.error(f"数据库重新连接失败: {reconnect_error}")
|
||||
await asyncio.sleep(self._retry_delay * (attempt + 1))
|
||||
else:
|
||||
logger.error(f"保存角色配置失败,已达到最大重试次数: {e}")
|
||||
raise
|
||||
|
||||
async def ensure_default_role(self):
|
||||
"""确保存在默认角色配置(应用启动时调用)"""
|
||||
try:
|
||||
role = await self._load_or_create_role()
|
||||
# 确保数据库连接正常
|
||||
await self._ensure_db_connection()
|
||||
role = await self._load_or_create_role_with_retry()
|
||||
logger.info(f"默认角色已确保存在: {role.name}")
|
||||
return role
|
||||
except Exception as e:
|
||||
@@ -102,6 +265,13 @@ class GLMService:
|
||||
async def close(self):
|
||||
"""关闭服务,清理资源"""
|
||||
self._current_role = None
|
||||
if self._db_client:
|
||||
try:
|
||||
await self._db_client.close()
|
||||
logger.info("数据库连接已关闭")
|
||||
except Exception as e:
|
||||
logger.error(f"关闭数据库连接时出错: {e}")
|
||||
self._db_initialized = False
|
||||
logger.info("GLM服务已关闭")
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
消息服务
|
||||
提供加载会话存档、解密、保存到数据库的整合功能
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import base64
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Optional
|
||||
import sys
|
||||
|
||||
|
||||
# 添加scripts目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts"))
|
||||
@@ -22,6 +22,9 @@ from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
from app.models.message import Message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -36,10 +39,8 @@ class MessageService:
|
||||
corpid: 企业ID,如果为None则从环境变量CORPID读取
|
||||
secret: 聊天内容存档的Secret,如果为None则从环境变量WEWORK_SECRET读取
|
||||
"""
|
||||
self.corpid = corpid or os.getenv("CORPID", "wwdf65802ca25ec195")
|
||||
self.secret = secret or os.getenv(
|
||||
"WEWORK_SECRET", "-Ta6WMWxBhfGolWnnlO15nQckj3DRKAowUOdX2fwvzE"
|
||||
)
|
||||
self.corpid = corpid or os.getenv("CORPID", "")
|
||||
self.secret = secret or os.getenv("WEWORK_SECRET", "")
|
||||
|
||||
# SDK实例(延迟初始化)
|
||||
self._sdk: Optional[WeWorkFinanceSDK] = None
|
||||
@@ -266,25 +267,6 @@ class MessageService:
|
||||
logger.info(f"开始将 {len(messages)} 条消息存储到数据库...")
|
||||
|
||||
try:
|
||||
# 导入数据库相关模块
|
||||
from pymongo import AsyncMongoClient
|
||||
from beanie import init_beanie
|
||||
from app.models import models
|
||||
from app.models.message import Message
|
||||
|
||||
# 从环境变量获取数据库配置
|
||||
mongodb_url = os.getenv(
|
||||
"MONGODB_URL",
|
||||
"mongodb://dblink:mgdb4link@localhost:27017/?authSource=admin",
|
||||
)
|
||||
database_name = os.getenv("DATABASE_NAME", "douba_chat_dev")
|
||||
|
||||
# 初始化数据库连接
|
||||
client = AsyncMongoClient(mongodb_url)
|
||||
await init_beanie(database=client[database_name], document_models=models)
|
||||
|
||||
logger.info(f"数据库连接成功: {database_name}")
|
||||
|
||||
# 批量处理消息
|
||||
batch_size = 100
|
||||
for i in range(0, len(messages), batch_size):
|
||||
@@ -352,9 +334,6 @@ class MessageService:
|
||||
logger.error(f"批量插入失败: {e}")
|
||||
stats["failed"] += len(new_messages)
|
||||
|
||||
# 关闭数据库连接
|
||||
await client.close()
|
||||
|
||||
logger.info(f"数据库存储完成:")
|
||||
logger.info(f" 总计消息: {stats['total_messages']}")
|
||||
logger.info(f" 成功存储: {stats['saved']}")
|
||||
@@ -392,23 +371,6 @@ class MessageService:
|
||||
分页查询结果
|
||||
"""
|
||||
try:
|
||||
# 导入数据库相关模块
|
||||
from pymongo import AsyncMongoClient
|
||||
from beanie import init_beanie
|
||||
from app.models import models
|
||||
from app.models.message import Message
|
||||
|
||||
# 从环境变量获取数据库配置
|
||||
mongodb_url = os.getenv(
|
||||
"MONGODB_URL",
|
||||
"mongodb://dblink:mgdb4link@localhost:27017/?authSource=admin",
|
||||
)
|
||||
database_name = os.getenv("DATABASE_NAME", "douba_chat_dev")
|
||||
|
||||
# 初始化数据库连接
|
||||
client = AsyncMongoClient(mongodb_url)
|
||||
await init_beanie(database=client[database_name], document_models=models)
|
||||
|
||||
# 构建查询条件
|
||||
query = {}
|
||||
if msgtype:
|
||||
@@ -432,8 +394,12 @@ class MessageService:
|
||||
# 获取总记录数
|
||||
total_count = await Message.find(query).count()
|
||||
|
||||
# 关闭数据库连接
|
||||
await client.close()
|
||||
# 记录成功日志
|
||||
logger.info(
|
||||
f"成功查询消息列表: 查询条件={query}, "
|
||||
f"页码={page}, 每页数量={page_size}, "
|
||||
f"总记录数={total_count}, 返回记录数={len(messages)}"
|
||||
)
|
||||
|
||||
# 准备响应数据
|
||||
return {
|
||||
@@ -519,6 +485,7 @@ class MessageService:
|
||||
"decrypted_failed": 0,
|
||||
"saved_count": 0,
|
||||
"duplicates_skipped": 0,
|
||||
"seq": seq,
|
||||
"max_seq": seq,
|
||||
"next_seq": seq,
|
||||
},
|
||||
@@ -552,6 +519,7 @@ class MessageService:
|
||||
"saved_count": db_stats["saved"],
|
||||
"duplicates_skipped": db_stats["skipped_duplicates"],
|
||||
"failed_to_save": db_stats["failed"],
|
||||
"seq": seq,
|
||||
"max_seq": max_seq,
|
||||
"next_seq": max_seq + 1 if total_fetched > 0 else seq,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user