添加独立的 WebUI 服务器支持,重构相关配置和启动逻辑
This commit is contained in:
148
src/webui/webui_server.py
Normal file
148
src/webui/webui_server.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""独立的 WebUI 服务器 - 运行在 0.0.0.0:8001"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from uvicorn import Config, Server as UvicornServer
|
||||
from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("webui_server")
|
||||
|
||||
|
||||
class WebUIServer:
|
||||
"""独立的 WebUI 服务器"""
|
||||
|
||||
def __init__(self, host: str = "0.0.0.0", port: int = 8001):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.app = FastAPI(title="MaiBot WebUI")
|
||||
self._server = None
|
||||
|
||||
# 显示 Access Token
|
||||
self._show_access_token()
|
||||
|
||||
# 重要:先注册 API 路由,再设置静态文件
|
||||
self._register_api_routes()
|
||||
self._setup_static_files()
|
||||
|
||||
def _show_access_token(self):
|
||||
"""显示 WebUI Access Token"""
|
||||
try:
|
||||
from src.webui.token_manager import get_token_manager
|
||||
|
||||
token_manager = get_token_manager()
|
||||
current_token = token_manager.get_token()
|
||||
logger.info(f"🔑 WebUI Access Token: {current_token}")
|
||||
logger.info("💡 请使用此 Token 登录 WebUI")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 获取 Access Token 失败: {e}")
|
||||
|
||||
def _setup_static_files(self):
|
||||
"""设置静态文件服务"""
|
||||
# 确保正确的 MIME 类型映射
|
||||
mimetypes.init()
|
||||
mimetypes.add_type("application/javascript", ".js")
|
||||
mimetypes.add_type("application/javascript", ".mjs")
|
||||
mimetypes.add_type("text/css", ".css")
|
||||
mimetypes.add_type("application/json", ".json")
|
||||
|
||||
base_dir = Path(__file__).parent.parent.parent
|
||||
static_path = base_dir / "webui" / "dist"
|
||||
|
||||
if not static_path.exists():
|
||||
logger.warning(f"❌ WebUI 静态文件目录不存在: {static_path}")
|
||||
logger.warning("💡 请先构建前端: cd webui && npm run build")
|
||||
return
|
||||
|
||||
if not (static_path / "index.html").exists():
|
||||
logger.warning(f"❌ 未找到 index.html: {static_path / 'index.html'}")
|
||||
logger.warning("💡 请确认前端已正确构建")
|
||||
return
|
||||
|
||||
# 处理 SPA 路由 - 注意:这个路由优先级最低
|
||||
@self.app.get("/{full_path:path}", include_in_schema=False)
|
||||
async def serve_spa(full_path: str):
|
||||
"""服务单页应用 - 只处理非 API 请求"""
|
||||
# 如果是根路径,直接返回 index.html
|
||||
if not full_path or full_path == "/":
|
||||
return FileResponse(static_path / "index.html", media_type="text/html")
|
||||
|
||||
# 检查是否是静态文件
|
||||
file_path = static_path / full_path
|
||||
if file_path.is_file() and file_path.exists():
|
||||
# 自动检测 MIME 类型
|
||||
media_type = mimetypes.guess_type(str(file_path))[0]
|
||||
return FileResponse(file_path, media_type=media_type)
|
||||
|
||||
# 其他路径返回 index.html(SPA 路由)
|
||||
return FileResponse(static_path / "index.html", media_type="text/html")
|
||||
|
||||
logger.info(f"✅ WebUI 静态文件服务已配置: {static_path}")
|
||||
|
||||
def _register_api_routes(self):
|
||||
"""注册所有 WebUI API 路由"""
|
||||
try:
|
||||
# 导入所有 WebUI 路由
|
||||
from src.webui.routes import router as webui_router
|
||||
from src.webui.logs_ws import router as logs_router
|
||||
|
||||
# 注册路由
|
||||
self.app.include_router(webui_router)
|
||||
self.app.include_router(logs_router)
|
||||
|
||||
logger.info("✅ WebUI API 路由已注册")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 注册 WebUI API 路由失败: {e}")
|
||||
|
||||
async def start(self):
|
||||
"""启动服务器"""
|
||||
config = Config(
|
||||
app=self.app,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
log_config=None,
|
||||
access_log=False,
|
||||
)
|
||||
self._server = UvicornServer(config=config)
|
||||
|
||||
logger.info("🌐 WebUI 服务器启动中...")
|
||||
logger.info(f"🌐 访问地址: http://{self.host}:{self.port}")
|
||||
|
||||
try:
|
||||
await self._server.serve()
|
||||
except Exception as e:
|
||||
logger.error(f"❌ WebUI 服务器运行错误: {e}")
|
||||
raise
|
||||
|
||||
async def shutdown(self):
|
||||
"""关闭服务器"""
|
||||
if self._server:
|
||||
logger.info("正在关闭 WebUI 服务器...")
|
||||
self._server.should_exit = True
|
||||
try:
|
||||
await asyncio.wait_for(self._server.shutdown(), timeout=3.0)
|
||||
logger.info("✅ WebUI 服务器已关闭")
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("⚠️ WebUI 服务器关闭超时")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ WebUI 服务器关闭失败: {e}")
|
||||
finally:
|
||||
self._server = None
|
||||
|
||||
|
||||
# 全局 WebUI 服务器实例
|
||||
_webui_server = None
|
||||
|
||||
|
||||
def get_webui_server() -> WebUIServer:
|
||||
"""获取全局 WebUI 服务器实例"""
|
||||
global _webui_server
|
||||
if _webui_server is None:
|
||||
# 从环境变量读取配置
|
||||
host = os.getenv("WEBUI_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("WEBUI_PORT", "8001"))
|
||||
_webui_server = WebUIServer(host=host, port=port)
|
||||
return _webui_server
|
||||
Reference in New Issue
Block a user