database_loader.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import mysql.connector
  2. from mysql.connector import errorcode
  3. from .config import settings
  4. from app.core.logger import get_logger
  5. logger = get_logger(__name__)
  6. # 全局的连接池
  7. db_connection_pool = None
  8. import mysql.connector
  9. from mysql.connector import errorcode
  10. from .config import settings
  11. from app.core.logger import get_logger
  12. logger = get_logger(__name__)
  13. # 全局的连接池
  14. db_connection_pool = None
  15. def init_database():
  16. """
  17. 初始化数据库:如果数据库或表不存在,则创建它们。
  18. """
  19. logger.info("--- 开始初始化数据库 ---")
  20. try:
  21. cnx = mysql.connector.connect(**settings.DATABASE_CONFIG)
  22. cursor = cnx.cursor()
  23. cursor.execute(f"CREATE DATABASE IF NOT EXISTS {settings.DB_NAME} DEFAULT CHARACTER SET 'utf8mb4'")
  24. logger.info(f"数据库 '{settings.DB_NAME}' 已准备就绪。")
  25. cnx.database = settings.DB_NAME
  26. # 1. 创建 card_images 表 (因为它被 cards 表引用)
  27. card_images_table = (
  28. f"CREATE TABLE IF NOT EXISTS `{settings.DB_IMAGE_TABLE_NAME}` ("
  29. " `image_id` INT AUTO_INCREMENT PRIMARY KEY,"
  30. " `image_name` VARCHAR(255) NULL,"
  31. " `image_path` VARCHAR(512) NOT NULL,"
  32. " `detection_json` JSON NOT NULL COMMENT '原始检测JSON数据',"
  33. " `modified_json` JSON NULL COMMENT '修改后的JSON数据',"
  34. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  35. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'JSON数据最后修改时间'"
  36. ") ENGINE=InnoDB COMMENT='存储所有卡牌的图片及检测数据'"
  37. )
  38. cursor.execute(card_images_table)
  39. logger.info(f"数据表 '{settings.DB_IMAGE_TABLE_NAME}' 已准备就绪。")
  40. # 2. 创建 cards 表 (不带外键)
  41. cards_table = (
  42. f"CREATE TABLE IF NOT EXISTS `{settings.DB_CARD_TABLE_NAME}` ("
  43. " `card_id` INT AUTO_INCREMENT PRIMARY KEY,"
  44. " `card_name` VARCHAR(255) NULL COMMENT '卡牌的通用名称',"
  45. " `front_face_id` INT NULL,"
  46. " `back_face_id` INT NULL,"
  47. " `front_edge_id` INT NULL,"
  48. " `back_edge_id` INT NULL,"
  49. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  50. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
  51. ") ENGINE=InnoDB COMMENT='存储实体卡牌及其核心图片引用'"
  52. )
  53. cursor.execute(cards_table)
  54. logger.info(f"数据表 '{settings.DB_CARD_TABLE_NAME}' 已准备就绪。")
  55. # 3. 为 cards 表添加外键约束 (如果它们不存在)
  56. # 这是一个更健壮的方法,可以重复运行而不会出错
  57. foreign_keys = {
  58. "fk_front_face": "front_face_id",
  59. "fk_back_face": "back_face_id",
  60. "fk_front_edge": "front_edge_id",
  61. "fk_back_edge": "back_edge_id"
  62. }
  63. for fk_name, col_name in foreign_keys.items():
  64. try:
  65. # 检查外键是否存在
  66. cursor.execute(f"""
  67. SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE
  68. WHERE TABLE_SCHEMA = '{settings.DB_NAME}'
  69. AND TABLE_NAME = '{settings.DB_CARD_TABLE_NAME}'
  70. AND CONSTRAINT_NAME = '{fk_name}';
  71. """)
  72. if cursor.fetchone()[0] == 0:
  73. alter_query = (
  74. f"ALTER TABLE `{settings.DB_CARD_TABLE_NAME}` "
  75. f"ADD CONSTRAINT `{fk_name}` "
  76. f"FOREIGN KEY (`{col_name}`) "
  77. f"REFERENCES `{settings.DB_IMAGE_TABLE_NAME}`(`image_id`) "
  78. "ON DELETE SET NULL"
  79. )
  80. cursor.execute(alter_query)
  81. logger.info(f"为表 '{settings.DB_CARD_TABLE_NAME}' 添加了外键 '{fk_name}'。")
  82. except mysql.connector.Error as err:
  83. # 1060: Duplicate column name; 1061: Duplicate key name; 1826: Duplicate foreign key
  84. if err.errno in [1060, 1061, 1826]:
  85. logger.warning(f"外键 '{fk_name}' 可能已存在。跳过。")
  86. else:
  87. raise err
  88. except mysql.connector.Error as err:
  89. logger.error(f"数据库初始化失败: {err}")
  90. exit(1)
  91. finally:
  92. if 'cursor' in locals() and cursor:
  93. cursor.close()
  94. if 'cnx' in locals() and cnx.is_connected():
  95. cnx.close()
  96. logger.info("--- 数据库初始化完成 ---")
  97. def load_database_pool():
  98. """
  99. 在应用启动时创建数据库连接池。
  100. """
  101. global db_connection_pool
  102. if db_connection_pool is None:
  103. logger.info("--- 创建数据库连接池 ---")
  104. try:
  105. db_connection_pool = mysql.connector.pooling.MySQLConnectionPool(
  106. pool_name="mypool",
  107. pool_size=5, # 池中保持的连接数
  108. **settings.DATABASE_CONFIG_WITH_DB
  109. )
  110. logger.info("--- 数据库连接池创建成功 ---")
  111. except mysql.connector.Error as err:
  112. logger.error(f"创建数据库连接池失败: {err}")
  113. exit(1)
  114. def close_database_pool():
  115. """
  116. 在应用关闭时,不需要手动关闭连接池,连接器会自动处理。
  117. 这个函数留作备用。
  118. """
  119. logger.info("--- 数据库连接池将自动关闭 ---")
  120. # --- FastAPI 依赖注入 ---
  121. def get_db_connection():
  122. """
  123. 一个FastAPI依赖项,用于从池中获取数据库连接。
  124. 它确保连接在使用后返回到池中。
  125. """
  126. if db_connection_pool is None:
  127. raise RuntimeError("数据库连接池未初始化")
  128. db_conn = None
  129. try:
  130. db_conn = db_connection_pool.get_connection()
  131. yield db_conn
  132. except mysql.connector.Error as err:
  133. logger.error(f"获取数据库连接失败: {err}")
  134. # 这里可以根据需要抛出HTTPException
  135. finally:
  136. if db_conn and db_conn.is_connected():
  137. db_conn.close()