refactor: 架构缺陷 6-12 修复

6. SECRET_KEY 从硬编码改为环境变量读取,未设置时自动生成随机值
7. 登录时 session 存储 tenant_id,auth_manager 返回用户的 tenant_id
8. 前端共享状态集中声明并添加注释,标注每个状态由哪个模块管理
9. 数据库启动时自动检测并添加缺失的 tenant_id 列(SQLite ADD COLUMN 迁移)
10. Webhook handler 添加文档说明双通道互斥建议
11. LLM chat_completion 添加自动重试(max_retries=2),服务端错误和超时自动重试
12. 知识库向量化器和 Embedding 禁用日志从 INFO 降为 DEBUG,减少噪音
This commit is contained in:
2026-04-02 22:19:56 +08:00
parent 61ef86d779
commit 587933f668
10 changed files with 99 additions and 33 deletions

View File

@@ -67,6 +67,7 @@ class AuthManager:
name_val = user.name
role_val = user.role
is_active_val = user.is_active
tenant_id_val = user.tenant_id
created_at_val = user.created_at
last_login_val = datetime.now()
@@ -86,6 +87,7 @@ class AuthManager:
'name': name_val,
'role': role_val,
'is_active': is_active_val,
'tenant_id': tenant_id_val,
'created_at': created_at_val,
'last_login': last_login_val
}

View File

@@ -68,6 +68,9 @@ class DatabaseManager:
Base.metadata.create_all(bind=self.engine)
logger.info("数据库初始化成功")
# 运行 schema 迁移(处理字段变更)
self._run_migrations()
# 确保默认租户存在
self._ensure_default_tenant()
@@ -75,6 +78,39 @@ class DatabaseManager:
logger.error(f"数据库初始化失败: {e}")
raise
def _run_migrations(self):
"""运行轻量级 schema 迁移SQLite 兼容)"""
try:
session = self.SessionLocal()
try:
# 检查并添加缺失的列SQLite 支持 ADD COLUMN
from sqlalchemy import inspect, text
inspector = inspect(self.engine)
migrations = [
# (表名, 列名, SQL 类型默认值)
('conversations', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('chat_sessions', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('work_orders', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('knowledge_entries', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('users', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('alerts', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
('analytics', 'tenant_id', "VARCHAR(50) DEFAULT 'default'"),
]
for table_name, col_name, col_type in migrations:
if table_name in inspector.get_table_names():
existing_cols = [c['name'] for c in inspector.get_columns(table_name)]
if col_name not in existing_cols:
session.execute(text(f"ALTER TABLE {table_name} ADD COLUMN {col_name} {col_type}"))
logger.info(f"迁移: {table_name} 添加列 {col_name}")
session.commit()
except Exception as e:
session.rollback()
logger.warning(f"Schema 迁移失败(不影响启动): {e}")
finally:
session.close()
except Exception as e:
logger.warning(f"Schema 迁移检查失败: {e}")
def _ensure_default_tenant(self):
"""确保默认租户记录存在"""
try:

View File

@@ -32,7 +32,7 @@ class EmbeddingClient:
if self.enabled:
logger.info(f"Embedding 客户端初始化: model={self.model_name} (本地模式)")
else:
logger.info("Embedding 功能已禁用,将使用关键词匹配降级")
logger.debug("Embedding 功能已禁用,将使用关键词匹配降级")
def _get_model(self):
"""延迟加载模型(首次调用时下载并加载)"""

View File

@@ -42,38 +42,46 @@ class LLMClient:
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 1000,
max_retries: int = 2,
**kwargs,
) -> Dict[str, Any]:
"""标准聊天补全(非流式)"""
try:
url = f"{self.base_url}/chat/completions"
payload = {
"model": self.model_name,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": False,
}
"""标准聊天补全(非流式),支持自动重试"""
url = f"{self.base_url}/chat/completions"
payload = {
"model": self.model_name,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": False,
}
response = requests.post(
url, headers=self.headers, json=payload, timeout=self.timeout
)
last_error = None
for attempt in range(max_retries + 1):
try:
response = requests.post(
url, headers=self.headers, json=payload, timeout=self.timeout
)
if response.status_code == 200:
return response.json()
elif response.status_code >= 500:
last_error = f"API 服务端错误: {response.status_code}"
logger.warning(f"LLM API 第{attempt+1}次请求失败({response.status_code}){'重试中...' if attempt < max_retries else '放弃'}")
continue
else:
logger.error(f"LLM API 失败: {response.status_code} - {response.text}")
return {"error": f"API请求失败: {response.status_code}"}
if response.status_code == 200:
return response.json()
else:
logger.error(f"LLM API 失败: {response.status_code} - {response.text}")
return {"error": f"API请求失败: {response.status_code}"}
except requests.exceptions.Timeout:
last_error = "请求超时"
logger.warning(f"LLM API 第{attempt+1}次超时,{'重试中...' if attempt < max_retries else '放弃'}")
except requests.exceptions.RequestException as e:
last_error = str(e)
logger.warning(f"LLM API 第{attempt+1}次异常: {e}")
except Exception as e:
logger.error(f"LLM 未知错误: {e}")
return {"error": f"未知错误: {str(e)}"}
except requests.exceptions.Timeout:
logger.error("LLM API 超时")
return {"error": "请求超时"}
except requests.exceptions.RequestException as e:
logger.error(f"LLM API 异常: {e}")
return {"error": f"请求异常: {str(e)}"}
except Exception as e:
logger.error(f"LLM 未知错误: {e}")
return {"error": f"未知错误: {str(e)}"}
return {"error": last_error or "请求失败"}
# ── 流式请求 ──────────────────────────────────────────