Files
life-echo/api/app/main.py

175 lines
6.0 KiB
Python
Raw Normal View History

"""
FastAPI 应用入口app 内主入口符合架构计划
"""
2026-03-19 14:36:14 +08:00
from pathlib import Path
from app.core.logging import setup_logging
setup_logging()
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from loguru import logger
from app.core.config import settings
from app.core.errors import register_exception_handlers
from app.core.middleware import RequestIdMiddleware
from app.core.openapi import custom_openapi
from app.features.auth.router import router as auth_router
from app.features.content.router import router as content_router
from app.features.conversation.router import router as conversation_router
from app.features.conversation.ws.router import websocket_endpoint
from app.features.memoir.router import router as memoir_router
from app.features.payment.router import orders_router as payment_orders_router
from app.features.payment.router import router as payment_router
from app.features.plan.router import router as plan_router
from app.features.quota.router import router as quota_router
from app.features.tasks.router import router as tasks_router
from app.features.user.router import feedback_router as user_feedback_router
from app.features.user.router import router as user_router
# 聚合注册所有 feature 的 model 到 Base.metadata供 Alembic 等使用)
from app.features.auth import models as _auth_models # noqa: F401
from app.features.conversation import models as _conv_models # noqa: F401
from app.features.memory import models as _memory_models # noqa: F401
from app.features.memoir import models as _memoir_models # noqa: F401
from app.features.payment import models as _payment_models # noqa: F401
from app.features.user import models as _user_models # noqa: F401
app = FastAPI(
title="Life Echo API",
version="1.0.0",
docs_url="/docs" if settings.enable_docs else None,
redoc_url="/redoc" if settings.enable_docs else None,
openapi_url="/openapi.json" if settings.enable_docs else None,
)
# OpenAPI 全局增强
app.openapi = lambda: custom_openapi(app) # type: ignore[assignment]
# Middleware注册顺序LIFO先注册的后执行
app.add_middleware(RequestIdMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 全局异常处理
register_exception_handlers(app)
def _run_alembic_upgrade() -> None:
"""在子线程中执行 alembic upgrade head初始化/更新数据库表结构。"""
from pathlib import Path
from alembic.config import Config
from alembic.command import upgrade
api_dir = Path(__file__).resolve().parent.parent
cfg = Config(str(api_dir / "alembic.ini"))
upgrade(cfg, "head")
@app.on_event("startup")
async def startup_event():
"""应用启动事件。启动时自动执行 alembic upgrade head 初始化/更新数据库表结构。"""
import asyncio
logger.info("Life Echo API 正在启动...")
try:
await asyncio.to_thread(_run_alembic_upgrade)
logger.info("Alembic 迁移已就绪")
except Exception as e:
2026-03-19 14:36:14 +08:00
logger.warning(
"Alembic 迁移失败(可能数据库未启动或 DATABASE_URL 未配置): %s", e
)
try:
from app.core.redis import redis_service
await redis_service.get_client()
logger.info("Redis 连接已建立")
except Exception as e:
logger.warning("Redis 连接失败(会话存储将不可用): %s", e)
try:
from app.core.dependencies import get_asr_provider
provider = get_asr_provider()
ensure_ready = getattr(provider, "ensure_ready", None)
if callable(ensure_ready):
asr_ready = await asyncio.to_thread(ensure_ready)
else:
asr_ready = True
if asr_ready:
from app.core.config import settings
2026-03-19 14:36:14 +08:00
name = (
"腾讯云一句话识别"
if settings.asr_provider == "tencent"
else "本地 Whisper"
)
logger.info("ASR 服务已就绪(%s", name)
else:
logger.warning("ASR 服务未就绪,语音转写将不可用")
except Exception as e:
logger.warning("ASR 初始化检查失败: %s", e)
try:
def _init_wechat_pay_client():
from app.features.payment.deps import get_payment_service
svc = get_payment_service()
if svc.is_method_available("wechat"):
_ = svc.wechat_client
await asyncio.to_thread(_init_wechat_pay_client)
logger.info("微信支付客户端已预初始化")
except Exception as e:
logger.warning(f"微信支付预初始化失败(首次下单时再初始化): {e}")
@app.on_event("shutdown")
async def shutdown_event():
"""应用关闭事件"""
logger.info("Life Echo API 正在关闭...")
try:
from app.core.redis import redis_service
await redis_service.close()
logger.info("Redis 连接已关闭")
except Exception as e:
logger.warning("关闭 Redis 连接失败: %s", e)
# ── Feature routers ──────────────────────────────────────────
app.include_router(auth_router)
app.websocket("/ws/conversation/{conversation_id}")(websocket_endpoint)
app.include_router(conversation_router)
app.include_router(memoir_router)
app.include_router(user_router)
app.include_router(user_feedback_router)
app.include_router(plan_router)
app.include_router(payment_router)
app.include_router(payment_orders_router)
app.include_router(quota_router)
app.include_router(tasks_router)
app.include_router(content_router)
# static 在 api/ 下app/main.py 在 api/app/ 下
_static_dir = Path(__file__).resolve().parent.parent / "static"
app.mount("/static", StaticFiles(directory=_static_dir), name="static")
@app.get("/health", include_in_schema=False)
async def health():
return {"status": "ok"}