Files
Binance_Altcoin_Monitor/backend/app/main.py
T
2026-05-22 13:30:50 +08:00

131 lines
4.3 KiB
Python

import logging
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from .aggregator import aggregate_period, enrich_snapshot_meta
from .config import ROOT_DIR, settings
from .db import get_latest_snapshot, init_db, log_push
from .periods import get_today_period, get_yesterday_period
from .scheduler import job_finalize_yesterday, job_push_wecom, job_refresh_today, start_scheduler, startup_tasks, stop_scheduler
from .state import get_today_cache
from .wecom import build_markdown, send_wecom_markdown
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
WEB_DIR = ROOT_DIR / "web"
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
if settings.proxy_enabled:
logger.info(
"Proxy enabled: %s (scope=%s)",
settings.proxy_url,
settings.proxy_for,
)
else:
logger.info("Proxy disabled (direct connection)")
await startup_tasks()
start_scheduler()
yield
stop_scheduler()
app = FastAPI(title="币安成交量排名监控", lifespan=lifespan)
if WEB_DIR.exists():
app.mount("/static", StaticFiles(directory=str(WEB_DIR)), name="static")
@app.get("/")
async def index():
index_path = WEB_DIR / "index.html"
if index_path.exists():
return FileResponse(index_path)
return {"message": "Web UI not found. Place files in /web"}
@app.get("/api/yesterday/top30")
async def api_yesterday_top30():
snap = get_latest_snapshot("yesterday")
if snap:
return {
"period_start": snap["period_start"],
"period_end": snap["period_end"],
"updated_at": snap["created_at"],
"top_n": settings.top_n,
"volume_threshold": settings.volume_threshold,
"change_threshold": settings.change_threshold,
"items": snap["items"],
}
start, end = get_yesterday_period()
try:
mode = settings.yesterday_data_mode
items = await aggregate_period(start, end, mode=mode)
return enrich_snapshot_meta(items, start, end, data_mode=mode)
except Exception as e:
logger.error("api yesterday failed: %s", e)
meta = enrich_snapshot_meta([], start, end)
meta["error"] = "数据暂不可用,请检查网络或稍后重试"
return meta
@app.get("/api/today/top30")
async def api_today_top30():
cached = get_today_cache()
if cached:
return cached
start, end = get_today_period()
try:
mode = settings.today_data_mode
items = await aggregate_period(start, end, use_live_prices=True, mode=mode)
return enrich_snapshot_meta(items, start, end, data_mode=mode)
except Exception as e:
logger.error("api today failed: %s", e)
meta = enrich_snapshot_meta([], start, end)
meta["error"] = "数据暂不可用,请检查网络或稍后重试"
return meta
@app.post("/api/push/test")
async def api_push_test():
snap = get_latest_snapshot("yesterday")
if not snap:
start, end = get_yesterday_period()
items = await aggregate_period(start, end)
from .db import save_snapshot
save_snapshot("yesterday", start, end, items)
snap = get_latest_snapshot("yesterday")
if not snap:
raise HTTPException(500, "无法生成昨日数据")
content = build_markdown(snap)
ok, msg = await send_wecom_markdown(content)
log_push(snap["period_start"], snap["period_end"], ok, msg)
if not ok:
raise HTTPException(500, f"推送失败: {msg}")
return {"success": True, "message": "推送成功"}
@app.post("/api/refresh/yesterday")
async def api_refresh_yesterday():
await job_finalize_yesterday()
snap = get_latest_snapshot("yesterday")
return snap or {"message": "done"}
@app.post("/api/refresh/today")
async def api_refresh_today():
await job_refresh_today()
return get_today_cache() or {"message": "done"}