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

168 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
FastAPI 应用入口app 内主入口,符合架构计划)
"""
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:
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
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"}