""" 数据缓冲平台 - FastAPI 主入口 """ import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, RedirectResponse from app.database import engine, Base from app.user_models import Base as UserBase from app.config import HOST, PORT, LOG_LEVEL from app.api import data, tasks, config, futures_analysis, ai_config, auth from app.services.scheduler import start_scheduler, stop_scheduler # 配置日志 logging.basicConfig( level=getattr(logging, LOG_LEVEL.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" # 启动时:建表 + 启动调度器 logger.info("创建数据库表...") Base.metadata.create_all(bind=engine) UserBase.metadata.create_all(bind=engine) from app.analysis_db import init_analysis_db init_analysis_db() logger.info("期货智析数据库初始化完成") # 创建默认管理员账户 from app.database import SessionLocal from app import auth_service db = SessionLocal() try: auth_service.create_default_admin(db) finally: db.close() logger.info("启动定时调度器...") start_scheduler() # 恢复已启用的任务 from app.services.cache import list_tasks from app.services.scheduler import add_job db = SessionLocal() try: enabled_tasks = [t for t in list_tasks(db) if t.enabled] for t in enabled_tasks: add_job(t.id, t.interval_seconds) logger.info(f"恢复定时任务: {t.symbol} (每 {t.interval_seconds}s)") finally: db.close() logger.info(f"数据缓冲平台已启动 http://{HOST}:{PORT}") yield # 关闭时 logger.info("停止调度器...") stop_scheduler() app = FastAPI( title="数据缓冲平台", description="期货/股票行情数据缓存与定时采集平台", version="1.0.0", lifespan=lifespan, ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 静态文件服务(禁用缓存以确保前端更新立即生效) STATIC_DIR = Path(__file__).resolve().parent / "static" STATIC_DIR.mkdir(parents=True, exist_ok=True) app.mount("/static", StaticFiles(directory=str(STATIC_DIR), html=True), name="static") # 登录页面(无需认证) @app.get("/login") def login_page(): """登录页面""" return FileResponse(str(STATIC_DIR / "login.html")) # 角色选择页面(需要管理员权限) @app.get("/role-select") def role_select_page(): """角色选择页面""" return FileResponse(str(STATIC_DIR / "role_select.html")) # 品种配置管理页面(需要管理员权限) @app.get("/ui") def ui_page(): """品种配置管理页面""" return FileResponse(str(STATIC_DIR / "index.html")) # 注册路由 app.include_router(auth.router, prefix="/api/v1") app.include_router(data.router, prefix="/api/v1") app.include_router(tasks.router, prefix="/api/v1") app.include_router(config.router, prefix="/api/v1") app.include_router(futures_analysis.router, prefix="/api/v1") app.include_router(ai_config.router, prefix="/api/v1") @app.get("/futures-analysis") def futures_analysis_page(): """期货智析页面""" return FileResponse(str(STATIC_DIR / "futures_analysis.html")) @app.get("/ai-config") def ai_config_page(): """AI模型配置页面""" return FileResponse(str(STATIC_DIR / "ai_config.html")) @app.get("/api/v1/health") def health(): return {"status": "ok", "service": "market-data-buffer"} # 根路径重定向到登录页 @app.get("/") def root(): """根路径重定向到登录页""" return RedirectResponse(url="/login") if __name__ == "__main__": import uvicorn uvicorn.run("app.main:app", host=HOST, port=PORT, reload=True)