Files
life-echo/api/main.py
penghanyuan d9e010ef70 feat: 更新 API 主页路由及 FastAPI 配置
- 将主页路由从 "/home" 更新为根路径 "/",以简化访问。
- 在 FastAPI 实例中禁用文档和 OpenAPI 相关的 URL,提升 API 安全性。
2026-02-14 21:25:37 +01:00

183 lines
6.6 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 应用入口
"""
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"}