You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.8 KiB
170 lines
4.8 KiB
"""FastAPI 主入口 - 应用生命周期、中间件、路由注册"""
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse, RedirectResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from app.config import settings
|
|
from app.database import init_db
|
|
from app.middleware import GlobalExceptionMiddleware, RequestLoggingMiddleware
|
|
from app.services.redis_cache import redis_cache
|
|
|
|
# 配置日志
|
|
logging.basicConfig(
|
|
level=getattr(logging, settings.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("初始化数据库...")
|
|
await init_db()
|
|
|
|
logger.info("连接 Redis 缓存...")
|
|
await redis_cache.connect()
|
|
|
|
# 创建默认管理员账户
|
|
from app.auth_service import create_default_admin
|
|
from app.database import async_session_factory
|
|
|
|
async with async_session_factory() as admin_db:
|
|
await create_default_admin(admin_db)
|
|
|
|
logger.info("启动定时调度器...")
|
|
from app.services.scheduler import start_scheduler
|
|
await start_scheduler()
|
|
|
|
# 恢复已启用的任务
|
|
from app.services.cache import list_tasks
|
|
from app.services.scheduler import add_job
|
|
|
|
async with async_session_factory() as db:
|
|
tasks = await list_tasks(db)
|
|
enabled_tasks = [t for t in tasks if t.enabled]
|
|
for t in enabled_tasks:
|
|
await add_job(t.id, t.interval_seconds)
|
|
logger.info("恢复定时任务: %s (每 %ds)", t.symbol, t.interval_seconds)
|
|
|
|
logger.info("数据缓冲平台 V2 已启动 http://%s:%d", settings.host, settings.port)
|
|
|
|
yield
|
|
|
|
# 关闭时
|
|
logger.info("停止调度器...")
|
|
from app.services.scheduler import stop_scheduler
|
|
await stop_scheduler()
|
|
|
|
logger.info("断开 Redis 连接...")
|
|
await redis_cache.disconnect()
|
|
|
|
|
|
app = FastAPI(
|
|
title="数据缓冲平台 V2",
|
|
description="期货智析系统 - 重构版本",
|
|
version="2.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# ==================== 中间件 ====================
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.add_middleware(RequestLoggingMiddleware)
|
|
app.add_middleware(GlobalExceptionMiddleware)
|
|
|
|
# ==================== 静态文件 ====================
|
|
|
|
STATIC_DIR = Path(__file__).resolve().parent.parent / "app" / "static"
|
|
if not STATIC_DIR.exists():
|
|
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("/")
|
|
async def root():
|
|
"""根路径重定向到登录页。"""
|
|
return RedirectResponse(url="/login")
|
|
|
|
|
|
@app.get("/login")
|
|
async def login_page():
|
|
"""登录页面。"""
|
|
return FileResponse(str(STATIC_DIR / "login.html"))
|
|
|
|
|
|
@app.get("/role-select")
|
|
async def role_select_page():
|
|
"""角色选择页面。"""
|
|
return FileResponse(str(STATIC_DIR / "role_select.html"))
|
|
|
|
|
|
@app.get("/ui")
|
|
async def ui_page():
|
|
"""品种配置管理页面。"""
|
|
return FileResponse(str(STATIC_DIR / "index.html"))
|
|
|
|
|
|
@app.get("/futures-analysis")
|
|
async def futures_analysis_page():
|
|
"""期货智析页面。"""
|
|
return FileResponse(str(STATIC_DIR / "futures_analysis.html"))
|
|
|
|
|
|
@app.get("/ai-config")
|
|
async def ai_config_page():
|
|
"""AI模型配置页面。"""
|
|
return FileResponse(str(STATIC_DIR / "ai_config.html"))
|
|
|
|
|
|
@app.get("/review-plan")
|
|
async def review_plan_page():
|
|
"""复盘计划页面。"""
|
|
return FileResponse(str(STATIC_DIR / "review_plan.html"))
|
|
|
|
|
|
# ==================== API 路由注册 ====================
|
|
|
|
from app.api import ai_config, auth, config, data, futures, review, tasks, trade_review # noqa: E402
|
|
|
|
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.router, prefix="/api/v1")
|
|
app.include_router(ai_config.router, prefix="/api/v1")
|
|
app.include_router(review.router, prefix="/api/v1")
|
|
app.include_router(trade_review.router, prefix="/api/v1")
|
|
|
|
|
|
# ==================== 健康检查 ====================
|
|
|
|
|
|
@app.get("/api/v1/health")
|
|
async def health():
|
|
"""健康检查端点。"""
|
|
return {"status": "ok", "service": "market-data-buffer-v2", "version": "2.0.0"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=True)
|