main.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. # 🔥 启动时初始化Redis
  26. try:
  27. redis_client = redis.Redis(
  28. host=settings.REDIS_HOST,
  29. port=settings.REDIS_PORT,
  30. db=settings.REDIS_DB,
  31. password=settings.REDIS_PASSWORD if settings.REDIS_PASSWORD else None,
  32. decode_responses=False, # 保持二进制,手动decode
  33. max_connections=settings.REDIS_MAX_CONNECTIONS,
  34. )
  35. # 测试连接
  36. await redis_client.ping()
  37. # 创建Redis缓存服务
  38. app.state.redis_cache = RedisCacheService(redis_client)
  39. app.state.redis_client = redis_client
  40. except Exception as e:
  41. app.state.redis_cache = None
  42. app.state.redis_client = None
  43. # 启动时初始化数据库
  44. try:
  45. await init_database()
  46. except Exception as e:
  47. pass
  48. yield
  49. # 关闭时清理资源
  50. if hasattr(app.state, 'redis_client') and app.state.redis_client:
  51. await app.state.redis_client.close()
  52. await close_database()
  53. app = FastAPI(
  54. title="Tolan Backend API",
  55. lifespan=lifespan
  56. )
  57. # 配置CORS
  58. app.add_middleware(
  59. CORSMiddleware,
  60. allow_origins=["*"], # 生产环境应该限制具体域名
  61. allow_credentials=True,
  62. allow_methods=["*"],
  63. allow_headers=["*"],
  64. )
  65. @app.get("/")
  66. async def root():
  67. """健康检查"""
  68. return {"status": "ok", "message": "Tolan Backend API"}
  69. class CreateUserRequest(BaseModel):
  70. """创建用户请求"""
  71. username: str = ""
  72. bot_name: Optional[str] = None
  73. class UpdateUsernameRequest(BaseModel):
  74. """更新用户名请求"""
  75. username: str
  76. class UpdateBotNameRequest(BaseModel):
  77. """更新机器人名字请求"""
  78. bot_name: str
  79. @app.post("/api/users/create")
  80. async def create_user(request: CreateUserRequest):
  81. """
  82. 创建游客用户
  83. Returns:
  84. {
  85. "user_id": "uuid",
  86. "username": "",
  87. "user_type": "guest",
  88. "bot_name": "机器人名字(可选)"
  89. }
  90. """
  91. try:
  92. user = await user_service.create_guest_user(
  93. username=request.username,
  94. bot_name=request.bot_name
  95. )
  96. return {
  97. "success": True,
  98. "data": user
  99. }
  100. except Exception as e:
  101. raise HTTPException(status_code=500, detail=f"创建用户失败: {str(e)}")
  102. @app.get("/api/users/{user_id}")
  103. async def get_user(user_id: str):
  104. """
  105. 获取用户信息
  106. Args:
  107. user_id: 用户ID
  108. Returns:
  109. {
  110. "success": true,
  111. "data": {
  112. "user_id": "uuid",
  113. "username": "",
  114. "user_type": "guest",
  115. "bot_name": "机器人名字(可选)",
  116. ...
  117. }
  118. }
  119. """
  120. try:
  121. user = await user_service.get_user(user_id)
  122. if user:
  123. return {
  124. "success": True,
  125. "data": user
  126. }
  127. else:
  128. raise HTTPException(status_code=404, detail="用户不存在")
  129. except HTTPException:
  130. raise
  131. except Exception as e:
  132. raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")
  133. @app.put("/api/users/{user_id}/username")
  134. async def update_username(user_id: str, request: UpdateUsernameRequest):
  135. """
  136. 更新用户名
  137. Args:
  138. user_id: 用户ID
  139. request: 更新请求(包含username)
  140. Returns:
  141. {
  142. "success": true,
  143. "message": "更新成功"
  144. }
  145. """
  146. try:
  147. success = await user_service.update_username(user_id, request.username)
  148. if success:
  149. return {
  150. "success": True,
  151. "message": "用户名更新成功"
  152. }
  153. else:
  154. raise HTTPException(status_code=500, detail="更新用户名失败")
  155. except Exception as e:
  156. raise HTTPException(status_code=500, detail=f"更新用户名失败: {str(e)}")
  157. @app.put("/api/users/{user_id}/bot-name")
  158. async def update_bot_name(user_id: str, request: UpdateBotNameRequest):
  159. """
  160. 更新用户给机器人取的名字
  161. Args:
  162. user_id: 用户ID
  163. request: 更新请求(包含bot_name)
  164. Returns:
  165. {
  166. "success": true,
  167. "message": "更新成功"
  168. }
  169. """
  170. try:
  171. success = await user_service.update_bot_name(user_id, request.bot_name)
  172. if success:
  173. return {
  174. "success": True,
  175. "message": "机器人名字更新成功"
  176. }
  177. else:
  178. raise HTTPException(status_code=500, detail="更新机器人名字失败")
  179. except Exception as e:
  180. raise HTTPException(status_code=500, detail=f"更新机器人名字失败: {str(e)}")
  181. @app.websocket("/ws")
  182. async def websocket_endpoint(websocket: WebSocket):
  183. """WebSocket端点"""
  184. # 从查询参数获取user_id(tolan_user_id)
  185. user_id = websocket.query_params.get("user_id")
  186. # 记录连接信息
  187. import logging
  188. logger = logging.getLogger(__name__)
  189. logger.info(f"🔌 WebSocket连接: user_id={user_id}, query_params={dict(websocket.query_params)}")
  190. # 🔥 传入Redis缓存服务和user_id
  191. redis_cache = getattr(app.state, 'redis_cache', None)
  192. handler = WebSocketHandler(websocket, user_id=user_id, redis_cache=redis_cache)
  193. await handler.handle_connection()
  194. if __name__ == "__main__":
  195. import uvicorn
  196. import os
  197. import socket
  198. from pathlib import Path
  199. # 🔥 DNS预解析(减少首次连接延迟)
  200. dashscope_domain = get_dashscope_domain()
  201. try:
  202. socket.getaddrinfo(dashscope_domain, 443)
  203. except Exception as e:
  204. pass
  205. # SSL证书路径配置
  206. # 优先使用环境变量配置的证书路径(生产环境)
  207. ssl_keyfile = settings.SSL_KEYFILE
  208. ssl_certfile = settings.SSL_CERTFILE
  209. # 如果环境变量未配置,使用本地开发证书
  210. if not ssl_keyfile or not ssl_certfile:
  211. ssl_dir = Path(__file__).parent.parent / "ssl"
  212. ssl_keyfile = str(ssl_dir / "key.pem")
  213. ssl_certfile = str(ssl_dir / "cert.pem")
  214. # 检查证书是否存在
  215. if ssl_keyfile and ssl_certfile:
  216. if not os.path.exists(ssl_keyfile) or not os.path.exists(ssl_certfile):
  217. ssl_keyfile = None
  218. ssl_certfile = None
  219. uvicorn.run(
  220. "main:app",
  221. host=settings.host,
  222. port=settings.port,
  223. reload=True,
  224. ssl_keyfile=ssl_keyfile,
  225. ssl_certfile=ssl_certfile,
  226. )