""" FastAPI 应用入口 """ import os import logging from pathlib import Path from dotenv import load_dotenv, dotenv_values from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) # 加载环境变量 env_file = Path('.env') env_file_exists = env_file.exists() if env_file_exists: logger.info(f"正在从文件加载环境变量: {env_file.absolute()}") # 先读取环境变量文件内容(用于日志记录) env_vars = dotenv_values(env_file) # 然后加载到环境变量中 result = load_dotenv(env_file) # 敏感环境变量列表(只显示名称,不显示值) sensitive_keys = ['SECRET_KEY', 'DEEPSEEK_API_KEY', 'LLM_API_KEY', 'PASSWORD', 'TOKEN', 'KEY'] # 记录加载的环境变量 loaded_vars = [] for key, value in env_vars.items(): if value is None: continue if any(sensitive in key.upper() for sensitive in sensitive_keys): # 敏感信息只显示名称和部分值 masked_value = f"{value[:4]}...{value[-4:]}" if value and len(value) > 8 else "***" loaded_vars.append(f"{key}={masked_value}") else: loaded_vars.append(f"{key}={value}") logger.info(f"成功加载 {len(loaded_vars)} 个环境变量:") for var in loaded_vars: logger.info(f" - {var}") else: logger.warning(f".env 文件不存在: {env_file.absolute()}") logger.info("尝试从系统环境变量加载配置") load_dotenv() from database import init_db from routers import ( websocket, chapters, books, conversations, auth, memoir_state, tasks, user, plans, orders, faqs, quota, feedback, legal, payment, home ) # 初始化数据库 logger.info("正在初始化数据库...") init_db() logger.info("数据库初始化完成") # 记录关键配置信息 logger.info("=== 应用配置信息 ===") db_url = os.getenv('DATABASE_URL', 'postgresql://postgres:postgres@localhost:5432/life_echo') # 隐藏密码 if '@' in db_url: parts = db_url.split('@') masked_url = parts[0].rsplit(':', 1)[0] + ':***@' + parts[1] else: masked_url = db_url logger.info(f"数据库连接: {masked_url}") logger.info(f"JWT 算法: {os.getenv('ALGORITHM', 'HS256')}") logger.info(f"访问令牌过期时间: {os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES', '120')} 分钟") # LLM 配置 if os.getenv("DEEPSEEK_API_KEY"): logger.info("LLM 提供商: DeepSeek") logger.info(f"DeepSeek 模型: {os.getenv('DEEPSEEK_MODEL', 'deepseek-chat')}") logger.info(f"DeepSeek Base URL: {os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com')}") elif os.getenv("LLM_API_KEY"): logger.info("LLM 提供商: 通用 LLM") logger.info(f"LLM 模型: {os.getenv('LLM_MODEL', 'deepseek-chat')}") logger.info(f"LLM Base URL: {os.getenv('LLM_BASE_URL', '未设置')}") else: logger.warning("未检测到 LLM API Key 配置") logger.info("===================") app = FastAPI(title="Life Echo API", version="1.0.0", docs_url=None, redoc_url=None, openapi_url=None) @app.on_event("startup") async def startup_event(): """应用启动事件""" import asyncio logger.info("=" * 50) logger.info("Life Echo API 正在启动...") logger.info("=" * 50) # 初始化 Redis 连接 try: from services.redis_service import redis_service await redis_service.get_client() logger.info("Redis 连接已建立") except Exception as e: logger.warning(f"Redis 连接失败(会话存储将不可用): {e}") # 检查并预加载 ASR 模型(在后台线程执行,避免阻塞启动) try: from services import asr_service from services import ASR_PROVIDER as _asr_provider asr_ready = await asyncio.to_thread(asr_service.ensure_ready) if asr_ready: provider_name = "腾讯云一句话识别" if _asr_provider == "tencent" else "本地 Whisper" logger.info(f"ASR 服务已就绪({provider_name})") else: logger.warning("ASR 服务未就绪,语音转写将不可用") except Exception as e: logger.warning(f"ASR 初始化检查失败: {e}") # 预初始化微信支付客户端(在后台线程执行,避免首次下单时 _ensure_client 占满 25s 导致超时) try: def _init_wechat_pay_client(): from routers.payment import get_payment_service svc = get_payment_service() if svc.is_method_available("wechat"): _ = svc.wechat_client # 触发 _ensure_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 正在关闭...") # 关闭 Redis 连接 try: from services.redis_service import redis_service await redis_service.close() logger.info("Redis 连接已关闭") except Exception as e: logger.warning(f"关闭 Redis 连接失败: {e}") # CORS 配置 app.add_middleware( CORSMiddleware, allow_origins=["*"], # 生产环境应该限制域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 注册路由 app.include_router(auth.router) # 认证路由(放在最前面) app.websocket("/ws/conversation/{conversation_id}")(websocket.websocket_endpoint) app.include_router(conversations.router) app.include_router(chapters.router) app.include_router(books.router) app.include_router(memoir_state.router) app.include_router(tasks.router) # 任务状态路由 app.include_router(user.router) # 用户相关路由 app.include_router(plans.router) # 订阅计划路由 app.include_router(orders.router) # 订单路由(旧接口,兼容保留) app.include_router(payment.router) # 支付路由(微信支付、支付宝) app.include_router(faqs.router) # 常见问题路由 app.include_router(quota.router) # 配额检查路由 app.include_router(feedback.router) # 反馈路由 app.include_router(legal.router) # 法律文档路由(用户协议、隐私政策) app.include_router(home.router) # 应用官网主页 app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static") @app.get("/health") async def health(): return {"status": "ok"}