user_service.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. """
  2. 用户服务模块(SQLAlchemy ORM 版本)
  3. """
  4. import uuid
  5. import random
  6. import asyncio
  7. import logging
  8. from typing import Dict, Optional
  9. from datetime import datetime
  10. from sqlalchemy import select, update, func
  11. from sqlalchemy.ext.asyncio import AsyncSession
  12. from database import db_manager
  13. from models_db import User
  14. logger = logging.getLogger(__name__)
  15. class UserService:
  16. """用户管理服务"""
  17. async def _generate_temp_username(self, session: AsyncSession) -> str:
  18. """
  19. 生成临时用户名(格式:User + 7位随机数字)
  20. 确保用户名不重复
  21. Args:
  22. session: 数据库会话
  23. Returns:
  24. 临时用户名
  25. """
  26. max_attempts = 100 # 最大重试次数,避免无限循环
  27. for attempt in range(max_attempts):
  28. # 生成7位随机数字(1000000-9999999)
  29. random_number = random.randint(1000000, 9999999)
  30. temp_username = f"User{random_number}"
  31. # 检查用户名是否已存在
  32. result = await session.execute(
  33. select(User).where(User.username == temp_username)
  34. )
  35. existing_user = result.scalar_one_or_none()
  36. if not existing_user:
  37. logger.debug(f"生成临时用户名: {temp_username} (尝试次数: {attempt + 1})")
  38. return temp_username
  39. # 如果100次都重复,使用时间戳的后7位作为备选方案
  40. # 使用当前时间戳的毫秒数取模,确保是7位数字
  41. for fallback_attempt in range(10): # 备选方案也最多尝试10次
  42. timestamp_suffix = int(datetime.now().timestamp() * 1000) % 10000000
  43. # 确保至少是7位数字(如果不足7位,前面补0)
  44. fallback_number = str(timestamp_suffix).zfill(7)
  45. temp_username = f"User{fallback_number}"
  46. # 再次检查是否重复
  47. result = await session.execute(
  48. select(User).where(User.username == temp_username)
  49. )
  50. existing_user = result.scalar_one_or_none()
  51. if not existing_user:
  52. logger.warning(f"⚠️ 临时用户名生成达到最大重试次数,使用备选方案: {temp_username}")
  53. return temp_username
  54. # 如果还是重复,等待1毫秒后重试(确保时间戳变化)
  55. await asyncio.sleep(0.001)
  56. # 最后的备选方案:使用UUID的十六进制转数字(取后7位)
  57. uuid_hex = uuid.uuid4().hex
  58. final_number = str(int(uuid_hex[:8], 16) % 10000000).zfill(7)
  59. temp_username = f"User{final_number}"
  60. logger.error(f"❌ 临时用户名生成异常,使用最终备选方案: {temp_username}")
  61. return temp_username
  62. async def create_guest_user(self, username: str = "", bot_name: Optional[str] = None) -> Dict:
  63. """
  64. 创建游客用户
  65. Args:
  66. username: 用户名(默认为空字符串,为空时自动生成临时用户名)
  67. bot_name: 用户给机器人取的名字(可选)
  68. Returns:
  69. 用户信息字典(包含user_id)
  70. """
  71. try:
  72. # 生成唯一用户ID
  73. user_id = str(uuid.uuid4())
  74. # 使用 ORM 创建用户
  75. async with db_manager.get_session() as session:
  76. # 如果用户名为空,生成临时用户名
  77. if not username or username.strip() == "":
  78. username = await self._generate_temp_username(session)
  79. logger.info(f"🔧 自动生成临时用户名: {username}")
  80. user = User(
  81. user_id=user_id,
  82. username=username,
  83. user_type="guest",
  84. bot_name=bot_name,
  85. )
  86. session.add(user)
  87. await session.commit()
  88. await session.refresh(user)
  89. logger.info(f"✅ 创建游客用户成功: user_id={user_id}, username={username}, bot_name={bot_name}")
  90. return {
  91. "user_id": user_id,
  92. "username": username,
  93. "user_type": "guest",
  94. "bot_name": bot_name,
  95. "created_at": datetime.now().isoformat()
  96. }
  97. except Exception as e:
  98. logger.error(f"❌ 创建用户失败: {e}")
  99. raise
  100. async def get_user(self, user_id: str) -> Optional[Dict]:
  101. """
  102. 获取用户信息
  103. Args:
  104. user_id: 用户ID
  105. Returns:
  106. 用户信息字典,如果不存在返回None
  107. """
  108. try:
  109. async with db_manager.get_session() as session:
  110. result = await session.execute(
  111. select(User).where(User.user_id == user_id)
  112. )
  113. user = result.scalar_one_or_none()
  114. if user:
  115. return user.to_dict()
  116. return None
  117. except Exception as e:
  118. logger.error(f"❌ 获取用户信息失败: user_id={user_id}, error={e}")
  119. return None
  120. async def update_user_last_active(self, user_id: str):
  121. """
  122. 更新用户最后活跃时间
  123. Args:
  124. user_id: 用户ID
  125. """
  126. try:
  127. async with db_manager.get_session() as session:
  128. await session.execute(
  129. update(User)
  130. .where(User.user_id == user_id)
  131. .values(last_active_at=func.now())
  132. )
  133. await session.commit()
  134. except Exception as e:
  135. logger.warning(f"⚠️ 更新用户活跃时间失败: user_id={user_id}, error={e}")
  136. async def update_username(self, user_id: str, username: str) -> bool:
  137. """
  138. 更新用户名
  139. Args:
  140. user_id: 用户ID
  141. username: 用户名
  142. Returns:
  143. 是否更新成功
  144. """
  145. try:
  146. async with db_manager.get_session() as session:
  147. result = await session.execute(
  148. update(User)
  149. .where(User.user_id == user_id)
  150. .values(username=username)
  151. )
  152. await session.commit()
  153. if result.rowcount > 0:
  154. logger.info(f"✅ 更新用户名成功: user_id={user_id}, username={username}")
  155. return True
  156. return False
  157. except Exception as e:
  158. logger.error(f"❌ 更新用户名失败: user_id={user_id}, error={e}")
  159. return False
  160. async def update_bot_name(self, user_id: str, bot_name: str) -> bool:
  161. """
  162. 更新用户给机器人取的名字
  163. Args:
  164. user_id: 用户ID
  165. bot_name: 机器人名字
  166. Returns:
  167. 是否更新成功
  168. """
  169. import time
  170. start_time = time.time()
  171. try:
  172. async with db_manager.get_session() as session:
  173. # 🔥 先查询当前值,避免不必要的更新
  174. query_start = time.time()
  175. result = await session.execute(
  176. select(User.bot_name).where(User.user_id == user_id)
  177. )
  178. current_bot_name = result.scalar_one_or_none()
  179. query_duration = (time.time() - query_start) * 1000
  180. # 如果值相同,直接返回(避免不必要的 UPDATE)
  181. if current_bot_name == bot_name:
  182. total_duration = (time.time() - start_time) * 1000
  183. logger.info(
  184. f"⏭️ 机器人名字未变化,跳过更新: user_id={user_id}, "
  185. f"bot_name={bot_name}, 耗时={total_duration:.1f}ms"
  186. )
  187. return True
  188. # 执行更新
  189. update_start = time.time()
  190. update_result = await session.execute(
  191. update(User)
  192. .where(User.user_id == user_id)
  193. .values(bot_name=bot_name)
  194. )
  195. await session.commit()
  196. update_duration = (time.time() - update_start) * 1000
  197. total_duration = (time.time() - start_time) * 1000
  198. if update_result.rowcount > 0:
  199. logger.info(
  200. f"✅ 更新机器人名字成功: user_id={user_id}, "
  201. f"bot_name={bot_name}, "
  202. f"查询耗时={query_duration:.1f}ms, "
  203. f"更新耗时={update_duration:.1f}ms, "
  204. f"总耗时={total_duration:.1f}ms"
  205. )
  206. return True
  207. else:
  208. logger.warning(
  209. f"⚠️ 更新机器人名字失败: 未找到用户 user_id={user_id}, "
  210. f"总耗时={total_duration:.1f}ms"
  211. )
  212. return False
  213. except asyncio.TimeoutError:
  214. total_duration = (time.time() - start_time) * 1000
  215. logger.error(
  216. f"❌ 更新机器人名字超时: user_id={user_id}, "
  217. f"耗时={total_duration:.1f}ms"
  218. )
  219. return False
  220. except Exception as e:
  221. total_duration = (time.time() - start_time) * 1000
  222. logger.error(
  223. f"❌ 更新机器人名字失败: user_id={user_id}, "
  224. f"error={e}, 耗时={total_duration:.1f}ms"
  225. )
  226. return False
  227. async def update_speech_duration(self, user_id: str, duration_ms: int) -> Optional[int]:
  228. """
  229. 更新用户累积说话时长
  230. Args:
  231. user_id: 用户ID
  232. duration_ms: 本次说话时长(毫秒)
  233. Returns:
  234. 是否更新成功
  235. """
  236. try:
  237. if duration_ms <= 0:
  238. return None # 时长无效,跳过更新
  239. async with db_manager.get_session() as session:
  240. result = await session.execute(
  241. select(User.total_speech_duration_ms)
  242. .where(User.user_id == user_id)
  243. .with_for_update()
  244. )
  245. current_total = result.scalar_one_or_none() or 0
  246. new_total = current_total + duration_ms
  247. await session.execute(
  248. update(User)
  249. .where(User.user_id == user_id)
  250. .values(total_speech_duration_ms=new_total)
  251. )
  252. await session.commit()
  253. logger.info(
  254. f"✅ 更新用户说话时长成功: user_id={user_id}, "
  255. f"duration_ms={duration_ms}, total_ms={new_total}"
  256. )
  257. return new_total
  258. except Exception as e:
  259. logger.error(f"❌ 更新用户说话时长失败: user_id={user_id}, error={e}")
  260. return None
  261. # 全局用户服务实例
  262. user_service = UserService()