database.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. """
  2. 数据库连接管理模块
  3. """
  4. import aiomysql
  5. import redis.asyncio as aioredis
  6. from typing import Optional
  7. from contextlib import asynccontextmanager
  8. from config import settings
  9. import logging
  10. logger = logging.getLogger(__name__)
  11. class DatabaseManager:
  12. """数据库连接管理器(MySQL + Redis)"""
  13. def __init__(self):
  14. self.mysql_pool: Optional[aiomysql.Pool] = None
  15. self.redis_client: Optional[aioredis.Redis] = None
  16. async def connect(self):
  17. """初始化数据库连接"""
  18. try:
  19. # 连接MySQL
  20. self.mysql_pool = await aiomysql.create_pool(
  21. host=settings.MYSQL_HOST,
  22. port=settings.MYSQL_PORT,
  23. user=settings.MYSQL_USER,
  24. password=settings.MYSQL_PASSWORD,
  25. db=settings.MYSQL_DATABASE,
  26. charset='utf8mb4',
  27. minsize=5,
  28. maxsize=settings.MYSQL_POOL_SIZE,
  29. autocommit=True,
  30. echo=False
  31. )
  32. logger.info(f"✅ MySQL连接池已创建: {settings.MYSQL_HOST}:{settings.MYSQL_PORT}/{settings.MYSQL_DATABASE}")
  33. # 连接Redis
  34. # 根据SSL配置选择协议(rediss:// 或 redis://)
  35. redis_protocol = "rediss" if settings.REDIS_SSL else "redis"
  36. redis_url = f"{redis_protocol}://:{settings.REDIS_PASSWORD}@{settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.REDIS_DB}"
  37. self.redis_client = await aioredis.from_url(
  38. redis_url,
  39. encoding="utf-8",
  40. decode_responses=True,
  41. max_connections=settings.REDIS_MAX_CONNECTIONS,
  42. socket_connect_timeout=10, # 增加连接超时到10秒
  43. socket_timeout=10, # 增加操作超时到10秒
  44. retry_on_timeout=True, # 超时自动重试
  45. health_check_interval=30 # 健康检查间隔30秒
  46. )
  47. # 测试Redis连接
  48. await self.redis_client.ping()
  49. logger.info(f"✅ Redis连接成功: {settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.REDIS_DB}")
  50. except Exception as e:
  51. logger.error(f"❌ 数据库连接失败: {e}")
  52. raise
  53. async def disconnect(self):
  54. """关闭数据库连接"""
  55. try:
  56. if self.mysql_pool:
  57. self.mysql_pool.close()
  58. await self.mysql_pool.wait_closed()
  59. logger.info("✅ MySQL连接池已关闭")
  60. if self.redis_client:
  61. await self.redis_client.close()
  62. logger.info("✅ Redis连接已关闭")
  63. except Exception as e:
  64. logger.error(f"❌ 关闭数据库连接失败: {e}")
  65. @asynccontextmanager
  66. async def get_mysql_connection(self):
  67. """获取MySQL连接(上下文管理器)"""
  68. if not self.mysql_pool:
  69. raise RuntimeError("MySQL连接池未初始化")
  70. async with self.mysql_pool.acquire() as conn:
  71. async with conn.cursor(aiomysql.DictCursor) as cursor:
  72. yield cursor
  73. async def execute_query(self, query: str, params: dict = None):
  74. """执行查询(SELECT)"""
  75. async with self.get_mysql_connection() as cursor:
  76. await cursor.execute(query, params or {})
  77. return await cursor.fetchall()
  78. async def execute_one(self, query: str, params: dict = None):
  79. """执行查询并返回一条记录"""
  80. async with self.get_mysql_connection() as cursor:
  81. await cursor.execute(query, params or {})
  82. return await cursor.fetchone()
  83. async def execute_update(self, query: str, params: dict = None):
  84. """执行更新(INSERT/UPDATE/DELETE)"""
  85. async with self.get_mysql_connection() as cursor:
  86. await cursor.execute(query, params or {})
  87. return cursor.lastrowid
  88. def get_redis(self) -> aioredis.Redis:
  89. """获取Redis客户端"""
  90. if not self.redis_client:
  91. raise RuntimeError("Redis客户端未初始化")
  92. return self.redis_client
  93. # 全局数据库管理器实例
  94. db_manager = DatabaseManager()
  95. async def init_database():
  96. """初始化数据库连接"""
  97. await db_manager.connect()
  98. async def close_database():
  99. """关闭数据库连接"""
  100. await db_manager.disconnect()