| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- """
- 主应用入口
- """
- import logging
- from typing import Optional
- from fastapi import FastAPI, WebSocket, HTTPException
- from fastapi.middleware.cors import CORSMiddleware
- from pydantic import BaseModel
- from contextlib import asynccontextmanager
- from config import settings, get_dashscope_domain
- from websocket_handler import WebSocketHandler
- from database import init_database, close_database
- import redis.asyncio as redis
- from services.redis_cache_service import RedisCacheService
- from services.user_service import user_service
- # 配置日志
- logging.basicConfig(
- level=getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO),
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S'
- )
- @asynccontextmanager
- async def lifespan(app: FastAPI):
- """应用生命周期管理"""
- # 🔥 启动时初始化Redis
- try:
- redis_client = redis.Redis(
- host=settings.REDIS_HOST,
- port=settings.REDIS_PORT,
- db=settings.REDIS_DB,
- password=settings.REDIS_PASSWORD if settings.REDIS_PASSWORD else None,
- decode_responses=False, # 保持二进制,手动decode
- max_connections=settings.REDIS_MAX_CONNECTIONS,
- )
-
- # 测试连接
- await redis_client.ping()
-
- # 创建Redis缓存服务
- app.state.redis_cache = RedisCacheService(redis_client)
- app.state.redis_client = redis_client
-
- except Exception as e:
- app.state.redis_cache = None
- app.state.redis_client = None
-
- # 启动时初始化数据库
- try:
- await init_database()
- except Exception as e:
- pass
-
- yield
-
- # 关闭时清理资源
- if hasattr(app.state, 'redis_client') and app.state.redis_client:
- await app.state.redis_client.close()
-
- await close_database()
- app = FastAPI(
- title="Tolan Backend API",
- lifespan=lifespan
- )
- # 配置CORS
- app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"], # 生产环境应该限制具体域名
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- @app.get("/")
- async def root():
- """健康检查"""
- return {"status": "ok", "message": "Tolan Backend API"}
- class CreateUserRequest(BaseModel):
- """创建用户请求"""
- username: str = ""
- bot_name: Optional[str] = None
- class UpdateUsernameRequest(BaseModel):
- """更新用户名请求"""
- username: str
- class UpdateBotNameRequest(BaseModel):
- """更新机器人名字请求"""
- bot_name: str
- @app.post("/api/users/create")
- async def create_user(request: CreateUserRequest):
- """
- 创建游客用户
-
- Returns:
- {
- "user_id": "uuid",
- "username": "",
- "user_type": "guest",
- "bot_name": "机器人名字(可选)"
- }
- """
- try:
- user = await user_service.create_guest_user(
- username=request.username,
- bot_name=request.bot_name
- )
- return {
- "success": True,
- "data": user
- }
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"创建用户失败: {str(e)}")
- @app.get("/api/users/{user_id}")
- async def get_user(user_id: str):
- """
- 获取用户信息
-
- Args:
- user_id: 用户ID
-
- Returns:
- {
- "success": true,
- "data": {
- "user_id": "uuid",
- "username": "",
- "user_type": "guest",
- "bot_name": "机器人名字(可选)",
- ...
- }
- }
- """
- try:
- user = await user_service.get_user(user_id)
- if user:
- return {
- "success": True,
- "data": user
- }
- else:
- raise HTTPException(status_code=404, detail="用户不存在")
- except HTTPException:
- raise
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")
- @app.put("/api/users/{user_id}/username")
- async def update_username(user_id: str, request: UpdateUsernameRequest):
- """
- 更新用户名
-
- Args:
- user_id: 用户ID
- request: 更新请求(包含username)
-
- Returns:
- {
- "success": true,
- "message": "更新成功"
- }
- """
- try:
- success = await user_service.update_username(user_id, request.username)
- if success:
- return {
- "success": True,
- "message": "用户名更新成功"
- }
- else:
- raise HTTPException(status_code=500, detail="更新用户名失败")
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"更新用户名失败: {str(e)}")
- @app.put("/api/users/{user_id}/bot-name")
- async def update_bot_name(user_id: str, request: UpdateBotNameRequest):
- """
- 更新用户给机器人取的名字
-
- Args:
- user_id: 用户ID
- request: 更新请求(包含bot_name)
-
- Returns:
- {
- "success": true,
- "message": "更新成功"
- }
- """
- try:
- success = await user_service.update_bot_name(user_id, request.bot_name)
- if success:
- return {
- "success": True,
- "message": "机器人名字更新成功"
- }
- else:
- raise HTTPException(status_code=500, detail="更新机器人名字失败")
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"更新机器人名字失败: {str(e)}")
- @app.websocket("/ws")
- async def websocket_endpoint(websocket: WebSocket):
- """WebSocket端点"""
- # 从查询参数获取user_id(tolan_user_id)
- user_id = websocket.query_params.get("user_id")
-
- # 记录连接信息
- import logging
- logger = logging.getLogger(__name__)
- logger.info(f"🔌 WebSocket连接: user_id={user_id}, query_params={dict(websocket.query_params)}")
-
- # 🔥 传入Redis缓存服务和user_id
- redis_cache = getattr(app.state, 'redis_cache', None)
- handler = WebSocketHandler(websocket, user_id=user_id, redis_cache=redis_cache)
- await handler.handle_connection()
- if __name__ == "__main__":
- import uvicorn
- import os
- import socket
- from pathlib import Path
-
- # 🔥 DNS预解析(减少首次连接延迟)
- dashscope_domain = get_dashscope_domain()
- try:
- socket.getaddrinfo(dashscope_domain, 443)
- except Exception as e:
- pass
-
- # SSL证书路径配置
- # 优先使用环境变量配置的证书路径(生产环境)
- ssl_keyfile = settings.SSL_KEYFILE
- ssl_certfile = settings.SSL_CERTFILE
-
- # 如果环境变量未配置,使用本地开发证书
- if not ssl_keyfile or not ssl_certfile:
- ssl_dir = Path(__file__).parent.parent / "ssl"
- ssl_keyfile = str(ssl_dir / "key.pem")
- ssl_certfile = str(ssl_dir / "cert.pem")
-
- # 检查证书是否存在
- if ssl_keyfile and ssl_certfile:
- if not os.path.exists(ssl_keyfile) or not os.path.exists(ssl_certfile):
- ssl_keyfile = None
- ssl_certfile = None
-
- uvicorn.run(
- "main:app",
- host=settings.host,
- port=settings.port,
- reload=True,
- ssl_keyfile=ssl_keyfile,
- ssl_certfile=ssl_certfile,
- )
|