from fastapi import FastAPI, HTTPException, Query from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pathlib import Path import logging from collections import deque # 引入调度器 from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger # 复用现有manager from core.manager import StrategyManager # ================== 初始化 ================== app = FastAPI(title="策略控制台") manager = StrategyManager() # 初始化调度器 scheduler = AsyncIOScheduler() # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # ================== 定时任务逻辑 ================== def scheduled_restart_task(): """ 定时任务:重启所有正在运行的策略 """ logger.info("⏰ [定时任务] 触发自动重启流程...") # 获取当前所有策略状态 status = manager.get_status() running_strategies = [ name for name, info in status['strategies'].items() if info['status'] == 'running' ] if not running_strategies: logger.info("⏰ [定时任务] 当前无运行中的策略,无需重启") return logger.info(f"⏰ [定时任务] 即将重启以下策略: {running_strategies}") for name in running_strategies: try: # 调用 manager 的重启逻辑 (包含 stop -> sleep -> start) manager.restart_strategy(name) logger.info(f"✅ [定时任务] {name} 重启成功") except Exception as e: logger.error(f"❌ [定时任务] {name} 重启失败: {e}") logger.info("⏰ [定时任务] 自动重启流程结束") # ================== FastAPI 事件钩子 ================== @app.on_event("startup") async def start_scheduler(): """服务启动时,加载定时任务""" # 任务 1: 每天 08:55 scheduler.add_job( scheduled_restart_task, CronTrigger(hour=8, minute=55), id="restart_morning", replace_existing=True ) # 任务 2: 每天 20:55 scheduler.add_job( scheduled_restart_task, CronTrigger(hour=20, minute=55), id="restart_evening", replace_existing=True ) scheduler.start() logger.info("📅 定时任务调度器已启动 (计划时间: 08:55, 20:55)") @app.on_event("shutdown") async def stop_scheduler(): """服务关闭时停止调度器""" scheduler.shutdown() # ================== 原有 REST API (保持不变) ================== @app.get("/api/status") def get_status(): return manager.get_status() @app.post("/api/strategy/{name}/start") def start_strategy(name: str): if manager.start_strategy(name): return {"success": True} raise HTTPException(400, "启动失败") @app.post("/api/strategy/{name}/stop") def stop_strategy(name: str): if manager.stop_strategy(name): return {"success": True} raise HTTPException(400, "停止失败") @app.post("/api/strategy/{name}/restart") def restart_strategy(name: str): # 修复了之前提到的返回值问题 if manager.restart_strategy(name): return {"success": True} raise HTTPException(400, "重启失败,请检查日志") @app.get("/api/logs/{name}") def get_logs(name: str, lines: int = Query(50, le=500)): try: if '_' not in name: return {"lines": []} strategy_name, symbol = name.split('_', 1) log_file = Path(f"logs/{strategy_name}/{symbol}.log") if not log_file.exists(): return {"lines": ["日志文件尚未生成"]} # 修复编码和读取 with open(log_file, 'r', encoding='utf-8', errors='replace') as f: last_lines = deque(f, maxlen=lines) return {"lines": [line.rstrip() for line in last_lines]} except Exception as e: raise HTTPException(500, f"读取日志失败: {e}") # ================== 静态文件挂载 ================== app.mount("/static", StaticFiles(directory="frontend/dist"), name="static") @app.get("/") def serve_frontend(): return FileResponse("frontend/dist/index.html")