main.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. """
  2. 主应用入口
  3. """
  4. import logging
  5. from typing import Optional
  6. from fastapi import FastAPI, WebSocket, HTTPException
  7. from fastapi.middleware.cors import CORSMiddleware
  8. from pydantic import BaseModel
  9. from contextlib import asynccontextmanager
  10. from config import settings, get_dashscope_domain
  11. from websocket_handler import WebSocketHandler
  12. from database import init_database, close_database
  13. import redis.asyncio as redis
  14. from services.redis_cache_service import RedisCacheService
  15. from services.user_service import user_service
  16. # 配置日志
  17. logging.basicConfig(
  18. level=getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO),
  19. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  20. datefmt='%Y-%m-%d %H:%M:%S'
  21. )
  22. @asynccontextmanager
  23. async def lifespan(app: FastAPI):
  24. """应用生命周期管理"""
  25. # 启动时初始化数据库(包含MySQL和Redis,自动启动健康检查任务)
  26. try:
  27. await init_database()
  28. # 获取Redis客户端(已配置健康检查)
  29. from database import db_manager
  30. redis_client = await db_manager.get_redis()
  31. # 创建Redis缓存服务
  32. app.state.redis_cache = RedisCacheService(redis_client)
  33. app.state.redis_client = redis_client
  34. except Exception as e:
  35. logger.error(f"❌ 数据库初始化失败: {e}")
  36. app.state.redis_cache = None
  37. app.state.redis_client = None
  38. yield
  39. # 关闭时清理资源(database.py会自动处理)
  40. await close_database()
  41. app = FastAPI(
  42. title="Tolan Backend API",
  43. lifespan=lifespan
  44. )
  45. # 配置CORS
  46. app.add_middleware(
  47. CORSMiddleware,
  48. allow_origins=["*"], # 生产环境应该限制具体域名
  49. allow_credentials=True,
  50. allow_methods=["*"],
  51. allow_headers=["*"],
  52. )
  53. @app.get("/")
  54. async def root():
  55. """健康检查"""
  56. return {"status": "ok", "message": "Tolan Backend API"}
  57. class CreateUserRequest(BaseModel):
  58. """创建用户请求"""
  59. username: str = ""
  60. bot_name: Optional[str] = None
  61. class UpdateUsernameRequest(BaseModel):
  62. """更新用户名请求"""
  63. username: str
  64. class UpdateBotNameRequest(BaseModel):
  65. """更新机器人名字请求"""
  66. bot_name: str
  67. @app.post("/api/users/create")
  68. async def create_user(request: CreateUserRequest):
  69. """
  70. 创建游客用户
  71. Returns:
  72. {
  73. "user_id": "uuid",
  74. "username": "",
  75. "user_type": "guest",
  76. "bot_name": "机器人名字(可选)"
  77. }
  78. """
  79. try:
  80. user = await user_service.create_guest_user(
  81. username=request.username,
  82. bot_name=request.bot_name
  83. )
  84. return {
  85. "success": True,
  86. "data": user
  87. }
  88. except Exception as e:
  89. raise HTTPException(status_code=500, detail=f"创建用户失败: {str(e)}")
  90. @app.get("/api/users/{user_id}")
  91. async def get_user(user_id: str):
  92. """
  93. 获取用户信息
  94. Args:
  95. user_id: 用户ID
  96. Returns:
  97. {
  98. "success": true,
  99. "data": {
  100. "user_id": "uuid",
  101. "username": "",
  102. "user_type": "guest",
  103. "bot_name": "机器人名字(可选)",
  104. ...
  105. }
  106. }
  107. """
  108. try:
  109. user = await user_service.get_user(user_id)
  110. if user:
  111. return {
  112. "success": True,
  113. "data": user
  114. }
  115. else:
  116. raise HTTPException(status_code=404, detail="用户不存在")
  117. except HTTPException:
  118. raise
  119. except Exception as e:
  120. raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")
  121. @app.put("/api/users/{user_id}/username")
  122. async def update_username(user_id: str, request: UpdateUsernameRequest):
  123. """
  124. 更新用户名
  125. Args:
  126. user_id: 用户ID
  127. request: 更新请求(包含username)
  128. Returns:
  129. {
  130. "success": true,
  131. "message": "更新成功"
  132. }
  133. """
  134. try:
  135. success = await user_service.update_username(user_id, request.username)
  136. if success:
  137. return {
  138. "success": True,
  139. "message": "用户名更新成功"
  140. }
  141. else:
  142. raise HTTPException(status_code=500, detail="更新用户名失败")
  143. except Exception as e:
  144. raise HTTPException(status_code=500, detail=f"更新用户名失败: {str(e)}")
  145. @app.put("/api/users/{user_id}/bot-name")
  146. async def update_bot_name(user_id: str, request: UpdateBotNameRequest):
  147. """
  148. 更新用户给机器人取的名字
  149. Args:
  150. user_id: 用户ID
  151. request: 更新请求(包含bot_name)
  152. Returns:
  153. {
  154. "success": true,
  155. "message": "更新成功"
  156. }
  157. """
  158. try:
  159. success = await user_service.update_bot_name(user_id, request.bot_name)
  160. if success:
  161. return {
  162. "success": True,
  163. "message": "机器人名字更新成功"
  164. }
  165. else:
  166. raise HTTPException(status_code=500, detail="更新机器人名字失败")
  167. except Exception as e:
  168. raise HTTPException(status_code=500, detail=f"更新机器人名字失败: {str(e)}")
  169. @app.websocket("/ws")
  170. async def websocket_endpoint(websocket: WebSocket):
  171. """WebSocket端点"""
  172. # 从查询参数获取user_id(tolan_user_id)
  173. user_id = websocket.query_params.get("user_id")
  174. # 记录连接信息
  175. import logging
  176. logger = logging.getLogger(__name__)
  177. logger.info(f"🔌 WebSocket连接: user_id={user_id}, query_params={dict(websocket.query_params)}")
  178. # 🔥 传入Redis缓存服务和user_id
  179. redis_cache = getattr(app.state, 'redis_cache', None)
  180. handler = WebSocketHandler(websocket, user_id=user_id, redis_cache=redis_cache)
  181. await handler.handle_connection()
  182. if __name__ == "__main__":
  183. import uvicorn
  184. import os
  185. import socket
  186. from pathlib import Path
  187. # 🔥 DNS预解析(减少首次连接延迟)
  188. dashscope_domain = get_dashscope_domain()
  189. try:
  190. socket.getaddrinfo(dashscope_domain, 443)
  191. except Exception as e:
  192. pass
  193. # SSL证书路径配置
  194. # 优先使用环境变量配置的证书路径(生产环境)
  195. ssl_keyfile = settings.SSL_KEYFILE
  196. ssl_certfile = settings.SSL_CERTFILE
  197. # 如果环境变量未配置,使用本地开发证书
  198. if not ssl_keyfile or not ssl_certfile:
  199. ssl_dir = Path(__file__).parent.parent / "ssl"
  200. ssl_keyfile = str(ssl_dir / "key.pem")
  201. ssl_certfile = str(ssl_dir / "cert.pem")
  202. # 检查证书是否存在
  203. if ssl_keyfile and ssl_certfile:
  204. if not os.path.exists(ssl_keyfile) or not os.path.exists(ssl_certfile):
  205. ssl_keyfile = None
  206. ssl_certfile = None
  207. uvicorn.run(
  208. "main:app",
  209. host=settings.host,
  210. port=settings.port,
  211. reload=True,
  212. ssl_keyfile=ssl_keyfile,
  213. ssl_certfile=ssl_certfile,
  214. )