# filename: main.py import subprocess import os import logging from collections import deque from pathlib import Path from fastapi import FastAPI, HTTPException, Query from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse # 引入调度器 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__) # ================== 新增:获取 Git 版本信息 ================== def get_git_commit_info(): """ [智能版] 获取 Git 仓库的最新提交信息。 此函数会自动从当前文件位置向上查找项目根目录(.git 文件夹所在位置)。 """ try: # 1. 获取 main.py 所在的目录 script_dir = Path(__file__).resolve().parent # 2. 推断出项目根目录 (即 main.py 所在目录的上一级) project_root = script_dir.parent # 3. 检查项目根目录下是否存在 .git 文件夹 git_dir = project_root / ".git" if not git_dir.is_dir(): return "Git repo not found in parent directory" # 4. 在推断出的项目根目录下执行 git 命令 # 使用 cwd (current working directory) 参数是关键 result = subprocess.run( ['git', 'log', '-1', '--pretty=format:%h - %s (%cr)'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True, encoding='utf-8', cwd=project_root # <--- 在这里指定命令的执行目录 ) return result.stdout.strip() except (FileNotFoundError, subprocess.CalledProcessError) as e: logger.warning(f"无法获取 Git 提交信息: {e}") return "获取 Git 信息失败" # ================== 定时任务逻辑 (保持不变) ================== 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.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(): scheduler.add_job( scheduled_restart_task, CronTrigger(hour=8, minute=58), id="restart_morning", replace_existing=True ) scheduler.add_job( scheduled_restart_task, CronTrigger(hour=20, minute=58), id="restart_evening", replace_existing=True ) scheduler.start() logger.info("📅 定时任务调度器已启动 (计划时间: 08:58, 20:58)") @app.on_event("shutdown") async def stop_scheduler(): scheduler.shutdown() # ================== API 路由 ================== @app.get("/api/status") def get_status(): # [核心修改] 在返回状态的同时,注入 Git 版本信息 status_data = manager.get_status() status_data['git_info'] = get_git_commit_info() return status_data @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}") # ================== 静态文件挂载 (保持不变) ================== # 注意: 这里的路径是示例,请确保它与你的项目结构匹配 # 假设你的HTML文件在 "frontend/" 目录下 app.mount("/static", StaticFiles(directory="frontend/dist"), name="static") @app.get("/") def serve_frontend(): return FileResponse("frontend/dist/index.html")