database_loader.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. def init_database():
  9. """
  10. 初始化数据库:如果数据库或表不存在,则创建它们。
  11. """
  12. logger.info("--- 开始初始化数据库 ---")
  13. try:
  14. cnx = mysql.connector.connect(**settings.DATABASE_CONFIG)
  15. cursor = cnx.cursor()
  16. cursor.execute(f"CREATE DATABASE IF NOT EXISTS {settings.DB_NAME} DEFAULT CHARACTER SET 'utf8mb4'")
  17. logger.info(f"数据库 '{settings.DB_NAME}' 已准备就绪。")
  18. cnx.database = settings.DB_NAME
  19. # 1. 创建 cards 表
  20. cards_table = (
  21. f"CREATE TABLE IF NOT EXISTS `{settings.DB_CARD_TABLE_NAME}` ("
  22. " `id` INT AUTO_INCREMENT PRIMARY KEY,"
  23. " `card_name` VARCHAR(255) NULL COMMENT '卡牌的通用名称',"
  24. " `cardNo` VARCHAR(100) NULL COMMENT '卡牌编号',"
  25. " `card_type` VARCHAR(50) NOT NULL DEFAULT 'pokemon',"
  26. " `detection_score` DECIMAL(4, 2) NULL,"
  27. " `modified_score` DECIMAL(4, 2) NULL,"
  28. " `is_edited` BOOLEAN NOT NULL DEFAULT FALSE,"
  29. " `review_state` TINYINT NOT NULL DEFAULT 1 COMMENT '审核状态(1待复检, 2已复检, 3审核未通过, 4审核通过)',"
  30. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  31. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
  32. ") ENGINE=InnoDB COMMENT='存储实体卡牌的核心信息'"
  33. )
  34. cursor.execute(cards_table)
  35. logger.info(f"数据表 '{settings.DB_CARD_TABLE_NAME}' 已准备就绪。")
  36. # User table: username is the login account, nickname is only for display.
  37. users_table = (
  38. f"CREATE TABLE IF NOT EXISTS `{settings.DB_USER_TABLE_NAME}` ("
  39. " `id` INT AUTO_INCREMENT PRIMARY KEY,"
  40. " `username` VARCHAR(20) NOT NULL,"
  41. " `nickname` VARCHAR(100) NOT NULL,"
  42. " `password` VARCHAR(255) NOT NULL,"
  43. " `is_admin` BOOLEAN NOT NULL DEFAULT FALSE,"
  44. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  45. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,"
  46. " UNIQUE KEY `uk_users_username` (`username`)"
  47. ") ENGINE=InnoDB COMMENT='users'"
  48. )
  49. cursor.execute(users_table)
  50. logger.info(f"数据表 '{settings.DB_USER_TABLE_NAME}' 已准备就绪。")
  51. # Mapping table: one card can be assigned to multiple users.
  52. user_card_table = (
  53. f"CREATE TABLE IF NOT EXISTS `{settings.DB_USER_CARD_TABLE_NAME}` ("
  54. " `id` INT AUTO_INCREMENT PRIMARY KEY,"
  55. " `user_id` INT NOT NULL,"
  56. " `card_id` INT NOT NULL,"
  57. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  58. f" FOREIGN KEY (`user_id`) REFERENCES `{settings.DB_USER_TABLE_NAME}`(`id`) ON DELETE CASCADE,"
  59. f" FOREIGN KEY (`card_id`) REFERENCES `{settings.DB_CARD_TABLE_NAME}`(`id`) ON DELETE CASCADE,"
  60. " UNIQUE KEY `uk_user_card` (`user_id`, `card_id`),"
  61. " INDEX `idx_user_card_card_id` (`card_id`)"
  62. ") ENGINE=InnoDB COMMENT='user card bindings'"
  63. )
  64. cursor.execute(user_card_table)
  65. logger.info(f"数据表 '{settings.DB_USER_CARD_TABLE_NAME}' 已准备就绪。")
  66. # 2. 创建 card_images 表 (主要计算图)
  67. card_images_table = (
  68. f"CREATE TABLE IF NOT EXISTS `{settings.DB_IMAGE_TABLE_NAME}` ("
  69. " `id` INT AUTO_INCREMENT PRIMARY KEY,"
  70. " `card_id` INT NOT NULL COMMENT '关联的卡牌ID',"
  71. " `image_type` VARCHAR(50) NOT NULL COMMENT '图片类型 (front_coaxial, back_ring, etc)',"
  72. " `image_name` VARCHAR(255) NULL,"
  73. " `image_path` VARCHAR(512) NOT NULL,"
  74. " `detection_image_path` VARCHAR(512) NULL,"
  75. " `modified_image_path` VARCHAR(512) NULL,"
  76. " `detection_json` JSON NOT NULL,"
  77. " `modified_json` JSON NULL,"
  78. " `is_edited` BOOLEAN NOT NULL DEFAULT FALSE,"
  79. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  80. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,"
  81. f" FOREIGN KEY (`card_id`) REFERENCES `{settings.DB_CARD_TABLE_NAME}`(`id`) ON DELETE CASCADE,"
  82. " UNIQUE KEY `uk_card_image_type` (`card_id`, `image_type`)"
  83. ") ENGINE=InnoDB COMMENT='存储主要卡牌图片及检测数据'"
  84. )
  85. cursor.execute(card_images_table)
  86. logger.info(f"数据表 '{settings.DB_IMAGE_TABLE_NAME}' 已准备就绪。")
  87. # 3. 创建 card_gray_images 表 (辅助灰度图)
  88. card_gray_images_table = (
  89. f"CREATE TABLE IF NOT EXISTS `{settings.DB_GRAY_IMAGE_TABLE_NAME}` ("
  90. " `id` INT AUTO_INCREMENT PRIMARY KEY,"
  91. " `card_id` INT NOT NULL COMMENT '关联的卡牌ID',"
  92. " `image_type` VARCHAR(50) NOT NULL COMMENT '类型 (front_gray, back_gray)',"
  93. " `image_path` VARCHAR(512) NOT NULL,"
  94. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  95. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,"
  96. f" FOREIGN KEY (`card_id`) REFERENCES `{settings.DB_CARD_TABLE_NAME}`(`id`) ON DELETE CASCADE,"
  97. " UNIQUE KEY `uk_card_gray_type` (`card_id`, `image_type`)"
  98. ") ENGINE=InnoDB COMMENT='存储辅助灰度图'"
  99. )
  100. cursor.execute(card_gray_images_table)
  101. rating_report_history_table = (
  102. "CREATE TABLE IF NOT EXISTS `rating_report_history` ("
  103. " `rating_id` BIGINT AUTO_INCREMENT PRIMARY KEY,"
  104. " `card_id` INT NOT NULL,"
  105. " `cardNo` VARCHAR(100) NOT NULL,"
  106. " `report_name` VARCHAR(255) NOT NULL,"
  107. " `report_json` JSON NOT NULL,"
  108. " `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
  109. " `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,"
  110. f" FOREIGN KEY (`card_id`) REFERENCES `{settings.DB_CARD_TABLE_NAME}`(`id`) ON DELETE CASCADE,"
  111. " INDEX `idx_rating_history_card_no_created` (`cardNo`, `created_at`),"
  112. " INDEX `idx_rating_history_card_id_created` (`card_id`, `created_at`)"
  113. ") ENGINE=InnoDB COMMENT='拆卡报告历史'"
  114. )
  115. cursor.execute(rating_report_history_table)
  116. logger.info(f"数据表 '{settings.DB_GRAY_IMAGE_TABLE_NAME}' 已准备就绪。")
  117. except mysql.connector.Error as err:
  118. logger.error(f"数据库初始化失败: {err}")
  119. exit(1)
  120. finally:
  121. if 'cursor' in locals() and cursor:
  122. cursor.close()
  123. if 'cnx' in locals() and cnx.is_connected():
  124. cnx.close()
  125. logger.info("--- 数据库初始化完成 ---")
  126. def load_database_pool():
  127. global db_connection_pool
  128. if db_connection_pool is None:
  129. logger.info("--- 创建数据库连接池 ---")
  130. try:
  131. db_connection_pool = mysql.connector.pooling.MySQLConnectionPool(
  132. pool_name="mypool",
  133. pool_size=5,
  134. **settings.DATABASE_CONFIG_WITH_DB
  135. )
  136. logger.info("--- 数据库连接池创建成功 ---")
  137. except mysql.connector.Error as err:
  138. logger.error(f"创建数据库连接池失败: {err}")
  139. exit(1)
  140. def close_database_pool():
  141. logger.info("--- 数据库连接池将自动关闭 ---")
  142. # --- FastAPI 依赖注入 ---
  143. def get_db_connection():
  144. if db_connection_pool is None:
  145. raise RuntimeError("数据库连接池未初始化")
  146. db_conn = None
  147. try:
  148. db_conn = db_connection_pool.get_connection()
  149. # 强制设置当前数据库会话时区为东八区
  150. cursor = db_conn.cursor()
  151. cursor.execute("SET time_zone = '+08:00';")
  152. cursor.close()
  153. yield db_conn
  154. except mysql.connector.Error as err:
  155. logger.error(f"获取数据库连接失败: {err}")
  156. finally:
  157. if db_conn and db_conn.is_connected():
  158. db_conn.close()