- 扩展websocket.py支持语音消息 - 优化asr_service.py语音识别服务 - 更新main.py和requirements.txt - 更新.env.production配置 Co-authored-by: Cursor <cursoragent@cursor.com>
170 lines
5.6 KiB
Python
170 lines
5.6 KiB
Python
"""
|
||
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
|
||
|
||
# 配置日志
|
||
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
|
||
)
|
||
|
||
# 初始化数据库
|
||
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")
|
||
|
||
|
||
@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.asr_service import asr_service
|
||
asr_ready = await asyncio.to_thread(asr_service.ensure_ready)
|
||
if asr_ready:
|
||
logger.info("ASR 模型已就绪(本地 Whisper)")
|
||
else:
|
||
logger.warning("ASR 模型未就绪,语音转写将不可用")
|
||
except Exception as e:
|
||
logger.warning(f"ASR 初始化检查失败: {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(faqs.router) # 常见问题路由
|
||
app.include_router(quota.router) # 配额检查路由
|
||
app.include_router(feedback.router) # 反馈路由
|
||
app.include_router(legal.router) # 法律文档路由(用户协议、隐私政策)
|
||
|
||
|
||
@app.get("/")
|
||
async def root():
|
||
return {"message": "Life Echo API", "version": "1.0.0"}
|
||
|
||
|
||
@app.get("/health")
|
||
async def health():
|
||
return {"status": "ok"}
|