209 lines
6.1 KiB
Python
209 lines
6.1 KiB
Python
# ///
|
|
# main.py
|
|
# 描述:FastAPI主入口,启动中控系统服务
|
|
# 作者:AI Generated
|
|
# 创建日期:2026-04-05
|
|
# ///
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import RedirectResponse, JSONResponse
|
|
|
|
from config import get_settings, load_config_from_file, save_config_to_file
|
|
from api import nodes, messages, external, plugins, callback, webhook
|
|
from plugins.builtin import register_builtin_plugins
|
|
from plugins.plugin_loader import hot_plugin_loader, create_sample_plugin
|
|
from services.node_manager import node_manager
|
|
from services.monitor_service import monitor_service
|
|
from services.log_service import log_service
|
|
from services.scheduler_service import scheduler_service
|
|
|
|
settings = get_settings()
|
|
|
|
app = FastAPI(
|
|
title=settings.app_name,
|
|
description="WXAuto中控系统 - 多节点微信控制平台",
|
|
version="1.0.0",
|
|
docs_url="/docs",
|
|
redoc_url="/redoc"
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
log_service.error(f"Global exception: {str(exc)}", "Error")
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"success": False, "error": str(exc)}
|
|
)
|
|
|
|
|
|
app.include_router(nodes.router, prefix=settings.api_prefix)
|
|
app.include_router(messages.router, prefix=settings.api_prefix)
|
|
app.include_router(external.router, prefix=settings.api_prefix)
|
|
app.include_router(plugins.router, prefix=settings.api_prefix)
|
|
app.include_router(callback.router, prefix=settings.api_prefix)
|
|
app.include_router(webhook.router, prefix=settings.api_prefix)
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
load_config_from_file("config.json")
|
|
register_builtin_plugins()
|
|
|
|
from database_service import init_database, get_db_service
|
|
from config import get_settings
|
|
settings = get_settings()
|
|
init_database(settings.database.url)
|
|
log_service.info("Database initialized", "Startup")
|
|
|
|
db_service = get_db_service()
|
|
if db_service:
|
|
for node in db_service.get_all_nodes():
|
|
await node_manager.add_node(
|
|
node_id=node.node_id,
|
|
name=node.name,
|
|
api_url=node.api_url,
|
|
api_key=node.api_key,
|
|
description=node.description or "",
|
|
group=node.group or "default"
|
|
)
|
|
log_service.info(f"Loaded {len(db_service.get_all_nodes())} nodes from database", "Startup")
|
|
|
|
for plugin in db_service.get_all_plugin_configs():
|
|
from plugins.base import PluginRegistry
|
|
p = PluginRegistry.get(plugin.plugin_name)
|
|
if p:
|
|
import json
|
|
config = json.loads(plugin.config_json) if plugin.config_json else {}
|
|
p.initialize(config)
|
|
if plugin.enabled:
|
|
p.enable()
|
|
else:
|
|
p.disable()
|
|
log_service.info("Plugins loaded from database", "Startup")
|
|
|
|
from services.plugin_notification_service import plugin_notification_service
|
|
plugin_notification_service.initialize()
|
|
log_service.info("Plugin notification service initialized", "Startup")
|
|
|
|
await monitor_service.start()
|
|
await scheduler_service.start()
|
|
hot_plugin_loader.start()
|
|
create_sample_plugin()
|
|
|
|
print(f"[{settings.app_name}] Started successfully")
|
|
print(f"API Documentation: http://{settings.host}:{settings.port}/docs")
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
await monitor_service.stop()
|
|
await scheduler_service.stop()
|
|
hot_plugin_loader.stop()
|
|
save_config_to_file("config.json")
|
|
print(f"[{settings.app_name}] Shutdown, config saved")
|
|
|
|
|
|
@app.get("/", summary="首页")
|
|
async def root():
|
|
return RedirectResponse(url="/static/index.html")
|
|
|
|
|
|
@app.get("/health", summary="健康检查")
|
|
async def health():
|
|
return {"status": "ok", "app": settings.app_name}
|
|
|
|
|
|
@app.get("/api/v1/config/monitor", summary="获取监控配置")
|
|
async def get_monitor_config():
|
|
return {
|
|
"success": True,
|
|
"data": settings.monitor.model_dump()
|
|
}
|
|
|
|
|
|
@app.post("/api/v1/config/monitor", summary="更新监控配置")
|
|
async def update_monitor_config(request: dict):
|
|
from config import MonitorConfig
|
|
global settings
|
|
settings.monitor = MonitorConfig(
|
|
enabled=request.get("enabled", True),
|
|
check_interval=request.get("check_interval", 60)
|
|
)
|
|
save_config_to_file()
|
|
return {"success": True, "message": "Monitor config updated"}
|
|
|
|
|
|
@app.get("/api/v1/monitor/status", summary="获取监控状态")
|
|
async def get_monitor_status():
|
|
return {
|
|
"success": True,
|
|
"data": monitor_service.get_all_status()
|
|
}
|
|
|
|
|
|
@app.get("/api/v1/logs/recent", summary="获取最近日志")
|
|
async def get_recent_logs(count: int = 50):
|
|
return {
|
|
"success": True,
|
|
"data": log_service.get_recent_logs(count)
|
|
}
|
|
|
|
|
|
@app.get("/api/v1/logs/stream", summary="日志流")
|
|
async def log_stream():
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
async def event_generator():
|
|
queue = await log_service.subscribe()
|
|
try:
|
|
while True:
|
|
log_entry = await queue.get()
|
|
yield f"data: {json.dumps(log_entry)}\n\n"
|
|
except Exception:
|
|
log_service.unsubscribe(queue)
|
|
|
|
return StreamingResponse(
|
|
event_generator(),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"Connection": "keep-alive",
|
|
"X-Accel-Buffering": "no"
|
|
}
|
|
)
|
|
|
|
|
|
static_path = os.path.join(os.path.dirname(__file__), "static")
|
|
if os.path.exists(static_path):
|
|
app.mount("/static", StaticFiles(directory=static_path), name="static")
|
|
|
|
|
|
def main():
|
|
import uvicorn
|
|
uvicorn.run(
|
|
"main:app",
|
|
host=settings.host,
|
|
port=settings.port,
|
|
reload=settings.debug
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|