320 lines
12 KiB
Python
320 lines
12 KiB
Python
import os
|
||
import aiofiles
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||
|
||
from yuxi.storage.postgres.models_business import User
|
||
from server.utils.auth_middleware import get_admin_user
|
||
from yuxi import config
|
||
from yuxi.models.chat import test_chat_model_status, test_all_chat_models_status
|
||
from yuxi.utils.logging_config import logger
|
||
|
||
system = APIRouter(prefix="/system", tags=["system"])
|
||
|
||
# =============================================================================
|
||
# === 健康检查分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
@system.get("/health")
|
||
async def health_check():
|
||
"""系统健康检查接口(公开接口)"""
|
||
return {"status": "ok", "message": "服务正常运行"}
|
||
|
||
|
||
# =============================================================================
|
||
# === 配置管理分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
@system.get("/config")
|
||
async def get_config(current_user: User = Depends(get_admin_user)):
|
||
"""获取系统配置"""
|
||
return config.dump_config()
|
||
|
||
|
||
@system.post("/config")
|
||
async def update_config_single(key=Body(...), value=Body(...), current_user: User = Depends(get_admin_user)) -> dict:
|
||
"""更新单个配置项"""
|
||
config[key] = value
|
||
config.save()
|
||
return config.dump_config()
|
||
|
||
|
||
@system.post("/config/update")
|
||
async def update_config_batch(items: dict = Body(...), current_user: User = Depends(get_admin_user)) -> dict:
|
||
"""批量更新配置项"""
|
||
config.update(items)
|
||
config.save()
|
||
return config.dump_config()
|
||
|
||
|
||
@system.get("/logs")
|
||
async def get_system_logs(levels: str | None = None, current_user: User = Depends(get_admin_user)):
|
||
"""获取系统日志
|
||
|
||
Args:
|
||
levels: 可选的日志级别过滤,多个级别用逗号分隔,如 "INFO,ERROR,DEBUG,WARNING"
|
||
"""
|
||
try:
|
||
from yuxi.utils.logging_config import LOG_FILE
|
||
|
||
# 解析日志级别过滤条件
|
||
level_filter = None
|
||
if levels:
|
||
level_filter = set(level.strip().upper() for level in levels.split(",") if level.strip())
|
||
|
||
async with aiofiles.open(LOG_FILE) as f:
|
||
# 读取最后1000行
|
||
lines = []
|
||
async for line in f:
|
||
filtered_line = line.rstrip("\n\r")
|
||
# 如果指定了日志级别过滤,则按级别过滤
|
||
if level_filter:
|
||
# 日志格式: 2025-03-10 08:26:37,269 - INFO - module - message
|
||
# 提取日志级别
|
||
parts = filtered_line.split(" - ")
|
||
if len(parts) >= 2 and parts[1].strip() in level_filter:
|
||
lines.append(filtered_line + "\n")
|
||
# 继续读取以保持行数统计准确
|
||
if len(lines) > 1000:
|
||
lines.pop(0)
|
||
else:
|
||
lines.append(filtered_line + "\n")
|
||
if len(lines) > 1000:
|
||
lines.pop(0)
|
||
|
||
log = "".join(lines)
|
||
return {"log": log, "message": "success", "log_file": LOG_FILE}
|
||
except Exception as e:
|
||
logger.error(f"获取系统日志失败: {e}")
|
||
raise HTTPException(status_code=500, detail=f"获取系统日志失败: {str(e)}")
|
||
|
||
|
||
# =============================================================================
|
||
# === 信息管理分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
async def load_info_config():
|
||
"""加载信息配置文件"""
|
||
try:
|
||
# 配置文件路径
|
||
brand_file_path = os.environ.get("YUXI_BRAND_FILE_PATH", "package/yuxi/config/static/info.local.yaml")
|
||
config_path = Path(brand_file_path)
|
||
|
||
# 检查文件是否存在
|
||
if not config_path.exists():
|
||
logger.debug(f"The config file {config_path} does not exist, using default config")
|
||
config_path = Path("package/yuxi/config/static/info.template.yaml")
|
||
|
||
# 异步读取配置文件
|
||
async with aiofiles.open(config_path, encoding="utf-8") as file:
|
||
content = await file.read()
|
||
config = yaml.safe_load(content)
|
||
|
||
return config
|
||
|
||
except Exception as e:
|
||
logger.error(f"Failed to load info config: {e}")
|
||
return {}
|
||
|
||
|
||
@system.get("/info")
|
||
async def get_info_config():
|
||
"""获取系统信息配置(公开接口,无需认证)"""
|
||
try:
|
||
config = await load_info_config()
|
||
return {"success": True, "data": config}
|
||
except Exception as e:
|
||
logger.error(f"获取信息配置失败: {e}")
|
||
raise HTTPException(status_code=500, detail="获取信息配置失败")
|
||
|
||
|
||
@system.post("/info/reload")
|
||
async def reload_info_config(current_user: User = Depends(get_admin_user)):
|
||
"""重新加载信息配置"""
|
||
try:
|
||
config = await load_info_config()
|
||
return {"success": True, "message": "配置重新加载成功", "data": config}
|
||
except Exception as e:
|
||
logger.error(f"重新加载信息配置失败: {e}")
|
||
raise HTTPException(status_code=500, detail="重新加载信息配置失败")
|
||
|
||
|
||
# =============================================================================
|
||
# === OCR服务分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
@system.get("/ocr/health")
|
||
async def check_ocr_services_health(current_user: User = Depends(get_admin_user)):
|
||
"""
|
||
检查所有OCR服务的健康状态
|
||
返回各个OCR服务的可用性信息
|
||
"""
|
||
from yuxi.plugins.parser.factory import DocumentProcessorFactory
|
||
|
||
try:
|
||
# 使用统一的健康检查接口
|
||
health_status = await DocumentProcessorFactory.check_all_health_async()
|
||
|
||
# 转换为旧格式以保持API兼容性
|
||
formatted_status = {}
|
||
for service_name, health_info in health_status.items():
|
||
formatted_status[service_name] = {
|
||
"status": health_info.get("status", "unknown"),
|
||
"message": health_info.get("message", ""),
|
||
"details": health_info.get("details", {}),
|
||
}
|
||
|
||
# 计算整体健康状态
|
||
overall_status = (
|
||
"healthy" if any(svc["status"] == "healthy" for svc in formatted_status.values()) else "unhealthy"
|
||
)
|
||
|
||
return {
|
||
"overall_status": overall_status,
|
||
"services": formatted_status,
|
||
"message": "OCR服务健康检查完成",
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"OCR健康检查失败: {str(e)}")
|
||
return {
|
||
"overall_status": "error",
|
||
"services": {},
|
||
"message": f"OCR健康检查失败: {str(e)}",
|
||
}
|
||
|
||
|
||
# =============================================================================
|
||
# === 聊天模型状态检查分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
@system.get("/chat-models/status")
|
||
async def get_chat_model_status(provider: str, model_name: str, current_user: User = Depends(get_admin_user)):
|
||
"""获取指定聊天模型的状态"""
|
||
logger.debug(f"Checking chat model status: {provider}/{model_name}")
|
||
try:
|
||
status = await test_chat_model_status(provider, model_name)
|
||
return {"status": status, "message": "success"}
|
||
except Exception as e:
|
||
logger.error(f"获取聊天模型状态失败 {provider}/{model_name}: {e}")
|
||
return {
|
||
"message": f"获取聊天模型状态失败: {e}",
|
||
"status": {"provider": provider, "model_name": model_name, "status": "error", "message": str(e)},
|
||
}
|
||
|
||
|
||
@system.get("/chat-models/all/status")
|
||
async def get_all_chat_models_status(current_user: User = Depends(get_admin_user)):
|
||
"""获取所有聊天模型的状态"""
|
||
logger.debug("Checking all chat models status")
|
||
try:
|
||
status = await test_all_chat_models_status()
|
||
return {"status": status, "message": "success"}
|
||
except Exception as e:
|
||
logger.error(f"获取所有聊天模型状态失败: {e}")
|
||
return {"message": f"获取所有聊天模型状态失败: {e}", "status": {"models": {}, "total": 0, "available": 0}}
|
||
|
||
|
||
# =============================================================================
|
||
# === 自定义供应商管理分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
@system.get("/custom-providers")
|
||
async def get_custom_providers(current_user: User = Depends(get_admin_user)):
|
||
"""获取所有自定义供应商"""
|
||
try:
|
||
custom_providers = config.get_custom_providers()
|
||
return {
|
||
"providers": {provider: info.model_dump() for provider, info in custom_providers.items()},
|
||
"message": "success",
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"获取自定义供应商失败: {e}")
|
||
raise HTTPException(status_code=500, detail=f"获取自定义供应商失败: {str(e)}")
|
||
|
||
|
||
@system.post("/custom-providers")
|
||
async def add_custom_provider(
|
||
provider_id: str = Body(..., description="供应商ID"),
|
||
provider_data: dict = Body(..., description="供应商配置数据"),
|
||
current_user: User = Depends(get_admin_user),
|
||
):
|
||
"""添加自定义供应商"""
|
||
try:
|
||
success = config.add_custom_provider(provider_id, provider_data)
|
||
if success:
|
||
return {"message": f"自定义供应商 {provider_id} 添加成功"}
|
||
else:
|
||
raise HTTPException(status_code=400, detail=f"供应商ID {provider_id} 已存在,请使用其他ID")
|
||
except Exception as e:
|
||
logger.error(f"添加自定义供应商失败 {provider_id}: {e}")
|
||
raise HTTPException(status_code=500, detail=f"添加自定义供应商失败: {str(e)}")
|
||
|
||
|
||
@system.put("/custom-providers/{provider_id}")
|
||
async def update_custom_provider(
|
||
provider_id: str,
|
||
provider_data: dict = Body(..., description="供应商配置数据"),
|
||
current_user: User = Depends(get_admin_user),
|
||
):
|
||
"""更新自定义供应商"""
|
||
try:
|
||
success = config.update_custom_provider(provider_id, provider_data)
|
||
if success:
|
||
return {"message": f"自定义供应商 {provider_id} 更新成功"}
|
||
else:
|
||
raise HTTPException(status_code=404, detail=f"自定义供应商 {provider_id} 不存在或更新失败")
|
||
except Exception as e:
|
||
logger.error(f"更新自定义供应商失败 {provider_id}: {e}")
|
||
raise HTTPException(status_code=500, detail=f"更新自定义供应商失败: {str(e)}")
|
||
|
||
|
||
@system.delete("/custom-providers/{provider_id}")
|
||
async def delete_custom_provider(provider_id: str, current_user: User = Depends(get_admin_user)):
|
||
"""删除自定义供应商"""
|
||
try:
|
||
success = config.delete_custom_provider(provider_id)
|
||
if success:
|
||
return {"message": f"自定义供应商 {provider_id} 删除成功"}
|
||
else:
|
||
raise HTTPException(status_code=404, detail=f"自定义供应商 {provider_id} 不存在或删除失败")
|
||
except Exception as e:
|
||
logger.error(f"删除自定义供应商失败 {provider_id}: {e}")
|
||
raise HTTPException(status_code=500, detail=f"删除自定义供应商失败: {str(e)}")
|
||
|
||
|
||
@system.post("/custom-providers/{provider_id}/test")
|
||
async def test_custom_provider(
|
||
provider_id: str, request: dict = Body(..., description="测试请求"), current_user: User = Depends(get_admin_user)
|
||
):
|
||
"""测试自定义供应商连接"""
|
||
try:
|
||
# 从请求中获取model_name
|
||
model_name = request.get("model_name")
|
||
if not model_name:
|
||
raise HTTPException(status_code=400, detail="缺少model_name参数")
|
||
|
||
# 检查供应商是否存在
|
||
if provider_id not in config.model_names:
|
||
raise HTTPException(status_code=404, detail=f"供应商 {provider_id} 不存在")
|
||
|
||
# 测试模型状态
|
||
status = await test_chat_model_status(provider_id, model_name)
|
||
return {"status": status, "message": "测试完成"}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"测试自定义供应商失败 {provider_id}/{model_name}: {e}")
|
||
return {
|
||
"message": f"测试自定义供应商失败: {e}",
|
||
"status": {"provider": provider_id, "model_name": model_name, "status": "error", "message": str(e)},
|
||
}
|