astock-agent/backend/app/main.py
2026-05-14 11:10:17 +08:00

181 lines
5.6 KiB
Python

"""A 股分析推荐 Agent - FastAPI 入口"""
import logging
import traceback
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.db.error_logger import log_error
from app.db.database import init_db
from app.engine.scheduler import start_scheduler, stop_scheduler
from app.api import market, sectors, recommendations, stocks, watchlists, websocket, chat, auth, debug, catalysts
def configure_logging() -> None:
logging.basicConfig(
level=logging.DEBUG if settings.debug else logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# 保留应用日志的调试能力,但压制基础设施层的噪音。
noisy_loggers = {
"aiosqlite": logging.WARNING,
"sqlalchemy": logging.WARNING,
"sqlalchemy.engine": logging.WARNING,
"sqlalchemy.pool": logging.WARNING,
"httpx": logging.INFO,
"httpcore": logging.WARNING,
"uvicorn.access": logging.INFO,
}
for name, level in noisy_loggers.items():
logging.getLogger(name).setLevel(level)
configure_logging()
logger = logging.getLogger(__name__)
async def ensure_admin_exists():
"""确保配置中的管理员账号存在并可用。"""
from sqlalchemy import select, insert, update
from app.db.database import get_db
from app.db.tables import users_table, invite_codes_table
from app.core.auth import hash_password
async with get_db() as db:
configured_admin = await db.execute(
select(users_table).where(users_table.c.email == settings.admin_email)
)
admin_row = configured_admin.mappings().first()
if admin_row is None:
await db.execute(
insert(users_table).values(
username=settings.admin_username,
email=settings.admin_email,
password_hash=hash_password(settings.admin_password),
role="admin",
is_active=True,
)
)
await db.commit()
logger.info(f"默认管理员用户 '{settings.admin_email}' 已创建")
elif admin_row["role"] != "admin" or not admin_row["is_active"]:
await db.execute(
update(users_table)
.where(users_table.c.id == admin_row["id"])
.values(
role="admin",
is_active=True,
)
)
await db.commit()
logger.info("默认管理员用户 '%s' 已修正为可用管理员", settings.admin_email)
invite = await db.execute(
select(invite_codes_table).where(invite_codes_table.c.code == "ASTOCK-ACCESS")
)
if invite.first() is None:
await db.execute(
insert(invite_codes_table).values(
code="ASTOCK-ACCESS",
description="默认注册邀请码",
is_active=True,
max_uses=999999,
used_count=0,
)
)
await db.commit()
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动
logger.info("A 股分析推荐 Agent 启动中...")
try:
await init_db()
logger.info("数据库初始化完成")
await ensure_admin_exists()
from app.llm.strategy_config import ensure_default_configs
await ensure_default_configs()
logger.info("策略配置中心初始化完成")
start_scheduler()
logger.info("调度器已启动")
yield
except Exception as e:
logger.exception("应用生命周期异常")
await log_error(
"lifespan",
f"应用生命周期异常: {e}",
detail=traceback.format_exc(),
level="critical",
)
raise
finally:
stop_scheduler()
logger.info("服务已关闭")
app = FastAPI(
title="A 股分析推荐 Agent",
description="基于资金驱动的四层漏斗模型,盘中实时分析推荐 A 股",
version="1.0.0",
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=[settings.frontend_url, "http://localhost:3002"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 路由
app.include_router(market.router)
app.include_router(sectors.router)
app.include_router(recommendations.router)
app.include_router(stocks.router)
app.include_router(watchlists.router)
app.include_router(chat.router)
app.include_router(auth.router)
app.include_router(debug.router)
app.include_router(catalysts.router)
# WebSocket
app.websocket("/ws")(websocket.ws_endpoint)
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
logger.exception("未处理的接口异常: %s %s", request.method, request.url.path)
query = str(request.url.query or "")
await log_error(
"asgi",
f"未处理的接口异常: {exc}",
detail=traceback.format_exc(),
level="error",
context={
"method": request.method,
"path": request.url.path,
"query": query,
},
)
return JSONResponse(
status_code=500,
content={"detail": "服务器内部错误"},
)
@app.get("/api/health")
async def health():
return {
"status": "ok",
"service": "astock-agent",
"llm_enabled": bool(settings.deepseek_api_key),
}