247 lines
9.6 KiB
Python
247 lines
9.6 KiB
Python
import os
|
||
from collections import deque
|
||
from pathlib import Path
|
||
|
||
import requests
|
||
import yaml
|
||
from fastapi import APIRouter, Body, Depends, HTTPException, Request
|
||
|
||
from server.models.user_model import User
|
||
from server.utils.auth_middleware import get_admin_user, get_superadmin_user
|
||
from src import config, graph_base, knowledge_base
|
||
from src.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")
|
||
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.post("/restart")
|
||
async def restart_system(current_user: User = Depends(get_superadmin_user)):
|
||
"""重启系统(仅超级管理员)"""
|
||
graph_base.start()
|
||
return {"message": "系统已重启"}
|
||
|
||
|
||
@system.get("/logs")
|
||
def get_system_logs(current_user: User = Depends(get_admin_user)):
|
||
"""获取系统日志"""
|
||
try:
|
||
from src.utils.logging_config import LOG_FILE
|
||
|
||
with open(LOG_FILE) as f:
|
||
last_lines = deque(f, maxlen=1000)
|
||
|
||
log = "".join(last_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)}")
|
||
|
||
|
||
# =============================================================================
|
||
# === 信息管理分组 ===
|
||
# =============================================================================
|
||
|
||
|
||
def load_info_config():
|
||
"""加载信息配置文件"""
|
||
try:
|
||
# 配置文件路径
|
||
brand_file_path = os.environ.get("YUXI_BRAND_FILE_PATH", "src/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("src/static/info.template.yaml")
|
||
|
||
# 读取配置文件
|
||
with open(config_path, encoding="utf-8") as file:
|
||
config = yaml.safe_load(file)
|
||
|
||
return config
|
||
|
||
except Exception as e:
|
||
logger.error(f"Failed to load info config: {e}")
|
||
return get_default_info_config()
|
||
|
||
|
||
def get_default_info_config():
|
||
"""获取默认信息配置"""
|
||
return {
|
||
"organization": {"name": "江南语析", "logo": "/favicon.svg", "avatar": "/avatar.jpg"},
|
||
"branding": {
|
||
"name": "Yuxi-Know",
|
||
"title": "Yuxi-Know",
|
||
"subtitle": "大模型驱动的知识库管理工具",
|
||
"description": "结合知识库与知识图谱,提供更准确、更全面的回答",
|
||
},
|
||
"features": ["📚 灵活知识库", "🕸️ 知识图谱集成", "🤖 多模型支持"],
|
||
"footer": {"copyright": "© 江南语析 2025 [WIP] v0.2.0"},
|
||
}
|
||
|
||
|
||
@system.get("/info")
|
||
async def get_info_config():
|
||
"""获取系统信息配置(公开接口,无需认证)"""
|
||
try:
|
||
config = 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 = 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/stats")
|
||
async def get_ocr_stats(current_user: User = Depends(get_admin_user)):
|
||
"""
|
||
获取OCR服务使用统计信息
|
||
返回各个OCR服务的处理统计和性能指标
|
||
"""
|
||
try:
|
||
from src.plugins._ocr import get_ocr_stats
|
||
|
||
stats = get_ocr_stats()
|
||
|
||
return {"status": "success", "stats": stats, "message": "OCR统计信息获取成功"}
|
||
except Exception as e:
|
||
logger.error(f"获取OCR统计信息失败: {str(e)}")
|
||
return {"status": "error", "stats": {}, "message": f"获取OCR统计信息失败: {str(e)}"}
|
||
|
||
|
||
@system.get("/ocr/health")
|
||
async def check_ocr_services_health(current_user: User = Depends(get_admin_user)):
|
||
"""
|
||
检查所有OCR服务的健康状态
|
||
返回各个OCR服务的可用性信息
|
||
"""
|
||
health_status = {
|
||
"rapid_ocr": {"status": "unknown", "message": ""},
|
||
"mineru_ocr": {"status": "unknown", "message": ""},
|
||
"paddlex_ocr": {"status": "unknown", "message": ""},
|
||
}
|
||
|
||
# 检查 RapidOCR (ONNX) 模型
|
||
try:
|
||
model_dir_root = (
|
||
os.getenv("MODEL_DIR") if not os.getenv("RUNNING_IN_DOCKER") else os.getenv("MODEL_DIR_IN_DOCKER")
|
||
)
|
||
model_dir = os.path.join(model_dir_root, "SWHL/RapidOCR")
|
||
det_model_path = os.path.join(model_dir, "PP-OCRv4/ch_PP-OCRv4_det_infer.onnx")
|
||
rec_model_path = os.path.join(model_dir, "PP-OCRv4/ch_PP-OCRv4_rec_infer.onnx")
|
||
|
||
if os.path.exists(model_dir) and os.path.exists(det_model_path) and os.path.exists(rec_model_path):
|
||
# 尝试初始化RapidOCR
|
||
from rapidocr_onnxruntime import RapidOCR
|
||
|
||
test_ocr = RapidOCR(det_box_thresh=0.3, det_model_path=det_model_path, rec_model_path=rec_model_path) # noqa: F841
|
||
health_status["rapid_ocr"]["status"] = "healthy"
|
||
health_status["rapid_ocr"]["message"] = "RapidOCR模型已加载"
|
||
else:
|
||
health_status["rapid_ocr"]["status"] = "unavailable"
|
||
health_status["rapid_ocr"]["message"] = f"模型文件不存在: {model_dir}"
|
||
except Exception as e:
|
||
health_status["rapid_ocr"]["status"] = "error"
|
||
health_status["rapid_ocr"]["message"] = f"RapidOCR初始化失败: {str(e)}"
|
||
|
||
# 检查 MinerU OCR 服务
|
||
try:
|
||
mineru_uri = os.getenv("MINERU_OCR_URI", "http://localhost:30000")
|
||
health_url = f"{mineru_uri}/health"
|
||
|
||
response = requests.get(health_url, timeout=5)
|
||
if response.status_code == 200:
|
||
health_status["mineru_ocr"]["status"] = "healthy"
|
||
health_status["mineru_ocr"]["message"] = f"MinerU服务运行正常 ({mineru_uri})"
|
||
else:
|
||
health_status["mineru_ocr"]["status"] = "unhealthy"
|
||
health_status["mineru_ocr"]["message"] = f"MinerU服务响应异常({mineru_uri}): {response.status_code}"
|
||
except requests.exceptions.ConnectionError:
|
||
health_status["mineru_ocr"]["status"] = "unavailable"
|
||
health_status["mineru_ocr"]["message"] = "MinerU服务无法连接,请检查服务是否启动"
|
||
except requests.exceptions.Timeout:
|
||
health_status["mineru_ocr"]["status"] = "timeout"
|
||
health_status["mineru_ocr"]["message"] = "MinerU服务连接超时"
|
||
except Exception as e:
|
||
health_status["mineru_ocr"]["status"] = "error"
|
||
health_status["mineru_ocr"]["message"] = f"MinerU服务检查失败: {str(e)}"
|
||
|
||
# 检查 PaddleX OCR 服务
|
||
try:
|
||
paddlex_uri = os.getenv("PADDLEX_URI", "http://localhost:8080")
|
||
health_url = f"{paddlex_uri}/health"
|
||
|
||
response = requests.get(health_url, timeout=5)
|
||
if response.status_code == 200:
|
||
health_status["paddlex_ocr"]["status"] = "healthy"
|
||
health_status["paddlex_ocr"]["message"] = f"PaddleX服务运行正常({paddlex_uri})"
|
||
else:
|
||
health_status["paddlex_ocr"]["status"] = "unhealthy"
|
||
health_status["paddlex_ocr"]["message"] = f"PaddleX服务响应异常({paddlex_uri}): {response.status_code}"
|
||
except requests.exceptions.ConnectionError:
|
||
health_status["paddlex_ocr"]["status"] = "unavailable"
|
||
health_status["paddlex_ocr"]["message"] = "PaddleX服务无法连接,请检查服务是否启动({paddlex_uri})"
|
||
except requests.exceptions.Timeout:
|
||
health_status["paddlex_ocr"]["status"] = "timeout"
|
||
health_status["paddlex_ocr"]["message"] = "PaddleX服务连接超时({paddlex_uri})"
|
||
except Exception as e:
|
||
health_status["paddlex_ocr"]["status"] = "error"
|
||
health_status["paddlex_ocr"]["message"] = f"PaddleX服务检查失败: {str(e)}"
|
||
|
||
# 计算整体健康状态
|
||
overall_status = "healthy" if any(svc["status"] == "healthy" for svc in health_status.values()) else "unhealthy"
|
||
|
||
return {"overall_status": overall_status, "services": health_status, "message": "OCR服务健康检查完成"}
|