|
|
|
|
"""
|
|
|
|
|
数据缓冲平台 - FastAPI 主入口
|
|
|
|
|
"""
|
|
|
|
|
import logging
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
from fastapi.responses import FileResponse
|
|
|
|
|
|
|
|
|
|
from app.database import engine, Base
|
|
|
|
|
from app.config import HOST, PORT, LOG_LEVEL
|
|
|
|
|
from app.api import data, tasks, config, futures_analysis, ai_config
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
logger.info("启动定时调度器...")
|
|
|
|
|
start_scheduler()
|
|
|
|
|
|
|
|
|
|
# 恢复已启用的任务
|
|
|
|
|
from app.database import SessionLocal
|
|
|
|
|
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)), name="static")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/ui")
|
|
|
|
|
def ui_page():
|
|
|
|
|
"""品种配置管理页面"""
|
|
|
|
|
return FileResponse(str(STATIC_DIR / "index.html"))
|
|
|
|
|
|
|
|
|
|
# 注册路由
|
|
|
|
|
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 {
|
|
|
|
|
"message": "数据缓冲平台 API",
|
|
|
|
|
"docs": "/docs",
|
|
|
|
|
"health": "/api/v1/health",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import uvicorn
|
|
|
|
|
uvicorn.run("app.main:app", host=HOST, port=PORT, reload=True)
|