Files
life-echo/api/app/main.py
Kevin 22d282dc01 feat(api): use Tencent 16k_zh_large ASR and remove local Whisper
Standardize ASR on Tencent's dialect-capable engine across all environments,
drop faster-whisper from dependencies and deployment images, and add an
expo-sqlite iOS vendor sync plus pod install in prebuild to prevent native
build failures after npm install.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:21:41 +08:00

183 lines
6.4 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 contextlib import asynccontextmanager
from pathlib import Path
from app.core.logging import get_logger, setup_logging
setup_logging()
from app.core.config import settings
from app.core.runtime_constants import asr_defaults, otel_defaults
from app.core.telemetry import instrument_fastapi_app, setup_telemetry
setup_telemetry(
service_name=otel_defaults.service_name or "life-echo-api",
)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
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.memory.router import router as memory_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.story import models as _story_models # noqa: F401
from app.features.user import models as _user_models # noqa: F401
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期:启动迁移/Redis/ASR/支付预初始化,关闭时释放连接。"""
import asyncio
from app.core.alembic_startup import run_alembic_upgrade_at_startup
logger.info("Life Echo API 正在启动...")
if settings.app_environment == "production" and not (
settings.api_cors_origins or ""
).strip():
logger.warning(
"生产环境未配置 API_CORS_ORIGINS浏览器跨域请求将无法携带 credentials"
)
await asyncio.to_thread(run_alembic_upgrade_at_startup)
try:
from app.core.celery_broker_dev import maybe_purge_celery_broker_on_startup
from app.core.redis import redis_service
_redis = await redis_service.get_client()
logger.info("Redis 连接已建立")
await maybe_purge_celery_broker_on_startup(_redis)
except Exception as e:
logger.warning("Redis 连接失败(会话存储将不可用): {}", 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:
logger.info(
"ASR 服务已就绪(腾讯云一句话识别,引擎 {}",
asr_defaults.engine_type,
)
else:
logger.warning("ASR 服务未就绪,语音转写将不可用")
except Exception as e:
logger.warning("ASR 初始化检查失败: {}", 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("微信支付预初始化失败(首次下单时再初始化): {}", e)
yield
logger.info("Life Echo API 正在关闭...")
try:
from app.core.telemetry import shutdown_telemetry
shutdown_telemetry()
except Exception as e:
logger.warning("关闭 OpenTelemetry 失败: {}", e)
try:
from app.core.redis import redis_service
await redis_service.close()
logger.info("Redis 连接已关闭")
except Exception as e:
logger.warning("关闭 Redis 连接失败: {}", e)
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,
lifespan=lifespan,
)
instrument_fastapi_app(app)
# OpenAPI 全局增强
app.openapi = lambda: custom_openapi(app) # type: ignore[assignment]
# Middleware注册顺序LIFO先注册的后执行
app.add_middleware(RequestIdMiddleware)
_origins = [
o.strip() for o in (settings.api_cors_origins or "").split(",") if o.strip()
]
_allow_creds = bool(_origins)
app.add_middleware(
CORSMiddleware,
allow_origins=_origins if _origins else ["*"],
allow_credentials=_allow_creds,
allow_methods=["*"],
allow_headers=["*"],
)
# 全局异常处理
register_exception_handlers(app)
# ── 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(memory_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(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"}